From 345e6460172e6b468cadbb8cc804399f61b3ab56 Mon Sep 17 00:00:00 2001 From: Anastasia Yaskevich Date: Wed, 26 Jun 2024 14:02:31 +0200 Subject: [PATCH 1/4] chore: Datepicker defaultProps refactoring --- .../ui/calendar/baseCalendar.component.tsx | 448 +++++++++--------- .../ui/calendar/calendar.component.tsx | 92 ++-- src/components/ui/calendar/calendar.spec.tsx | 22 +- .../ui/calendar/rangeCalendar.component.tsx | 102 ++-- .../ui/calendar/rangeCalendar.spec.tsx | 6 +- .../datepicker/baseDatepicker.component.tsx | 281 +++++------ .../ui/datepicker/baseDatepicker.utils.ts | 68 +++ .../ui/datepicker/datepicker.component.tsx | 129 +++-- .../ui/datepicker/datepicker.spec.tsx | 88 ++-- .../datepicker/rangeDatepicker.component.tsx | 107 +++-- .../ui/datepicker/rangeDatepicker.spec.tsx | 25 +- src/components/ui/index.ts | 4 + .../calendarInitialVisibleDate.component.tsx | 4 +- ...datepickerInitialVisibleDate.component.tsx | 4 +- 14 files changed, 754 insertions(+), 626 deletions(-) create mode 100644 src/components/ui/datepicker/baseDatepicker.utils.ts diff --git a/src/components/ui/calendar/baseCalendar.component.tsx b/src/components/ui/calendar/baseCalendar.component.tsx index 33b7e4c85..bc334b4bf 100644 --- a/src/components/ui/calendar/baseCalendar.component.tsx +++ b/src/components/ui/calendar/baseCalendar.component.tsx @@ -36,12 +36,19 @@ import { } from './type'; import { TranslationWidth } from './i18n/type'; import { DateService } from './service/date.service'; -import { NativeDateService } from './service/nativeDate.service'; import { CalendarDataService, DateBatch, } from './service/calendarData.service'; +export interface DerivedCalendarProps extends BaseCalendarProps { + createDates: (date: D) => DateBatch; + selectedDate: () => D | undefined; + onDateSelect: (item: D) => void; + isDateSelected: (date: D) => boolean; + shouldUpdateDate: (props: CalendarPickerCellProps, nextProps: CalendarPickerCellProps) => boolean; +} + export interface BaseCalendarProps extends ViewProps { min?: D; max?: D; @@ -61,68 +68,95 @@ export interface BaseCalendarProps extends ViewProps { eva?: EvaProp; } -export type BaseCalendarElement = React.ReactElement>; +export type BaseCalendarElement = React.ReactElement>; + +const PICKER_ROWS = 4; +const PICKER_COLUMNS = 3; +const VIEWS_IN_PICKER: number = PICKER_ROWS * PICKER_COLUMNS; + +export interface BaseCalendarRef { + scrollToToday: () => void; + scrollToDate: (date: D) => void; + dataService: CalendarDataService; + state: State; +} interface State { viewMode: CalendarViewMode; - visibleDate: D; // is used in date view mode - pickerDate: D; // is used in month/year view mode, goal - not to change visibleDate until month has changed - // pickerDate equals to visibleDate from start - // is auto synchronised with visibleDate on onPickerNavigationPress (open/close month/year picker) - // visibleDate is set to pickerDate on onMonthSelect + visibleDate: D; + pickerDate: D; } -const PICKER_ROWS = 4; -const PICKER_COLUMNS = 3; -const VIEWS_IN_PICKER: number = PICKER_ROWS * PICKER_COLUMNS; +function BaseCalendarComponent( + { + dateService, + boundingMonth = true, + startView = CalendarViewModes.DATE, + ...props + }: DerivedCalendarProps, + ref: React.RefObject> +): BaseCalendarElement { + if (!dateService) { + throw Error('No dateService'); + } + const dataService: CalendarDataService = new CalendarDataService(dateService); + const [viewMode, setViewMode] = React.useState(startView); -export abstract class BaseCalendarComponent extends React.Component & P, State> { - static defaultProps: Partial = { - dateService: new NativeDateService(), - boundingMonth: true, - startView: CalendarViewModes.DATE, - }; + console.log('base current ref', ref.current); - public state: State = { - viewMode: this.props.startView, - visibleDate: this.dateService.getMonthStart(this.initialVisibleDate()), - pickerDate: this.dateService.getMonthStart(this.initialVisibleDate()), + const initialVisibleDate = (): D => { + return props.initialVisibleDate || props.selectedDate() || dateService.today(); }; - protected dataService: CalendarDataService = new CalendarDataService(this.dateService); + // is used in date view mode + const [ + visibleDate, + setVisibleDate, + ] = React.useState(dateService.getMonthStart(initialVisibleDate())); - protected get dateService(): DateService { - return this.props.dateService; - } + // is used in month/year view mode, goal - not to change visibleDate until month has changed + const [pickerDate, setPickerDate, + ] = React.useState(dateService.getMonthStart(initialVisibleDate())); - private get min(): D { - return this.props.min || this.dateService.getYearStart(this.dateService.today()); - } + // pickerDate equals to visibleDate from start + // is auto synchronised with visibleDate on onPickerNavigationPress (open/close month/year picker) + // visibleDate is set to pickerDate on onMonthSelect - private get max(): D { - return this.props.max || this.dateService.getYearEnd(this.dateService.today()); - } + const min = (): D => { + return props.min || dateService.getYearStart(dateService.today()); + }; - public scrollToToday = (): void => { - this.setState({ - viewMode: CalendarViewModes.DATE, - visibleDate: this.dateService.today(), - pickerDate: this.dateService.today(), - }); + const max = (): D => { + return props.max || dateService.getYearEnd(dateService.today()); }; - public scrollToDate = (date: D): void => { + React.useImperativeHandle(ref, () => ({ + scrollToToday, + scrollToDate, + dataService, + state: { + viewMode, + visibleDate, + pickerDate, + }, + })); + + const scrollToToday = (): void => { + setViewMode(CalendarViewModes.DATE); + setVisibleDate(dateService.today()); + setPickerDate(dateService.today()); + }; + + const scrollToDate = (date: D): void => { if (date) { - this.setState({ - viewMode: CalendarViewModes.DATE, - visibleDate: date, - pickerDate: date, - }); + setViewMode(CalendarViewModes.DATE); + setVisibleDate(date); + setPickerDate(date); } }; - public getCalendarStyle = (source: StyleType): StyleType => { + const getCalendarStyle = (source: StyleType): StyleType => { return { container: { width: source.width, @@ -159,111 +193,84 @@ export abstract class BaseCalendarComponent extends React.Component }; }; - public isDayDisabled = ({ date }: CalendarDateInfo): boolean => { - const minDayStart: D = this.dateService.createDate( - this.dateService.getYear(this.min), - this.dateService.getMonth(this.min), - this.dateService.getDate(this.min), + const isDayDisabled = ({ date }: CalendarDateInfo): boolean => { + const minDayStart: D = dateService.createDate( + dateService.getYear(min()), + dateService.getMonth(min()), + dateService.getDate(min()), ); - const maxDayStart: D = this.dateService.createDate( - this.dateService.getYear(this.max), - this.dateService.getMonth(this.max), - this.dateService.getDate(this.max), + const maxDayStart: D = dateService.createDate( + dateService.getYear(max()), + dateService.getMonth(max()), + dateService.getDate(max()), ); - const fitsFilter: boolean = this.props.filter && !this.props.filter(date) || false; + const fitsFilter: boolean = props.filter && !props.filter(date) || false; - return !this.dateService.isBetweenIncludingSafe(date, minDayStart, maxDayStart) || fitsFilter; + return !dateService.isBetweenIncludingSafe(date, minDayStart, maxDayStart) || fitsFilter; }; - public isDayToday = ({ date }: CalendarDateInfo): boolean => { - return this.dateService.isSameDaySafe(date, this.dateService.today()); + const isDayToday = ({ date }: CalendarDateInfo): boolean => { + return dateService.isSameDaySafe(date, dateService.today()); }; - protected abstract createDates(date: D): DateBatch; - - protected abstract selectedDate(): D | undefined; - - protected abstract onDateSelect(item: D): void; - - protected abstract isDateSelected(date: D): boolean; - - protected abstract shouldUpdateDate(props: CalendarPickerCellProps, - nextProps: CalendarPickerCellProps): boolean; - - private initialVisibleDate(): D { - return this.props.initialVisibleDate || this.selectedDate() || this.dateService.today(); - } - - private onDaySelect = ({ date }: CalendarDateInfo): void => { - this.onDateSelect(date); + const onDaySelect = ({ date }: CalendarDateInfo): void => { + props.onDateSelect(date); }; - private onMonthSelect = ({ date }: CalendarDateInfo): void => { - const { pickerDate, viewMode } = this.state; - const nextVisibleDate: D = this.dateService.createDate( - this.dateService.getYear(pickerDate), - this.dateService.getMonth(date), - this.dateService.getDate(pickerDate), + const onMonthSelect = ({ date }: CalendarDateInfo): void => { + const nextVisibleDate: D = dateService.createDate( + dateService.getYear(pickerDate), + dateService.getMonth(date), + dateService.getDate(pickerDate), ); - this.setState({ - viewMode: viewMode.pickNext(), - visibleDate: nextVisibleDate, - pickerDate: nextVisibleDate, - }, () => { - this.props.onVisibleDateChange?.(this.state.visibleDate, this.state.viewMode.id); - }); - }; - - private onYearSelect = ({ date }: CalendarDateInfo): void => { - const { pickerDate, viewMode } = this.state; - const nextVisibleDate: D = this.dateService.createDate( - this.dateService.getYear(date), - this.dateService.getMonth(pickerDate), - this.dateService.getDate(pickerDate), + setViewMode(viewMode.pickNext()); + setVisibleDate(nextVisibleDate); + setPickerDate(nextVisibleDate); + props.onVisibleDateChange?.(nextVisibleDate, viewMode.id); + }; + + const onYearSelect = ({ date }: CalendarDateInfo): void => { + const nextVisibleDate: D = dateService.createDate( + dateService.getYear(date), + dateService.getMonth(pickerDate), + dateService.getDate(pickerDate), ); - this.setState({ - viewMode: viewMode.pickNext(), - pickerDate: nextVisibleDate, - }); + setViewMode(viewMode.pickNext()); + setPickerDate(nextVisibleDate); }; - private onPickerNavigationPress = (): void => { - const { viewMode, visibleDate } = this.state; - this.setState({ - viewMode: viewMode.navigationNext(), - pickerDate: visibleDate, - }); + const onPickerNavigationPress = (): void => { + setViewMode(viewMode.navigationNext()); + setPickerDate(visibleDate); }; - private onHeaderNavigationLeftPress = (): void => { - const nextDate = this.createViewModeVisibleDate(-1); + const onHeaderNavigationLeftPress = (): void => { + const nextDate = createViewModeVisibleDate(-1); - if (this.state.viewMode.id === CalendarViewModes.DATE.id) { - this.setState({ visibleDate: nextDate }, () => { - this.props.onVisibleDateChange?.(this.state.visibleDate, this.state.viewMode.id); - }); + if (viewMode.id === CalendarViewModes.DATE.id) { + setVisibleDate(nextDate); + props.onVisibleDateChange?.(visibleDate, viewMode.id); } else { - this.setState({ pickerDate: nextDate }); + setPickerDate(nextDate); } }; - private onHeaderNavigationRightPress = (): void => { - const nextDate = this.createViewModeVisibleDate(1); + const onHeaderNavigationRightPress = (): void => { + const nextDate = createViewModeVisibleDate(1); - if (this.state.viewMode.id === CalendarViewModes.DATE.id) { - this.setState({ visibleDate: nextDate }, () => { - this.props.onVisibleDateChange?.(this.state.visibleDate, this.state.viewMode.id); - }); + if (viewMode.id === CalendarViewModes.DATE.id) { + setVisibleDate(nextDate); + props.onVisibleDateChange?.(visibleDate, viewMode.id); } else { - this.setState({ pickerDate: nextDate }); + setPickerDate(nextDate); } }; - private getWeekdayStyle = (source: StyleType): StyleType => { + const getWeekdayStyle = (source: StyleType): StyleType => { return { fontSize: source.weekdayTextFontSize, fontWeight: source.weekdayTextFontWeight, @@ -272,71 +279,71 @@ export abstract class BaseCalendarComponent extends React.Component }; }; - private isDaySelected = ({ date }: CalendarDateInfo): boolean => { - return this.isDateSelected(date); + const isDaySelected = ({ date }: CalendarDateInfo): boolean => { + return props.isDateSelected(date); }; - private isMonthSelected = ({ date }: CalendarDateInfo): boolean => { - return this.dateService.isSameMonthSafe(date, this.selectedDate()); + const isMonthSelected = ({ date }: CalendarDateInfo): boolean => { + return dateService.isSameMonthSafe(date, props.selectedDate()); }; - private isYearSelected = ({ date }: CalendarDateInfo): boolean => { - return this.dateService.isSameYearSafe(date, this.selectedDate()); + const isYearSelected = ({ date }: CalendarDateInfo): boolean => { + return dateService.isSameYearSafe(date, props.selectedDate()); }; - private isMonthDisabled = ({ date }: CalendarDateInfo): boolean => { - const minMonthStart: D = this.dateService.getMonthStart(this.min); - const maxMonthStart: D = this.dateService.getMonthStart(this.max); + const isMonthDisabled = ({ date }: CalendarDateInfo): boolean => { + const minMonthStart: D = dateService.getMonthStart(min()); + const maxMonthStart: D = dateService.getMonthStart(max()); - return !this.dateService.isBetweenIncludingSafe(date, minMonthStart, maxMonthStart); + return !dateService.isBetweenIncludingSafe(date, minMonthStart, maxMonthStart); }; - private isYearDisabled = ({ date }: CalendarDateInfo): boolean => { - const minYearStart: D = this.dateService.getYearStart(this.min); - const maxYearStart: D = this.dateService.getYearEnd(this.max); + const isYearDisabled = ({ date }: CalendarDateInfo): boolean => { + const minYearStart: D = dateService.getYearStart(min()); + const maxYearStart: D = dateService.getYearEnd(max()); - return !this.dateService.isBetweenIncludingSafe(date, minYearStart, maxYearStart); + return !dateService.isBetweenIncludingSafe(date, minYearStart, maxYearStart); }; - private isMonthToday = (date: CalendarDateInfo): boolean => { - return this.dateService.isSameMonthSafe(date.date, this.dateService.today()); + const isMonthToday = (date: CalendarDateInfo): boolean => { + return dateService.isSameMonthSafe(date.date, dateService.today()); }; - private isYearToday = ({ date }: CalendarDateInfo): boolean => { - return this.dateService.isSameYearSafe(date, this.dateService.today()); + const isYearToday = ({ date }: CalendarDateInfo): boolean => { + return dateService.isSameYearSafe(date, dateService.today()); }; - private isHeaderNavigationAllowed = (): boolean => { - return this.state.viewMode.id !== CalendarViewModes.MONTH.id; + const isHeaderNavigationAllowed = (): boolean => { + return viewMode.id !== CalendarViewModes.MONTH.id; }; - private createViewModeVisibleDate = (page: number): D => { - switch (this.state.viewMode.id) { + const createViewModeVisibleDate = (page: number): D => { + switch (viewMode.id) { case CalendarViewModes.DATE.id: { - return this.dateService.addMonth(this.state.visibleDate, page); + return dateService.addMonth(visibleDate, page); } case CalendarViewModes.MONTH.id: { - return this.dateService.addYear(this.state.pickerDate, page); + return dateService.addYear(pickerDate, page); } case CalendarViewModes.YEAR.id: { - return this.dateService.addYear(this.state.pickerDate, VIEWS_IN_PICKER * page); + return dateService.addYear(pickerDate, VIEWS_IN_PICKER * page); } default: return; } }; - private createViewModeHeaderTitle = (visibleDate: D, pickerDate: D, viewMode: CalendarViewMode): string => { - switch (viewMode.id) { + const createViewModeHeaderTitle = (newVisibleDate: D, newPickerDate: D, newViewMode: CalendarViewMode): string => { + switch (newViewMode.id) { case CalendarViewModes.DATE.id: { - const month: string = this.props.dateService.getMonthName(visibleDate, TranslationWidth.LONG); - const year: number = this.props.dateService.getYear(visibleDate); + const month: string = dateService.getMonthName(newVisibleDate, TranslationWidth.LONG); + const year: number = dateService.getYear(newVisibleDate); return `${month} ${year}`; } case CalendarViewModes.MONTH.id: { - return `${this.dateService.getYear(pickerDate)}`; + return `${dateService.getYear(newPickerDate)}`; } case CalendarViewModes.YEAR.id: { - const minDateFormat: number = this.dateService.getYear(pickerDate); + const minDateFormat: number = dateService.getYear(newPickerDate); const maxDateFormat: number = minDateFormat + VIEWS_IN_PICKER - 1; return `${minDateFormat} - ${maxDateFormat}`; @@ -345,168 +352,175 @@ export abstract class BaseCalendarComponent extends React.Component } }; - private renderDayIfNeeded = (item: CalendarDateInfo, style: StyleType): CalendarDateContentElement => { - const shouldRender: boolean = !item.bounding || this.props.boundingMonth; + const renderDayIfNeeded = (item: CalendarDateInfo, style: StyleType): CalendarDateContentElement => { + const shouldRender: boolean = !item.bounding || boundingMonth; if (shouldRender) { - const renderSelector = this.props.renderDay || this.renderDayElement; + const renderSelector = props.renderDay || renderDayElement; return renderSelector(item, style); } return null; }; - private renderWeekdayElement = (weekday: string, index: number): CalendarDateContentElement => { + const renderWeekdayElement = (weekday: string, index: number): CalendarDateContentElement => { return ( {weekday} ); }; - private renderDayElement = ({ date }: CalendarDateInfo, evaStyle): CalendarDateContentElement => { + const renderDayElement = ({ date }: CalendarDateInfo, evaStyle: StyleType): CalendarDateContentElement => { return ( - {this.dateService.getDate(date)} + {dateService.getDate(date)} ); }; - private renderMonthElement = ({ date }: CalendarDateInfo, evaStyle): CalendarDateContentElement => { + const renderMonthElement = ({ date }: CalendarDateInfo, evaStyle: StyleType): CalendarDateContentElement => { return ( - {this.dateService.getMonthName(date, TranslationWidth.SHORT)} + {dateService.getMonthName(date, TranslationWidth.SHORT)} ); }; - private renderYearElement = ({ date }: CalendarDateInfo, evaStyle): CalendarDateContentElement => { + const renderYearElement = ({ date }: CalendarDateInfo, evaStyle: StyleType): CalendarDateContentElement => { return ( - {this.dateService.getYear(date)} + {dateService.getYear(date)} ); }; - private renderDayPickerElement = (date: D, evaStyle): React.ReactElement => { + const renderDayPickerElement = (date: D, evaStyle: StyleType): React.ReactElement => { return ( <> - {this.renderWeekdayElement} + {renderWeekdayElement} - {this.renderDayIfNeeded} + {renderDayIfNeeded} ); }; - private renderMonthPickerElement = (date: D, evaStyle): CalendarPickerElement => { + const renderMonthPickerElement = (date: D, evaStyle: StyleType): CalendarPickerElement => { return ( - {this.props.renderMonth || this.renderMonthElement} + {props.renderMonth || renderMonthElement} ); }; - private renderYearPickerElement = (date: D, style: StyleType): CalendarPickerElement => { + const renderYearPickerElement = (date: D, style: StyleType): CalendarPickerElement => { return ( - {this.props.renderYear || this.renderYearElement} + {props.renderYear || renderYearElement} ); }; - private renderPickerElement = (style: StyleType): React.ReactNode => { - switch (this.state.viewMode.id) { + const renderPickerElement = (style: StyleType): React.ReactNode => { + switch (viewMode.id) { case CalendarViewModes.DATE.id: - return this.renderDayPickerElement(this.state.visibleDate, style); + return renderDayPickerElement(visibleDate, style); case CalendarViewModes.MONTH.id: - return this.renderMonthPickerElement(this.state.pickerDate, style); + return renderMonthPickerElement(pickerDate, style); case CalendarViewModes.YEAR.id: - return this.renderYearPickerElement(this.state.pickerDate, style); + return renderYearPickerElement(pickerDate, style); default: return; } }; - private renderFooterElement = (): React.ReactElement => { - if (this.props.renderFooter) { - return this.props.renderFooter(); + const renderFooterElement = (): React.ReactElement => { + if (props.renderFooter) { + return props.renderFooter(); } return null; }; - private renderHeaderElement = (evaStyle): CalendarHeaderElement => { - const titleSelector = this.props.title || this.createViewModeHeaderTitle; + + const renderHeaderElement = (evaStyle: StyleType): CalendarHeaderElement => { + const titleSelector = props.title || createViewModeHeaderTitle; return ( ); }; - public render(): React.ReactElement { - const { eva, style, ...viewProps } = this.props; - const evaStyle = this.getCalendarStyle(eva.style); + const { eva, style, ...viewProps } = props; + const evaStyle = getCalendarStyle(eva.style); - return ( - - {this.renderHeaderElement(evaStyle)} - {this.renderPickerElement(evaStyle)} - {this.renderFooterElement()} - - ); - } + return ( + + {renderHeaderElement(evaStyle)} + {renderPickerElement(evaStyle)} + {renderFooterElement()} + + ); } + +BaseCalendarComponent.displayName = 'BaseCalendarComponent'; + +const component = React.forwardRef(BaseCalendarComponent); + +export { + component as BaseCalendarComponent, +}; diff --git a/src/components/ui/calendar/calendar.component.tsx b/src/components/ui/calendar/calendar.component.tsx index 9f5364d22..eb565873b 100644 --- a/src/components/ui/calendar/calendar.component.tsx +++ b/src/components/ui/calendar/calendar.component.tsx @@ -4,6 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ +import { DateService, NativeDateService } from '@ui-kitten/components'; import React from 'react'; import { styled, @@ -12,6 +13,7 @@ import { import { BaseCalendarComponent, BaseCalendarProps, + BaseCalendarRef, } from './baseCalendar.component'; import { CalendarPickerCellProps } from './components/picker/calendarPickerCell.component'; import { DateBatch } from './service/calendarData.service'; @@ -23,6 +25,8 @@ export interface CalendarProps extends StyledComponentProps, BaseCalen export type CalendarElement = React.ReactElement>; +export type CalendarRef = BaseCalendarRef; + /** * Calendar provides a simple way to select a date. * @@ -127,47 +131,48 @@ export type CalendarElement = React.ReactElement>; * @overview-example CalendarTheming * Styling of the calendar is possible with [configuring a custom theme](guides/branding). */ +function Calendar ( + props: CalendarProps, + ref: React.RefObject>, +): CalendarElement { + const baseCalendarRef = React.useRef>(null); + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; + + React.useImperativeHandle(ref, () => ({ + ...baseCalendarRef.current, + })); + + const createDates = (date: D): DateBatch => { + if (baseCalendarRef.current) { + return baseCalendarRef.current.dataService.createDayPickerData(date); + } + return []; + }; -@styled('Calendar') -export class Calendar extends BaseCalendarComponent, D> { - - constructor(props: CalendarProps) { - super(props); - - this.createDates = this.createDates.bind(this); - this.selectedDate = this.selectedDate.bind(this); - this.onDateSelect = this.onDateSelect.bind(this); - this.isDateSelected = this.isDateSelected.bind(this); - this.shouldUpdateDate = this.shouldUpdateDate.bind(this); - } - - // BaseCalendarComponent - - protected createDates(date: D): DateBatch { - return this.dataService.createDayPickerData(date); - } - - protected selectedDate(): D | undefined { - return this.props.date; - } + const selectedDate = (): D | undefined => { + return props.date; + }; - protected onDateSelect(date: D): void { - this.props.onSelect?.(date); - } + const onDateSelect = (date: D): void => { + props.onSelect?.(date); + }; - protected isDateSelected(date: D): boolean { - return this.dateService.isSameDaySafe(date, this.selectedDate()); - } + const isDateSelected = (date: D): boolean => { + return dateService.isSameDaySafe(date, selectedDate()); + }; - protected shouldUpdateDate(props: CalendarPickerCellProps, nextProps: CalendarPickerCellProps): boolean { - const dateChanged: boolean = this.dateService.compareDatesSafe(props.date.date, nextProps.date.date) !== 0; + const shouldUpdateDate = (prevProps: CalendarPickerCellProps, nextProps: CalendarPickerCellProps): boolean => { + const dateChanged: boolean = dateService.compareDatesSafe( + prevProps.date.date, + nextProps.date.date, + ) !== 0; if (dateChanged) { return true; } - const selectionChanged: boolean = props.selected !== nextProps.selected; - const disablingChanged: boolean = props.disabled !== nextProps.disabled; + const selectionChanged: boolean = prevProps.selected !== nextProps.selected; + const disablingChanged: boolean = prevProps.disabled !== nextProps.disabled; const value: boolean = selectionChanged || disablingChanged; @@ -175,6 +180,25 @@ export class Calendar extends BaseCalendarComponent, return true; } - return props.eva.theme !== nextProps.eva.theme; - } + return prevProps.eva.theme !== nextProps.eva.theme; + }; + + return ( + + ); } + +const component = styled('Calendar')(React.forwardRef(Calendar)); + +export { + component as Calendar, +}; diff --git a/src/components/ui/calendar/calendar.spec.tsx b/src/components/ui/calendar/calendar.spec.tsx index 5a34f4ef6..16dda81fd 100644 --- a/src/components/ui/calendar/calendar.spec.tsx +++ b/src/components/ui/calendar/calendar.spec.tsx @@ -22,7 +22,7 @@ import { import { ApplicationProvider } from '../../theme'; import { Calendar, - CalendarProps, + CalendarProps, CalendarRef, } from './calendar.component'; import { CalendarViewModes } from './type'; import { MomentDateService } from '@ui-kitten/moment'; @@ -46,7 +46,7 @@ describe('@calendar: component checks', () => { const TestCalendar = React.forwardRef(( props: Partial>, - ref: React.Ref, + ref: React.Ref, ) => { const [date, setDate] = React.useState(props.date); @@ -125,7 +125,7 @@ describe('@calendar: component checks', () => { }); it('should be rendered with view passed to startView prop', () => { - const componentRef = React.createRef(); + const componentRef = React.createRef(); render( { }); it('should change month to next when navigation button pressed', () => { - const componentRef = React.createRef(); + const componentRef = React.createRef(); const component = render( , ); @@ -153,7 +153,7 @@ describe('@calendar: component checks', () => { }); it('should change month to previous when navigation button pressed', () => { - const componentRef = React.createRef(); + const componentRef = React.createRef(); const component = render( , ); @@ -169,7 +169,7 @@ describe('@calendar: component checks', () => { }); it('should change year to next when navigation button pressed', () => { - const componentRef = React.createRef(); + const componentRef = React.createRef(); const component = render( { }); it('should change year to previous when navigation button pressed', () => { - const componentRef = React.createRef(); + const componentRef = React.createRef(); const component = render( { it('should show the selected date on load provided by date prop', () => { const date = new Date(2021, 2, 1); - const componentRef = React.createRef(); + const componentRef = React.createRef(); render( { it('should show the specific date on load provided by initialVisibleDate prop', () => { const initialDate = new Date(2021, 2, 1); - const componentRef = React.createRef(); + const componentRef = React.createRef(); render( { }); it('should scroll to current month when scrollToToday called', () => { - const componentRef = React.createRef(); + const componentRef = React.createRef(); render( { it('should scroll to the specific date when scrollToDate called', () => { const dateToScroll = new Date(2021, 2, 1); - const componentRef = React.createRef(); + const componentRef = React.createRef(); render( extends StyledComponentProps, Base export type RangeCalendarElement = React.ReactElement>; +export type RangeCalendarRef = BaseCalendarRef; + /** * Range Calendar provides a simple way to select a date range. * @@ -109,59 +112,54 @@ export type RangeCalendarElement = React.ReactElement extends BaseCalendarComponent, D> { - - static defaultProps: Partial = { - ...BaseCalendarComponent.defaultProps, - range: {}, +function RangeCalendar ( + { + range = {}, + ...props + }: RangeCalendarProps, + ref: React.RefObject>, +): RangeCalendarElement { + const baseCalendarRef = React.useRef>(null); + const rangeDateService: RangeDateService = new RangeDateService(props.dateService); + + React.useImperativeHandle(ref, () => ({ + ...baseCalendarRef.current, + })); + + const createDates = (date: D): DateBatch => { + if (baseCalendarRef.current) { + return baseCalendarRef.current.dataService.createDayPickerData(date, range); + } + return []; }; - private rangeDateService: RangeDateService = new RangeDateService(this.dateService); - - constructor(props: RangeCalendarProps) { - super(props); - - this.createDates = this.createDates.bind(this); - this.selectedDate = this.selectedDate.bind(this); - this.onDateSelect = this.onDateSelect.bind(this); - this.isDateSelected = this.isDateSelected.bind(this); - this.shouldUpdateDate = this.shouldUpdateDate.bind(this); - } - - // BaseCalendarComponent - - protected createDates(date: D): DateBatch { - return this.dataService.createDayPickerData(date, this.props.range); - } - - protected selectedDate(): D | undefined { - return this.props.range?.startDate; - } + const selectedDate = (): D | undefined => { + return range.startDate; + }; - protected onDateSelect(date: D): void { - if (this.props.onSelect) { - const range: CalendarRange = this.rangeDateService.createRange(this.props.range, date); - this.props.onSelect(range); + const onDateSelect = (date: D): void => { + if (props.onSelect) { + const calendarRange: CalendarRange = rangeDateService.createRange(range, date); + props.onSelect(calendarRange); } - } + }; - protected isDateSelected(): boolean { + const isDateSelected = (): boolean => { return false; - } + }; - protected shouldUpdateDate(props: CalendarPickerCellProps, nextProps: CalendarPickerCellProps): boolean { - const dateChanged: boolean = this.dateService.compareDatesSafe(props.date.date, nextProps.date.date) !== 0; + const shouldUpdateDate = (prevProps: CalendarPickerCellProps, nextProps: CalendarPickerCellProps): boolean => { + const dateChanged: boolean = props.dateService.compareDatesSafe(prevProps.date.date, nextProps.date.date) !== 0; if (dateChanged) { return true; } - const selectionChanged: boolean = props.selected !== nextProps.selected; - const disablingChanged: boolean = props.disabled !== nextProps.disabled; - const rangeChanged: boolean = props.range !== nextProps.range; - const rangeStartPlaceChanged: boolean = props.firstRangeItem !== nextProps.firstRangeItem; - const rangeEndPlaceChanged: boolean = props.lastRangeItem !== nextProps.lastRangeItem; + const selectionChanged: boolean = prevProps.selected !== nextProps.selected; + const disablingChanged: boolean = prevProps.disabled !== nextProps.disabled; + const rangeChanged: boolean = prevProps.range !== nextProps.range; + const rangeStartPlaceChanged: boolean = prevProps.firstRangeItem !== nextProps.firstRangeItem; + const rangeEndPlaceChanged: boolean = prevProps.lastRangeItem !== nextProps.lastRangeItem; const shouldUpdate: boolean = selectionChanged || @@ -174,6 +172,24 @@ export class RangeCalendar extends BaseCalendarComponent + ); } + +const Component = styled('RangeCalendar')(React.forwardRef(RangeCalendar)); + +export { + Component as RangeCalendar, +}; diff --git a/src/components/ui/calendar/rangeCalendar.spec.tsx b/src/components/ui/calendar/rangeCalendar.spec.tsx index ccfe79010..dc554a354 100644 --- a/src/components/ui/calendar/rangeCalendar.spec.tsx +++ b/src/components/ui/calendar/rangeCalendar.spec.tsx @@ -10,7 +10,7 @@ import { import { ApplicationProvider } from '../../theme'; import { RangeCalendar, - RangeCalendarProps, + RangeCalendarProps, RangeCalendarRef, } from './rangeCalendar.component'; import { CalendarRange } from './type'; import { TouchableOpacity } from 'react-native'; @@ -38,7 +38,7 @@ describe('@range-calendar: component checks', () => { const TestRangeCalendar = React.forwardRef(( props: Partial, - ref: React.Ref) => { + ref: React.Ref) => { const [range, setRange] = React.useState>(props.range || {}); @@ -122,7 +122,7 @@ describe('@range-calendar: component checks', () => { it('should show startDate of the selected range on load provided by range prop', () => { const date = new Date(2021, 2, 1); - const componentRef = React.createRef(); + const componentRef = React.createRef(); render( extends StyledComponentProps, TouchableOpacityProps, BaseCalendarProps { controlStyle?: StyleProp; - label?: RenderProp | React.ReactText; - caption?: RenderProp | React.ReactText; + label?: RenderProp | string | number; + caption?: RenderProp | string | number; accessoryLeft?: RenderProp>; accessoryRight?: RenderProp>; status?: EvaStatus; size?: EvaInputSize; - placeholder?: RenderProp | React.ReactText; + placeholder?: RenderProp | string | number; placement?: PopoverPlacement | string; backdropStyle?: StyleProp; onFocus?: () => void; onBlur?: () => void; } -interface State { - visible: boolean; +interface DerivedDatepickerProps extends BaseDatepickerProps { + renderCalendar: () => CalendarElement | RangeCalendarElement; + getComponentTitle: () => RenderProp | string | number; + clear: () => void; } -export abstract class BaseDatepickerComponent extends React.Component & P, State> { - - static defaultProps: Partial = { - dateService: new NativeDateService(), - placeholder: 'dd/mm/yyyy', - placement: PopoverPlacements.BOTTOM_START, - }; - - public state: State = { - visible: false, - }; +export interface BaseDatepickerRef extends BaseCalendarRef { + focus: () => void; + blur: () => void; + isFocused: () => boolean; +} - // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected calendarRef = React.createRef(); +function BaseDatepickerComponent( + props: DerivedDatepickerProps, + ref: React.RefObject>, +): React.ReactElement> { + const { + eva, + style, + testID, + backdropStyle, + label, + caption, + placement = PopoverPlacements.BOTTOM_START, + onFocus, + onBlur, + renderCalendar, + getComponentTitle, + } = props; + const evaStyle = getComponentStyle(eva.style); - public scrollToToday = (): void => { - this.calendarRef.current?.scrollToToday(); - }; + const calendarRef = React.useRef>(null); + const [visible, setVisible] = React.useState(false); - public scrollToDate = (date: Date): void => { - this.calendarRef.current?.scrollToDate(date); - }; + React.useImperativeHandle(ref, () => ({ + ...calendarRef.current, + focus, + blur, + isFocused, + })); - public focus = (): void => { - this.setState({ visible: true }, this.onPickerVisible); + const focus = (): void => { + setVisible(true); + onPickerVisible(); }; - public blur = (): void => { - this.setState({ visible: false }, this.onPickerInvisible); + const blur = (): void => { + setVisible(false); + onPickerInvisible(); }; - public isFocused = (): boolean => { - return this.state.visible; + const isFocused = (): boolean => { + return visible; }; - public abstract clear(): void; - - protected abstract getComponentTitle(): RenderProp | React.ReactText; - - protected abstract renderCalendar(): CalendarElement | RangeCalendarElement; - - private getComponentStyle = (style: StyleType): StyleType => { - const { - textMarginHorizontal, - textFontFamily, - textFontSize, - textFontWeight, - textColor, - placeholderColor, - iconWidth, - iconHeight, - iconMarginHorizontal, - iconTintColor, - labelColor, - labelFontSize, - labelMarginBottom, - labelFontWeight, - labelFontFamily, - captionMarginTop, - captionColor, - captionFontSize, - captionFontWeight, - captionFontFamily, - popoverWidth, - ...controlParameters - } = style; - - return { - control: controlParameters, - text: { - marginHorizontal: textMarginHorizontal, - fontFamily: textFontFamily, - fontSize: textFontSize, - fontWeight: textFontWeight, - color: textColor, - }, - placeholder: { - marginHorizontal: textMarginHorizontal, - color: placeholderColor, - }, - icon: { - width: iconWidth, - height: iconHeight, - marginHorizontal: iconMarginHorizontal, - tintColor: iconTintColor, - }, - label: { - color: labelColor, - fontSize: labelFontSize, - fontFamily: labelFontFamily, - marginBottom: labelMarginBottom, - fontWeight: labelFontWeight, - }, - captionLabel: { - fontSize: captionFontSize, - fontWeight: captionFontWeight, - fontFamily: captionFontFamily, - color: captionColor, - }, - popover: { - width: popoverWidth, - marginBottom: captionMarginTop, - }, - }; + const onPress = (event: GestureResponderEvent): void => { + setPickerVisible(); + props.onPress?.(event); }; - private onPress = (event: GestureResponderEvent): void => { - this.setPickerVisible(); - this.props.onPress?.(event); + const onPressIn = (event: GestureResponderEvent): void => { + eva.dispatch([Interaction.ACTIVE]); + props.onPressIn?.(event); }; - private onPressIn = (event: GestureResponderEvent): void => { - this.props.eva.dispatch([Interaction.ACTIVE]); - this.props.onPressIn?.(event); + const onPressOut = (event: GestureResponderEvent): void => { + eva.dispatch([]); + props.onPressOut?.(event); }; - private onPressOut = (event: GestureResponderEvent): void => { - this.props.eva.dispatch([]); - this.props.onPressOut?.(event); + const onPickerVisible = (): void => { + eva.dispatch([Interaction.ACTIVE]); + onFocus?.(); }; - private onPickerVisible = (): void => { - this.props.eva.dispatch([Interaction.ACTIVE]); - this.props.onFocus?.(); + const onPickerInvisible = (): void => { + eva.dispatch([]); + onBlur?.(); }; - private onPickerInvisible = (): void => { - this.props.eva.dispatch([]); - this.props.onBlur?.(); + const setPickerVisible = (): void => { + setVisible(true); + onPickerVisible(); }; - private setPickerVisible = (): void => { - this.setState({ visible: true }, this.onPickerVisible); + const setPickerInvisible = (): void => { + setVisible(false); + onPickerInvisible(); }; - private setPickerInvisible = (): void => { - this.setState({ visible: false }, this.onPickerInvisible); - }; - - private renderInputElement = (props, evaStyle): React.ReactElement => { + const renderInputElement = (): React.ReactElement => { return ( ); }; - public render(): React.ReactElement { - const { - eva, - style, - testID, - backdropStyle, - controlStyle, - placement, - label, - accessoryLeft, - accessoryRight, - caption, - ...touchableProps - } = this.props; - - const evaStyle = this.getComponentStyle(eva.style); - - return ( - + + - - this.renderInputElement(touchableProps, evaStyle)} - onBackdropPress={this.setPickerInvisible} - > - {this.renderCalendar()} - - - - ); - } + {renderCalendar()} + + + + ); } +const Component = React.forwardRef(BaseDatepickerComponent); + +export { + Component as BaseDatepickerComponent, +}; + const styles = StyleSheet.create({ popover: { borderWidth: 0, diff --git a/src/components/ui/datepicker/baseDatepicker.utils.ts b/src/components/ui/datepicker/baseDatepicker.utils.ts new file mode 100644 index 000000000..e5c2c0f04 --- /dev/null +++ b/src/components/ui/datepicker/baseDatepicker.utils.ts @@ -0,0 +1,68 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { StyleType } from '../../theme'; + +export const getComponentStyle = ({ + textMarginHorizontal, + textFontFamily, + textFontSize, + textFontWeight, + textColor, + placeholderColor, + iconWidth, + iconHeight, + iconMarginHorizontal, + iconTintColor, + labelColor, + labelFontSize, + labelMarginBottom, + labelFontWeight, + labelFontFamily, + captionMarginTop, + captionColor, + captionFontSize, + captionFontWeight, + captionFontFamily, + popoverWidth, + ...controlParameters +}: StyleType): StyleType => ({ + control: controlParameters, + text: { + marginHorizontal: textMarginHorizontal, + fontFamily: textFontFamily, + fontSize: textFontSize, + fontWeight: textFontWeight, + color: textColor, + }, + placeholder: { + marginHorizontal: textMarginHorizontal, + color: placeholderColor, + }, + icon: { + width: iconWidth, + height: iconHeight, + marginHorizontal: iconMarginHorizontal, + tintColor: iconTintColor, + }, + label: { + color: labelColor, + fontSize: labelFontSize, + fontFamily: labelFontFamily, + marginBottom: labelMarginBottom, + fontWeight: labelFontWeight, + }, + captionLabel: { + fontSize: captionFontSize, + fontWeight: captionFontWeight, + fontFamily: captionFontFamily, + color: captionColor, + }, + popover: { + width: popoverWidth, + marginBottom: captionMarginTop, + }, +}); diff --git a/src/components/ui/datepicker/datepicker.component.tsx b/src/components/ui/datepicker/datepicker.component.tsx index 6d8c25799..41b26e00a 100644 --- a/src/components/ui/datepicker/datepicker.component.tsx +++ b/src/components/ui/datepicker/datepicker.component.tsx @@ -4,17 +4,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ +import { DateService, NativeDateService } from '@ui-kitten/components'; import React from 'react'; import { RenderProp } from '../../devsupport'; import { styled } from '../../theme'; import { BaseDatepickerComponent, BaseDatepickerProps, + BaseDatepickerRef, } from './baseDatepicker.component'; import { Calendar, CalendarElement, CalendarProps, + CalendarRef, } from '../calendar/calendar.component'; import { TextProps } from '../text/text.component'; @@ -24,6 +27,10 @@ export interface DatepickerProps extends BaseDatepickerProps, Calen export type DatepickerElement = React.ReactElement>; +export type DatepickerRef = BaseDatepickerRef & { + clear: () => void; +}; + /** * Date picker provides a simple way to select a date within a picker displayed in modal. * @@ -187,69 +194,95 @@ export type DatepickerElement = React.ReactElement> * @overview-example DatepickerTheming * In most cases this is redundant, if [custom theme is configured](guides/branding). */ -@styled('Datepicker') -export class Datepicker extends BaseDatepickerComponent, D> { - - static defaultProps: DatepickerProps = { - ...BaseDatepickerComponent.defaultProps, - autoDismiss: true, - }; +function Datepicker( + { + autoDismiss = true, + placeholder = 'dd/mm/yyyy', + ...props + }: DatepickerProps, + ref: React.RefObject> +): React.ReactElement> { + const calendarRef = React.useRef>(null); + const baseDatepickerRef = React.useRef>(null); + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; - constructor(props: DatepickerProps) { - super(props); - this.clear = this.clear.bind(this); - } + React.useImperativeHandle(ref, () => ({ + focus: baseDatepickerRef.current?.focus, + blur: baseDatepickerRef.current?.blur, + isFocused: baseDatepickerRef.current?.isFocused, + scrollToToday: calendarRef.current?.scrollToToday, + scrollToDate: calendarRef.current?.scrollToDate, + state: calendarRef.current?.state, + dataService: calendarRef.current?.dataService, + clear, + })); - private get calendarProps(): CalendarProps { - return { - min: this.props.min, - max: this.props.max, - date: this.props.date, - initialVisibleDate: this.props.initialVisibleDate, - dateService: this.props.dateService, - boundingMonth: this.props.boundingMonth, - startView: this.props.startView, - filter: this.props.filter, - title: this.props.title, - onSelect: this.props.onSelect, - renderDay: this.props.renderDay, - renderMonth: this.props.renderMonth, - renderYear: this.props.renderYear, - renderFooter: this.props.renderFooter, - renderArrowRight: this.props.renderArrowRight, - renderArrowLeft: this.props.renderArrowLeft, - onVisibleDateChange: this.props.onVisibleDateChange, - }; - } + const calendarProps: CalendarProps = ({ + dateService, + min: props.min, + max: props.max, + date: props.date, + initialVisibleDate: props.initialVisibleDate, + boundingMonth: props.boundingMonth, + startView: props.startView, + filter: props.filter, + title: props.title, + onSelect: props.onSelect, + renderDay: props.renderDay, + renderMonth: props.renderMonth, + renderYear: props.renderYear, + renderFooter: props.renderFooter, + renderArrowRight: props.renderArrowRight, + renderArrowLeft: props.renderArrowLeft, + onVisibleDateChange: props.onVisibleDateChange, + }); - public clear = (): void => { - if (this.props.onSelect) { - this.props.onSelect(null); + const clear = (): void => { + if (props.onSelect) { + props.onSelect(null); } }; // BaseDatepickerComponent - protected getComponentTitle(): RenderProp | React.ReactText { - if (this.props.date) { - return this.props.dateService.format(this.props.date, null); + const getComponentTitle = (): RenderProp | string | number => { + if (props.date) { + return dateService.format(props.date, null); } else { - return this.props.placeholder; + return placeholder; } - } + }; - protected onSelect = (date: D): void => { - this.props.onSelect?.(date); - this.props.autoDismiss && this.blur(); + const onSelect = (date: D): void => { + props.onSelect?.(date); + autoDismiss && baseDatepickerRef.current?.blur?.(); }; - protected renderCalendar(): CalendarElement { + const renderCalendar = (): CalendarElement => { return ( ); - } + }; + + return ( + + ); } + +const Component = styled('Datepicker')(React.forwardRef(Datepicker)); + +export { + Component as Datepicker, +}; diff --git a/src/components/ui/datepicker/datepicker.spec.tsx b/src/components/ui/datepicker/datepicker.spec.tsx index d84535ab2..d30b6776e 100644 --- a/src/components/ui/datepicker/datepicker.spec.tsx +++ b/src/components/ui/datepicker/datepicker.spec.tsx @@ -11,6 +11,7 @@ import { View, } from 'react-native'; import { + act, fireEvent, render, RenderAPI, @@ -24,6 +25,7 @@ import { ApplicationProvider } from '../../theme'; import { Datepicker, DatepickerProps, + DatepickerRef, } from './datepicker.component'; import { Calendar } from '../calendar/calendar.component'; import { CalendarViewModes } from '../calendar/type'; @@ -47,7 +49,10 @@ describe('@datepicker: component checks', () => { jest.clearAllMocks(); }); - const TestDatepicker = React.forwardRef((props: Partial, ref: React.Ref) => { + const TestDatepicker = React.forwardRef(( + props: Partial, + ref: React.Ref, + ) => { const [date, setDate] = React.useState(props.date); const onSelect = (nextDate: Date): void => { @@ -379,7 +384,7 @@ describe('@datepicker: component checks', () => { }); it('should show calendar by calling `focus` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const component = render( , @@ -392,7 +397,7 @@ describe('@datepicker: component checks', () => { }); it('should hide calendar by calling `blur` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const component = render( , @@ -408,7 +413,7 @@ describe('@datepicker: component checks', () => { }); it('should return false if calendar not visible by calling `isFocused` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( , @@ -418,7 +423,7 @@ describe('@datepicker: component checks', () => { }); it('should return true if calendar visible by calling `isFocused` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( , @@ -431,7 +436,7 @@ describe('@datepicker: component checks', () => { }); it('should call onSelect with null when calling `clear` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const onSelect = jest.fn(); render( @@ -479,7 +484,7 @@ describe('@datepicker: component checks', () => { it('should show the selected date on load provided by date prop', () => { const date = new Date(2021, 2, 1); - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( { componentRef.current.focus(); - // @ts-ignore: private calendarRef - const calendarState = componentRef.current.calendarRef.current.state; + const calendarState = componentRef.current.state; expect(calendarState.visibleDate.getFullYear()).toEqual(date.getFullYear()); expect(calendarState.visibleDate.getMonth()).toEqual(date.getMonth()); }); it('should show the specific date on load provided by initialVisibleDate prop', () => { const initialDate = new Date(2021, 2, 1); - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( { componentRef.current.focus(); // @ts-ignore: private calendarRef - const visibleDate = componentRef.current.calendarRef.current.state.visibleDate; + const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(initialDate.getFullYear()); expect(visibleDate.getMonth()).toEqual(initialDate.getMonth()); }); - it('should scroll to current month when scrollToToday called', () => { - const componentRef: React.RefObject = React.createRef(); + it('should scroll to current month when scrollToToday called', async () => { + const componentRef: React.RefObject = React.createRef(); render( { componentRef.current.focus(); componentRef.current.scrollToToday(); - // @ts-ignore: private calendarRef - const visibleDate = componentRef.current.calendarRef.current.state.visibleDate; + const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(today.getFullYear()); expect(visibleDate.getMonth()).toEqual(today.getMonth()); }); - it('should scroll to the specific date when scrollToDate called', () => { + it('should scroll to the specific date when scrollToDate called', async () => { const dateToScroll = new Date(2021, 2, 1); - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( { />, ); - componentRef.current.focus(); - componentRef.current.scrollToDate(dateToScroll); + await act(() => { + componentRef.current.focus(); + componentRef.current.scrollToDate(dateToScroll); + }); - // @ts-ignore: private calendarRef - const visibleDate = componentRef.current.calendarRef.current.state.visibleDate; + const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(dateToScroll.getFullYear()); expect(visibleDate.getMonth()).toEqual(dateToScroll.getMonth()); }); - it('should render custom left arrow', () => { - const componentRef: React.RefObject = React.createRef(); + it('should render custom left arrow', async () => { + const componentRef: React.RefObject = React.createRef(); const onVisibleDateChange = jest.fn(); @@ -581,7 +585,9 @@ describe('@datepicker: component checks', () => { /> ); - componentRef.current?.focus(); + await act(() => { + componentRef.current.focus(); + }); const leftArrow = component.queryByTestId('@arrow/left'); fireEvent.press(leftArrow); @@ -589,33 +595,33 @@ describe('@datepicker: component checks', () => { expect(onVisibleDateChange).toBeCalled(); }); - it('should render custom right arrow', () => { - const componentRef: React.RefObject = React.createRef(); + it('should render custom right arrow', async () => { + const componentRef: React.RefObject = React.createRef(); const onVisibleDateChange = jest.fn(); - const renderArrow = (props: { onPress: () => void }): React.ReactElement => { - return ( - - - RIGHT - - - ); - }; - const component = render( void }): React.ReactElement => { + return ( + + + RIGHT + + + ); + }} onVisibleDateChange={onVisibleDateChange} /> ); - componentRef.current?.focus(); + await act(() => { + componentRef.current.focus(); + }); const leftArrow = component.queryByTestId('@arrow/right'); fireEvent.press(leftArrow); diff --git a/src/components/ui/datepicker/rangeDatepicker.component.tsx b/src/components/ui/datepicker/rangeDatepicker.component.tsx index 7699870f4..881f4a91b 100644 --- a/src/components/ui/datepicker/rangeDatepicker.component.tsx +++ b/src/components/ui/datepicker/rangeDatepicker.component.tsx @@ -9,11 +9,13 @@ import { styled } from '../../theme'; import { BaseDatepickerComponent, BaseDatepickerProps, + BaseDatepickerRef, } from './baseDatepicker.component'; import { RangeCalendar, RangeCalendarElement, RangeCalendarProps, + RangeCalendarRef, } from '../calendar/rangeCalendar.component'; import { RenderProp } from '@ui-kitten/components/devsupport'; import { TextProps } from '@ui-kitten/components'; @@ -21,6 +23,10 @@ import { TextProps } from '@ui-kitten/components'; export type RangeDatepickerProps = BaseDatepickerProps & RangeCalendarProps; export type RangeDatepickerElement = React.ReactElement>; +export type RangeDatepickerRef = RangeCalendarRef & BaseDatepickerRef & { + clear: () => void; +}; + /** * Range date picker provides a simple way to select a date range within a picker displayed in modal. * @@ -137,63 +143,86 @@ export type RangeDatepickerElement = React.ReactElement extends BaseDatepickerComponent, D> { - - static styledComponentName = 'Datepicker'; +function RangeDatepicker ( + { + placeholder = 'dd/mm/yyyy', + ...props + }: RangeDatepickerProps, + ref: React.RefObject>, +): RangeCalendarElement { + const calendarRef = React.useRef>(null); + const baseDatepickerRef = React.useRef>(null); - constructor(props: RangeDatepickerProps) { - super(props); - this.clear = this.clear.bind(this); - } + React.useImperativeHandle(ref, () => ({ + ...calendarRef.current, + ...baseDatepickerRef.current, + clear, + })); - private get calendarProps(): RangeCalendarProps { + const calendarProps = (): RangeCalendarProps => { return { - min: this.props.min, - max: this.props.max, - range: this.props.range, - initialVisibleDate: this.props.initialVisibleDate, - dateService: this.props.dateService, - boundingMonth: this.props.boundingMonth, - startView: this.props.startView, - filter: this.props.filter, - title: this.props.title, - onSelect: this.props.onSelect, - renderDay: this.props.renderDay, - renderMonth: this.props.renderMonth, - renderYear: this.props.renderYear, - renderFooter: this.props.renderFooter, - renderArrowRight: this.props.renderArrowRight, - renderArrowLeft: this.props.renderArrowLeft, - onVisibleDateChange: this.props.onVisibleDateChange, + min: props.min, + max: props.max, + range: props.range, + initialVisibleDate: props.initialVisibleDate, + dateService: props.dateService, + boundingMonth: props.boundingMonth, + startView: props.startView, + filter: props.filter, + title: props.title, + onSelect: props.onSelect, + renderDay: props.renderDay, + renderMonth: props.renderMonth, + renderYear: props.renderYear, + renderFooter: props.renderFooter, + renderArrowRight: props.renderArrowRight, + renderArrowLeft: props.renderArrowLeft, + onVisibleDateChange: props.onVisibleDateChange, }; - } + }; - public clear = (): void => { - this.props.onSelect?.({}); + const clear = (): void => { + props.onSelect?.({}); }; // BaseDatepickerComponent - protected getComponentTitle(): RenderProp | React.ReactText { - const { startDate, endDate } = this.props.range; + const getComponentTitle = (): RenderProp | string | number => { + const { startDate, endDate } = props.range; if (startDate || endDate) { - const start: string = startDate ? this.props.dateService.format(startDate, null) : ''; - const end: string = endDate ? this.props.dateService.format(endDate, null) : ''; + const start: string = startDate ? props.dateService.format(startDate, null) : ''; + const end: string = endDate ? props.dateService.format(endDate, null) : ''; return `${start} - ${end}`; } else { - return this.props.placeholder; + return placeholder; } - } + }; - protected renderCalendar(): RangeCalendarElement { + const renderCalendar = (): RangeCalendarElement => { return ( ); - } + }; + + return ( + + ); } + +const Component = styled('RangeDatepicker')(React.forwardRef(RangeDatepicker)); + +export { + Component as RangeDatepicker, +}; diff --git a/src/components/ui/datepicker/rangeDatepicker.spec.tsx b/src/components/ui/datepicker/rangeDatepicker.spec.tsx index dbefd4f07..40e6c5b72 100644 --- a/src/components/ui/datepicker/rangeDatepicker.spec.tsx +++ b/src/components/ui/datepicker/rangeDatepicker.spec.tsx @@ -24,6 +24,7 @@ import { ApplicationProvider } from '../../theme'; import { RangeDatepicker, RangeDatepickerProps, + RangeDatepickerRef, } from './rangeDatepicker.component'; import { RangeCalendar } from '../calendar/rangeCalendar.component'; import { @@ -51,7 +52,7 @@ describe('@range-datepicker: component checks', () => { }); const TestRangeDatepicker = React.forwardRef((props: Partial, - ref: React.Ref) => { + ref: React.Ref) => { const [range, setRange] = React.useState(props.range || {}); const onSelect = (nextRange: CalendarRange): void => { @@ -372,7 +373,7 @@ describe('@range-datepicker: component checks', () => { }); it('should show calendar by calling `focus` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const component = render( , ); @@ -384,7 +385,7 @@ describe('@range-datepicker: component checks', () => { }); it('should hide calendar by calling `blur` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const component = render( , ); @@ -399,7 +400,7 @@ describe('@range-datepicker: component checks', () => { }); it('should return false if calendar not visible by calling `isFocused` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( , ); @@ -408,7 +409,7 @@ describe('@range-datepicker: component checks', () => { }); it('should return true if calendar visible by calling `isFocused` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( , ); @@ -420,7 +421,7 @@ describe('@range-datepicker: component checks', () => { }); it('should call onSelect with empty object when calling `clear` with ref', async () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const onSelect = jest.fn(); render( @@ -468,7 +469,7 @@ describe('@range-datepicker: component checks', () => { it('should show startDate of the selected range on load provided by range prop', () => { const date = new Date(2021, 2, 1); - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( { it('should show the specific date on load provided by initialVisibleDate prop', () => { const initialDate = new Date(2021, 2, 1); - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( { }); it('should scroll to current month when scrollToToday called', () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( { it('should scroll to the specific date when scrollToDate called', () => { const dateToScroll = new Date(2020, 1, 1); - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); render( { }); it('should render custom left arrow', () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const onVisibleDateChange = jest.fn(); @@ -581,7 +582,7 @@ describe('@range-datepicker: component checks', () => { }); it('should render custom right arrow', () => { - const componentRef: React.RefObject = React.createRef(); + const componentRef: React.RefObject = React.createRef(); const onVisibleDateChange = jest.fn(); diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index a95daaedb..f856e3b0c 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -38,6 +38,7 @@ export { Calendar, CalendarElement, CalendarProps, + CalendarRef, } from './calendar/calendar.component'; export { Card, @@ -48,6 +49,7 @@ export { RangeCalendar, RangeCalendarProps, RangeCalendarElement, + RangeCalendarRef, } from './calendar/rangeCalendar.component'; export { CalendarRange, @@ -64,11 +66,13 @@ export { Datepicker, DatepickerProps, DatepickerElement, + DatepickerRef, } from './datepicker/datepicker.component'; export { RangeDatepicker, RangeDatepickerProps, RangeDatepickerElement, + RangeDatepickerRef, } from './datepicker/rangeDatepicker.component'; export { Drawer, diff --git a/src/showcases/components/calendar/calendarInitialVisibleDate.component.tsx b/src/showcases/components/calendar/calendarInitialVisibleDate.component.tsx index 264c3aae5..e2e228a19 100644 --- a/src/showcases/components/calendar/calendarInitialVisibleDate.component.tsx +++ b/src/showcases/components/calendar/calendarInitialVisibleDate.component.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { Button, Calendar, Layout, Text } from '@ui-kitten/components'; +import { Button, Calendar, CalendarRef, Layout, Text } from '@ui-kitten/components'; const now = new Date(); const date = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate()); @@ -9,7 +9,7 @@ const initialVisibleDate = new Date(now.getFullYear(), now.getMonth() + 3, now.g export const CalendarInitialVisibleDateShowcase = (): React.ReactElement => { const [selectedDate, setSelectedDate] = React.useState(date); - const componentRef = React.createRef(); + const componentRef = React.useRef(); const scrollToSelected = (): void => { if (componentRef.current) { diff --git a/src/showcases/components/datepicker/datepickerInitialVisibleDate.component.tsx b/src/showcases/components/datepicker/datepickerInitialVisibleDate.component.tsx index 627570d5e..dc915ce6b 100644 --- a/src/showcases/components/datepicker/datepickerInitialVisibleDate.component.tsx +++ b/src/showcases/components/datepicker/datepickerInitialVisibleDate.component.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { Button, Datepicker, Layout, Text } from '@ui-kitten/components'; +import { Button, Datepicker, DatepickerRef, Layout, Text } from '@ui-kitten/components'; const now = new Date(); const date = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate()); @@ -10,7 +10,7 @@ export const DatepickerInitialVisibleDateShowcase = (): React.ReactElement => { const [selectedDate, setSelectedDate] = React.useState(date); const [initialVisibleDate, setInitialVisibleDate] = React.useState(initialDate); - const componentRef = React.createRef(); + const componentRef = React.useRef(); const scrollToSelected = (): void => { if (componentRef.current) { From 3e1eb7734f2b761bb2de626be7cad83a7dcc5790 Mon Sep 17 00:00:00 2001 From: Anastasia Yaskevich Date: Mon, 1 Jul 2024 13:59:37 +0200 Subject: [PATCH 2/4] chore: Calendar tests fix --- .../ui/calendar/baseCalendar.component.tsx | 31 ++++++---------- .../ui/calendar/calendar.component.tsx | 23 ++++++------ .../ui/calendar/rangeCalendar.component.tsx | 25 ++++++------- .../datepicker/baseDatepicker.component.tsx | 27 ++++++-------- .../ui/datepicker/datepicker.component.tsx | 26 +++++-------- .../ui/datepicker/datepicker.spec.tsx | 1 - .../datepicker/rangeDatepicker.component.tsx | 37 ++++++++----------- .../ui/datepicker/rangeDatepicker.spec.tsx | 23 ++++++------ 8 files changed, 83 insertions(+), 110 deletions(-) diff --git a/src/components/ui/calendar/baseCalendar.component.tsx b/src/components/ui/calendar/baseCalendar.component.tsx index bc334b4bf..a23d4df83 100644 --- a/src/components/ui/calendar/baseCalendar.component.tsx +++ b/src/components/ui/calendar/baseCalendar.component.tsx @@ -54,6 +54,7 @@ export interface BaseCalendarProps extends ViewProps { max?: D; initialVisibleDate?: D; dateService?: DateService; + dataService?: CalendarDataService; boundingMonth?: boolean; startView?: CalendarViewMode; title?: (datePickerDate: D, monthYearPickerDate: D, viewMode: CalendarViewMode) => string; @@ -77,7 +78,6 @@ const VIEWS_IN_PICKER: number = PICKER_ROWS * PICKER_COLUMNS; export interface BaseCalendarRef { scrollToToday: () => void; scrollToDate: (date: D) => void; - dataService: CalendarDataService; state: State; } @@ -90,21 +90,15 @@ interface State { function BaseCalendarComponent( { dateService, + dataService, boundingMonth = true, startView = CalendarViewModes.DATE, ...props }: DerivedCalendarProps, ref: React.RefObject> ): BaseCalendarElement { - if (!dateService) { - throw Error('No dateService'); - } - const dataService: CalendarDataService = new CalendarDataService(dateService); const [viewMode, setViewMode] = React.useState(startView); - - console.log('base current ref', ref.current); - const initialVisibleDate = (): D => { return props.initialVisibleDate || props.selectedDate() || dateService.today(); }; @@ -131,17 +125,6 @@ function BaseCalendarComponent( return props.max || dateService.getYearEnd(dateService.today()); }; - React.useImperativeHandle(ref, () => ({ - scrollToToday, - scrollToDate, - dataService, - state: { - viewMode, - visibleDate, - pickerDate, - }, - })); - const scrollToToday = (): void => { setViewMode(CalendarViewModes.DATE); setVisibleDate(dateService.today()); @@ -156,6 +139,16 @@ function BaseCalendarComponent( } }; + React.useImperativeHandle(ref, () => ({ + scrollToToday, + scrollToDate, + state: { + pickerDate, + viewMode, + visibleDate, + }, + })); + const getCalendarStyle = (source: StyleType): StyleType => { return { container: { diff --git a/src/components/ui/calendar/calendar.component.tsx b/src/components/ui/calendar/calendar.component.tsx index eb565873b..f27fc75e4 100644 --- a/src/components/ui/calendar/calendar.component.tsx +++ b/src/components/ui/calendar/calendar.component.tsx @@ -16,7 +16,7 @@ import { BaseCalendarRef, } from './baseCalendar.component'; import { CalendarPickerCellProps } from './components/picker/calendarPickerCell.component'; -import { DateBatch } from './service/calendarData.service'; +import { CalendarDataService, DateBatch } from './service/calendarData.service'; export interface CalendarProps extends StyledComponentProps, BaseCalendarProps { date?: D; @@ -25,7 +25,9 @@ export interface CalendarProps extends StyledComponentProps, BaseCalen export type CalendarElement = React.ReactElement>; -export type CalendarRef = BaseCalendarRef; +export type CalendarRef = BaseCalendarRef & { + dataService: CalendarDataService; +}; /** * Calendar provides a simple way to select a date. @@ -133,20 +135,18 @@ export type CalendarRef = BaseCalendarRef; */ function Calendar ( props: CalendarProps, - ref: React.RefObject>, + ref: React.RefObject>, ): CalendarElement { - const baseCalendarRef = React.useRef>(null); const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; + const dataService: CalendarDataService = new CalendarDataService(dateService); React.useImperativeHandle(ref, () => ({ - ...baseCalendarRef.current, - })); + ...ref.current, + dataService, + }), [dataService]); const createDates = (date: D): DateBatch => { - if (baseCalendarRef.current) { - return baseCalendarRef.current.dataService.createDayPickerData(date); - } - return []; + return dataService.createDayPickerData(date); }; const selectedDate = (): D | undefined => { @@ -187,7 +187,8 @@ function Calendar ( ( }: RangeCalendarProps, ref: React.RefObject>, ): RangeCalendarElement { - const baseCalendarRef = React.useRef>(null); - const rangeDateService: RangeDateService = new RangeDateService(props.dateService); - - React.useImperativeHandle(ref, () => ({ - ...baseCalendarRef.current, - })); + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; + const rangeDateService: RangeDateService = new RangeDateService(dateService); + const dataService: CalendarDataService = new CalendarDataService(dateService); const createDates = (date: D): DateBatch => { - if (baseCalendarRef.current) { - return baseCalendarRef.current.dataService.createDayPickerData(date, range); - } - return []; + return dataService.createDayPickerData(date, range); }; const selectedDate = (): D | undefined => { @@ -149,7 +144,7 @@ function RangeCalendar ( }; const shouldUpdateDate = (prevProps: CalendarPickerCellProps, nextProps: CalendarPickerCellProps): boolean => { - const dateChanged: boolean = props.dateService.compareDatesSafe(prevProps.date.date, nextProps.date.date) !== 0; + const dateChanged: boolean = dateService.compareDatesSafe(prevProps.date.date, nextProps.date.date) !== 0; if (dateChanged) { return true; @@ -178,7 +173,9 @@ function RangeCalendar ( return ( ( ); } -const Component = styled('RangeCalendar')(React.forwardRef(RangeCalendar)); +const Component = styled('Calendar')(React.forwardRef(RangeCalendar)); export { Component as RangeCalendar, diff --git a/src/components/ui/datepicker/baseDatepicker.component.tsx b/src/components/ui/datepicker/baseDatepicker.component.tsx index 065e22b9f..ba596de07 100644 --- a/src/components/ui/datepicker/baseDatepicker.component.tsx +++ b/src/components/ui/datepicker/baseDatepicker.component.tsx @@ -26,7 +26,7 @@ import { Interaction, StyledComponentProps, } from '../../theme'; -import { BaseCalendarProps, BaseCalendarRef } from '../calendar/baseCalendar.component'; +import { BaseCalendarProps } from '../calendar/baseCalendar.component'; import { CalendarElement } from '../calendar/calendar.component'; import { RangeCalendarElement } from '../calendar/rangeCalendar.component'; import { Popover } from '../popover/popover.component'; @@ -40,7 +40,6 @@ import { getComponentStyle } from './baseDatepicker.utils'; export interface BaseDatepickerProps extends StyledComponentProps, TouchableOpacityProps, BaseCalendarProps { - controlStyle?: StyleProp; label?: RenderProp | string | number; caption?: RenderProp | string | number; @@ -56,12 +55,12 @@ export interface BaseDatepickerProps extends StyledComponentProps, } interface DerivedDatepickerProps extends BaseDatepickerProps { - renderCalendar: () => CalendarElement | RangeCalendarElement; + children: CalendarElement | RangeCalendarElement; getComponentTitle: () => RenderProp | string | number; clear: () => void; } -export interface BaseDatepickerRef extends BaseCalendarRef { +export interface BaseDatepickerRef { focus: () => void; blur: () => void; isFocused: () => boolean; @@ -69,7 +68,7 @@ export interface BaseDatepickerRef extends BaseCalendarRef { function BaseDatepickerComponent( props: DerivedDatepickerProps, - ref: React.RefObject>, + ref: React.RefObject, ): React.ReactElement> { const { eva, @@ -81,21 +80,13 @@ function BaseDatepickerComponent( placement = PopoverPlacements.BOTTOM_START, onFocus, onBlur, - renderCalendar, getComponentTitle, + children, } = props; const evaStyle = getComponentStyle(eva.style); - const calendarRef = React.useRef>(null); const [visible, setVisible] = React.useState(false); - React.useImperativeHandle(ref, () => ({ - ...calendarRef.current, - focus, - blur, - isFocused, - })); - const focus = (): void => { setVisible(true); onPickerVisible(); @@ -110,6 +101,12 @@ function BaseDatepickerComponent( return visible; }; + React.useImperativeHandle(ref, () => ({ + focus, + blur, + isFocused, + }), [focus, blur, isFocused]); + const onPress = (event: GestureResponderEvent): void => { setPickerVisible(); props.onPress?.(event); @@ -189,7 +186,7 @@ function BaseDatepickerComponent( anchor={renderInputElement} onBackdropPress={setPickerInvisible} > - {renderCalendar()} + {children} extends BaseDatepickerProps, Calen export type DatepickerElement = React.ReactElement>; -export type DatepickerRef = BaseDatepickerRef & { +export type DatepickerRef = CalendarRef & BaseDatepickerRef & { clear: () => void; }; @@ -201,9 +200,9 @@ function Datepicker( ...props }: DatepickerProps, ref: React.RefObject> -): React.ReactElement> { +): DatepickerElement { const calendarRef = React.useRef>(null); - const baseDatepickerRef = React.useRef>(null); + const baseDatepickerRef = React.useRef(null); const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; React.useImperativeHandle(ref, () => ({ @@ -258,26 +257,21 @@ function Datepicker( autoDismiss && baseDatepickerRef.current?.blur?.(); }; - const renderCalendar = (): CalendarElement => { - return ( - - ); - }; - return ( + > + + ); } diff --git a/src/components/ui/datepicker/datepicker.spec.tsx b/src/components/ui/datepicker/datepicker.spec.tsx index d30b6776e..ab1a07f6c 100644 --- a/src/components/ui/datepicker/datepicker.spec.tsx +++ b/src/components/ui/datepicker/datepicker.spec.tsx @@ -514,7 +514,6 @@ describe('@datepicker: component checks', () => { componentRef.current.focus(); - // @ts-ignore: private calendarRef const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(initialDate.getFullYear()); expect(visibleDate.getMonth()).toEqual(initialDate.getMonth()); diff --git a/src/components/ui/datepicker/rangeDatepicker.component.tsx b/src/components/ui/datepicker/rangeDatepicker.component.tsx index 881f4a91b..1059af392 100644 --- a/src/components/ui/datepicker/rangeDatepicker.component.tsx +++ b/src/components/ui/datepicker/rangeDatepicker.component.tsx @@ -18,12 +18,12 @@ import { RangeCalendarRef, } from '../calendar/rangeCalendar.component'; import { RenderProp } from '@ui-kitten/components/devsupport'; -import { TextProps } from '@ui-kitten/components'; +import { DateService, NativeDateService, TextProps } from '@ui-kitten/components'; export type RangeDatepickerProps = BaseDatepickerProps & RangeCalendarProps; export type RangeDatepickerElement = React.ReactElement>; -export type RangeDatepickerRef = RangeCalendarRef & BaseDatepickerRef & { +export type RangeDatepickerRef = RangeCalendarRef & BaseDatepickerRef & { clear: () => void; }; @@ -150,22 +150,20 @@ function RangeDatepicker ( }: RangeDatepickerProps, ref: React.RefObject>, ): RangeCalendarElement { - const calendarRef = React.useRef>(null); - const baseDatepickerRef = React.useRef>(null); + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; React.useImperativeHandle(ref, () => ({ - ...calendarRef.current, - ...baseDatepickerRef.current, + ...ref.current, clear, })); const calendarProps = (): RangeCalendarProps => { return { + dateService, min: props.min, max: props.max, range: props.range, initialVisibleDate: props.initialVisibleDate, - dateService: props.dateService, boundingMonth: props.boundingMonth, startView: props.startView, filter: props.filter, @@ -191,8 +189,8 @@ function RangeDatepicker ( const { startDate, endDate } = props.range; if (startDate || endDate) { - const start: string = startDate ? props.dateService.format(startDate, null) : ''; - const end: string = endDate ? props.dateService.format(endDate, null) : ''; + const start: string = startDate ? dateService.format(startDate, null) : ''; + const end: string = endDate ? dateService.format(endDate, null) : ''; return `${start} - ${end}`; } else { @@ -200,28 +198,23 @@ function RangeDatepicker ( } }; - const renderCalendar = (): RangeCalendarElement => { - return ( - - ); - }; - return ( + > + + ); } -const Component = styled('RangeDatepicker')(React.forwardRef(RangeDatepicker)); +const Component = styled('Datepicker')(React.forwardRef(RangeDatepicker)); export { Component as RangeDatepicker, diff --git a/src/components/ui/datepicker/rangeDatepicker.spec.tsx b/src/components/ui/datepicker/rangeDatepicker.spec.tsx index 40e6c5b72..1886d083b 100644 --- a/src/components/ui/datepicker/rangeDatepicker.spec.tsx +++ b/src/components/ui/datepicker/rangeDatepicker.spec.tsx @@ -51,8 +51,10 @@ describe('@range-datepicker: component checks', () => { jest.clearAllMocks(); }); - const TestRangeDatepicker = React.forwardRef((props: Partial, - ref: React.Ref) => { + const TestRangeDatepicker = React.forwardRef(( + props: Partial, + ref: React.Ref, + ) => { const [range, setRange] = React.useState(props.range || {}); const onSelect = (nextRange: CalendarRange): void => { @@ -128,7 +130,7 @@ describe('@range-datepicker: component checks', () => { expect(component.queryByText('I love Babel')).toBeTruthy(); }); - it('should render label as pure JSX component', async () => { + it('should render label as pure JSX component', () => { const component = render( @@ -483,8 +485,7 @@ describe('@range-datepicker: component checks', () => { componentRef.current.focus(); - // @ts-ignore: private calendarRef - const calendarState = componentRef.current.calendarRef.current.state; + const calendarState = componentRef.current.state; expect(calendarState.visibleDate.getFullYear()).toEqual(date.getFullYear()); expect(calendarState.visibleDate.getMonth()).toEqual(date.getMonth()); }); @@ -502,8 +503,7 @@ describe('@range-datepicker: component checks', () => { componentRef.current.focus(); - // @ts-ignore: private calendarRef - const visibleDate = componentRef.current.calendarRef.current.state.visibleDate; + const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(initialDate.getFullYear()); expect(visibleDate.getMonth()).toEqual(initialDate.getMonth()); }); @@ -522,7 +522,7 @@ describe('@range-datepicker: component checks', () => { componentRef.current.scrollToToday(); // @ts-ignore: private calendarRef - const visibleDate = componentRef.current.calendarRef.current.state.visibleDate; + const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(today.getFullYear()); expect(visibleDate.getMonth()).toEqual(today.getMonth()); }); @@ -541,8 +541,7 @@ describe('@range-datepicker: component checks', () => { componentRef.current.focus(); componentRef.current.scrollToDate(dateToScroll); - // @ts-ignore: private calendarRef - const visibleDate = componentRef.current.calendarRef.current.state.visibleDate; + const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(dateToScroll.getFullYear()); expect(visibleDate.getMonth()).toEqual(dateToScroll.getMonth()); }); @@ -573,7 +572,7 @@ describe('@range-datepicker: component checks', () => { /> ); - componentRef.current?.focus(); + componentRef.current.focus(); const leftArrow = component.queryByTestId('@arrow/left'); fireEvent.press(leftArrow); @@ -607,7 +606,7 @@ describe('@range-datepicker: component checks', () => { /> ); - componentRef.current?.focus(); + componentRef.current.focus(); const leftArrow = component.queryByTestId('@arrow/right'); fireEvent.press(leftArrow); From 5f8d4a63408068bf80e4919f8de0c716f3986a5c Mon Sep 17 00:00:00 2001 From: Anastasia Yaskevich Date: Mon, 1 Jul 2024 16:43:01 +0200 Subject: [PATCH 3/4] chore: Datepicker tests fix --- .../ui/calendar/baseCalendar.component.tsx | 1 + .../ui/datepicker/baseDatepicker.component.tsx | 5 ++++- .../ui/datepicker/datepicker.component.tsx | 18 ++++++------------ .../ui/datepicker/datepicker.spec.tsx | 8 +++----- .../datepicker/rangeDatepicker.component.tsx | 2 ++ 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/components/ui/calendar/baseCalendar.component.tsx b/src/components/ui/calendar/baseCalendar.component.tsx index a23d4df83..eeae4d5d3 100644 --- a/src/components/ui/calendar/baseCalendar.component.tsx +++ b/src/components/ui/calendar/baseCalendar.component.tsx @@ -140,6 +140,7 @@ function BaseCalendarComponent( }; React.useImperativeHandle(ref, () => ({ + ...ref.current, scrollToToday, scrollToDate, state: { diff --git a/src/components/ui/datepicker/baseDatepicker.component.tsx b/src/components/ui/datepicker/baseDatepicker.component.tsx index ba596de07..b5e12f3bb 100644 --- a/src/components/ui/datepicker/baseDatepicker.component.tsx +++ b/src/components/ui/datepicker/baseDatepicker.component.tsx @@ -102,10 +102,11 @@ function BaseDatepickerComponent( }; React.useImperativeHandle(ref, () => ({ + ...ref.current, focus, blur, isFocused, - }), [focus, blur, isFocused]); + })); const onPress = (event: GestureResponderEvent): void => { setPickerVisible(); @@ -196,6 +197,8 @@ function BaseDatepickerComponent( ); } +BaseDatepickerComponent.displayName = 'BaseDatepickerComponent'; + const Component = React.forwardRef(BaseDatepickerComponent); export { diff --git a/src/components/ui/datepicker/datepicker.component.tsx b/src/components/ui/datepicker/datepicker.component.tsx index 7370a8ef0..2e8d128d9 100644 --- a/src/components/ui/datepicker/datepicker.component.tsx +++ b/src/components/ui/datepicker/datepicker.component.tsx @@ -199,20 +199,14 @@ function Datepicker( placeholder = 'dd/mm/yyyy', ...props }: DatepickerProps, - ref: React.RefObject> + ref: React.MutableRefObject> ): DatepickerElement { - const calendarRef = React.useRef>(null); - const baseDatepickerRef = React.useRef(null); + const baseDatepickerRef = React.useRef(); const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; React.useImperativeHandle(ref, () => ({ - focus: baseDatepickerRef.current?.focus, - blur: baseDatepickerRef.current?.blur, - isFocused: baseDatepickerRef.current?.isFocused, - scrollToToday: calendarRef.current?.scrollToToday, - scrollToDate: calendarRef.current?.scrollToDate, - state: calendarRef.current?.state, - dataService: calendarRef.current?.dataService, + ...ref.current, + ...baseDatepickerRef.current, clear, })); @@ -254,7 +248,7 @@ function Datepicker( const onSelect = (date: D): void => { props.onSelect?.(date); - autoDismiss && baseDatepickerRef.current?.blur?.(); + autoDismiss && baseDatepickerRef?.current?.blur?.(); }; return ( @@ -267,8 +261,8 @@ function Datepicker( clear={clear} > diff --git a/src/components/ui/datepicker/datepicker.spec.tsx b/src/components/ui/datepicker/datepicker.spec.tsx index ab1a07f6c..19239d386 100644 --- a/src/components/ui/datepicker/datepicker.spec.tsx +++ b/src/components/ui/datepicker/datepicker.spec.tsx @@ -537,7 +537,7 @@ describe('@datepicker: component checks', () => { expect(visibleDate.getMonth()).toEqual(today.getMonth()); }); - it('should scroll to the specific date when scrollToDate called', async () => { + it('should scroll to the specific date when scrollToDate called', () => { const dateToScroll = new Date(2021, 2, 1); const componentRef: React.RefObject = React.createRef(); @@ -548,10 +548,8 @@ describe('@datepicker: component checks', () => { />, ); - await act(() => { - componentRef.current.focus(); - componentRef.current.scrollToDate(dateToScroll); - }); + componentRef.current.focus(); + componentRef.current.scrollToDate(dateToScroll); const visibleDate = componentRef.current.state.visibleDate; expect(visibleDate.getFullYear()).toEqual(dateToScroll.getFullYear()); diff --git a/src/components/ui/datepicker/rangeDatepicker.component.tsx b/src/components/ui/datepicker/rangeDatepicker.component.tsx index 1059af392..62a6db8d8 100644 --- a/src/components/ui/datepicker/rangeDatepicker.component.tsx +++ b/src/components/ui/datepicker/rangeDatepicker.component.tsx @@ -150,10 +150,12 @@ function RangeDatepicker ( }: RangeDatepickerProps, ref: React.RefObject>, ): RangeCalendarElement { + const baseDatepickerRef = React.useRef(); const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; React.useImperativeHandle(ref, () => ({ ...ref.current, + ...baseDatepickerRef.current, clear, })); From 3ef6d82eaabb4cf0bb9e4e6f199311818fb2826f Mon Sep 17 00:00:00 2001 From: Anastasia Yaskevich Date: Tue, 2 Jul 2024 13:16:26 +0200 Subject: [PATCH 4/4] chore: RangeDatepicker tests fix --- .../ui/calendar/rangeCalendar.component.tsx | 7 ++- .../datepicker/rangeDatepicker.component.tsx | 48 ++++++++----------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/components/ui/calendar/rangeCalendar.component.tsx b/src/components/ui/calendar/rangeCalendar.component.tsx index 08a34a709..98f106821 100644 --- a/src/components/ui/calendar/rangeCalendar.component.tsx +++ b/src/components/ui/calendar/rangeCalendar.component.tsx @@ -124,6 +124,11 @@ function RangeCalendar ( const rangeDateService: RangeDateService = new RangeDateService(dateService); const dataService: CalendarDataService = new CalendarDataService(dateService); + React.useImperativeHandle(ref, () => ({ + ...ref.current, + dataService, + }), [dataService]); + const createDates = (date: D): DateBatch => { return dataService.createDayPickerData(date, range); }; @@ -173,9 +178,9 @@ function RangeCalendar ( return ( ( placeholder = 'dd/mm/yyyy', ...props }: RangeDatepickerProps, - ref: React.RefObject>, + ref: React.MutableRefObject>, ): RangeCalendarElement { - const baseDatepickerRef = React.useRef(); const dateService = props.dateService ?? new NativeDateService() as unknown as DateService; React.useImperativeHandle(ref, () => ({ ...ref.current, - ...baseDatepickerRef.current, clear, })); - const calendarProps = (): RangeCalendarProps => { - return { - dateService, - min: props.min, - max: props.max, - range: props.range, - initialVisibleDate: props.initialVisibleDate, - boundingMonth: props.boundingMonth, - startView: props.startView, - filter: props.filter, - title: props.title, - onSelect: props.onSelect, - renderDay: props.renderDay, - renderMonth: props.renderMonth, - renderYear: props.renderYear, - renderFooter: props.renderFooter, - renderArrowRight: props.renderArrowRight, - renderArrowLeft: props.renderArrowLeft, - onVisibleDateChange: props.onVisibleDateChange, - }; - }; + const calendarProps: RangeCalendarProps = ({ + dateService, + min: props.min, + max: props.max, + range: props.range, + initialVisibleDate: props.initialVisibleDate, + boundingMonth: props.boundingMonth, + startView: props.startView, + filter: props.filter, + title: props.title, + onSelect: props.onSelect, + renderDay: props.renderDay, + renderMonth: props.renderMonth, + renderYear: props.renderYear, + renderFooter: props.renderFooter, + renderArrowRight: props.renderArrowRight, + renderArrowLeft: props.renderArrowLeft, + onVisibleDateChange: props.onVisibleDateChange, + }); const clear = (): void => { props.onSelect?.({}); }; - // BaseDatepickerComponent - const getComponentTitle = (): RenderProp | string | number => { const { startDate, endDate } = props.range; @@ -209,8 +203,8 @@ function RangeDatepicker ( clear={clear} > );