diff --git a/src/components/ui/calendar/baseCalendar.component.tsx b/src/components/ui/calendar/baseCalendar.component.tsx index 33b7e4c85..eeae4d5d3 100644 --- a/src/components/ui/calendar/baseCalendar.component.tsx +++ b/src/components/ui/calendar/baseCalendar.component.tsx @@ -36,17 +36,25 @@ 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<D = Date> extends BaseCalendarProps<D> { + createDates: (date: D) => DateBatch<D>; + selectedDate: () => D | undefined; + onDateSelect: (item: D) => void; + isDateSelected: (date: D) => boolean; + shouldUpdateDate: (props: CalendarPickerCellProps<D>, nextProps: CalendarPickerCellProps<D>) => boolean; +} + export interface BaseCalendarProps<D = Date> extends ViewProps { min?: D; max?: D; initialVisibleDate?: D; dateService?: DateService<D>; + dataService?: CalendarDataService<D>; boundingMonth?: boolean; startView?: CalendarViewMode; title?: (datePickerDate: D, monthYearPickerDate: D, viewMode: CalendarViewMode) => string; @@ -61,68 +69,88 @@ export interface BaseCalendarProps<D = Date> extends ViewProps { eva?: EvaProp; } -export type BaseCalendarElement<D> = React.ReactElement<BaseCalendarProps<D>>; - -interface State<D> { - 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 -} +export type BaseCalendarElement<D = Date> = React.ReactElement<DerivedCalendarProps<D>>; const PICKER_ROWS = 4; const PICKER_COLUMNS = 3; const VIEWS_IN_PICKER: number = PICKER_ROWS * PICKER_COLUMNS; -export abstract class BaseCalendarComponent<P, D = Date> extends React.Component<BaseCalendarProps<D> & P, State<D>> { +export interface BaseCalendarRef<D = Date> { + scrollToToday: () => void; + scrollToDate: (date: D) => void; + state: State<D>; +} + +interface State<D> { + viewMode: CalendarViewMode; + visibleDate: D; + pickerDate: D; +} - static defaultProps: Partial<BaseCalendarProps> = { - dateService: new NativeDateService(), - boundingMonth: true, - startView: CalendarViewModes.DATE, - }; +function BaseCalendarComponent<D = Date>( + { + dateService, + dataService, + boundingMonth = true, + startView = CalendarViewModes.DATE, + ...props + }: DerivedCalendarProps<D>, + ref: React.RefObject<BaseCalendarRef<D>> +): BaseCalendarElement<D> { + const [viewMode, setViewMode] = React.useState<CalendarViewMode>(startView); - public state: State<D> = { - 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<D> = new CalendarDataService(this.dateService); + // is used in date view mode + const [ + visibleDate, + setVisibleDate, + ] = React.useState(dateService.getMonthStart(initialVisibleDate())); - protected get dateService(): DateService<D> { - 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 + + const min = (): D => { + return props.min || dateService.getYearStart(dateService.today()); + }; - private get max(): D { - return this.props.max || this.dateService.getYearEnd(this.dateService.today()); - } + const max = (): D => { + return props.max || dateService.getYearEnd(dateService.today()); + }; - public scrollToToday = (): void => { - this.setState({ - viewMode: CalendarViewModes.DATE, - visibleDate: this.dateService.today(), - pickerDate: this.dateService.today(), - }); + const scrollToToday = (): void => { + setViewMode(CalendarViewModes.DATE); + setVisibleDate(dateService.today()); + setPickerDate(dateService.today()); }; - public scrollToDate = (date: D): void => { + 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 => { + React.useImperativeHandle(ref, () => ({ + ...ref.current, + scrollToToday, + scrollToDate, + state: { + pickerDate, + viewMode, + visibleDate, + }, + })); + + const getCalendarStyle = (source: StyleType): StyleType => { return { container: { width: source.width, @@ -159,111 +187,84 @@ export abstract class BaseCalendarComponent<P, D = Date> extends React.Component }; }; - public isDayDisabled = ({ date }: CalendarDateInfo<D>): 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<D>): 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<D>): boolean => { - return this.dateService.isSameDaySafe(date, this.dateService.today()); + const isDayToday = ({ date }: CalendarDateInfo<D>): boolean => { + return dateService.isSameDaySafe(date, dateService.today()); }; - protected abstract createDates(date: D): DateBatch<D>; - - protected abstract selectedDate(): D | undefined; - - protected abstract onDateSelect(item: D): void; - - protected abstract isDateSelected(date: D): boolean; - - protected abstract shouldUpdateDate(props: CalendarPickerCellProps<D>, - nextProps: CalendarPickerCellProps<D>): boolean; - - private initialVisibleDate(): D { - return this.props.initialVisibleDate || this.selectedDate() || this.dateService.today(); - } - - private onDaySelect = ({ date }: CalendarDateInfo<D>): void => { - this.onDateSelect(date); + const onDaySelect = ({ date }: CalendarDateInfo<D>): void => { + props.onDateSelect(date); }; - private onMonthSelect = ({ date }: CalendarDateInfo<D>): 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<D>): 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<D>): 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<D>): 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 +273,71 @@ export abstract class BaseCalendarComponent<P, D = Date> extends React.Component }; }; - private isDaySelected = ({ date }: CalendarDateInfo<D>): boolean => { - return this.isDateSelected(date); + const isDaySelected = ({ date }: CalendarDateInfo<D>): boolean => { + return props.isDateSelected(date); }; - private isMonthSelected = ({ date }: CalendarDateInfo<D>): boolean => { - return this.dateService.isSameMonthSafe(date, this.selectedDate()); + const isMonthSelected = ({ date }: CalendarDateInfo<D>): boolean => { + return dateService.isSameMonthSafe(date, props.selectedDate()); }; - private isYearSelected = ({ date }: CalendarDateInfo<D>): boolean => { - return this.dateService.isSameYearSafe(date, this.selectedDate()); + const isYearSelected = ({ date }: CalendarDateInfo<D>): boolean => { + return dateService.isSameYearSafe(date, props.selectedDate()); }; - private isMonthDisabled = ({ date }: CalendarDateInfo<D>): boolean => { - const minMonthStart: D = this.dateService.getMonthStart(this.min); - const maxMonthStart: D = this.dateService.getMonthStart(this.max); + const isMonthDisabled = ({ date }: CalendarDateInfo<D>): 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<D>): boolean => { - const minYearStart: D = this.dateService.getYearStart(this.min); - const maxYearStart: D = this.dateService.getYearEnd(this.max); + const isYearDisabled = ({ date }: CalendarDateInfo<D>): 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<D>): boolean => { - return this.dateService.isSameMonthSafe(date.date, this.dateService.today()); + const isMonthToday = (date: CalendarDateInfo<D>): boolean => { + return dateService.isSameMonthSafe(date.date, dateService.today()); }; - private isYearToday = ({ date }: CalendarDateInfo<D>): boolean => { - return this.dateService.isSameYearSafe(date, this.dateService.today()); + const isYearToday = ({ date }: CalendarDateInfo<D>): 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 +346,175 @@ export abstract class BaseCalendarComponent<P, D = Date> extends React.Component } }; - private renderDayIfNeeded = (item: CalendarDateInfo<D>, style: StyleType): CalendarDateContentElement => { - const shouldRender: boolean = !item.bounding || this.props.boundingMonth; + const renderDayIfNeeded = (item: CalendarDateInfo<D>, 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 ( <CalendarDateContent key={index} - textStyle={this.getWeekdayStyle(this.props.eva.style)} + textStyle={getWeekdayStyle(props.eva.style)} > {weekday} </CalendarDateContent> ); }; - private renderDayElement = ({ date }: CalendarDateInfo<D>, evaStyle): CalendarDateContentElement => { + const renderDayElement = ({ date }: CalendarDateInfo<D>, evaStyle: StyleType): CalendarDateContentElement => { return ( <CalendarDateContent style={evaStyle.container} textStyle={evaStyle.text} > - {this.dateService.getDate(date)} + {dateService.getDate(date)} </CalendarDateContent> ); }; - private renderMonthElement = ({ date }: CalendarDateInfo<D>, evaStyle): CalendarDateContentElement => { + const renderMonthElement = ({ date }: CalendarDateInfo<D>, evaStyle: StyleType): CalendarDateContentElement => { return ( <CalendarDateContent style={evaStyle.container} textStyle={evaStyle.text} > - {this.dateService.getMonthName(date, TranslationWidth.SHORT)} + {dateService.getMonthName(date, TranslationWidth.SHORT)} </CalendarDateContent> ); }; - private renderYearElement = ({ date }: CalendarDateInfo<D>, evaStyle): CalendarDateContentElement => { + const renderYearElement = ({ date }: CalendarDateInfo<D>, evaStyle: StyleType): CalendarDateContentElement => { return ( <CalendarDateContent style={evaStyle.container} textStyle={evaStyle.text} > - {this.dateService.getYear(date)} + {dateService.getYear(date)} </CalendarDateContent> ); }; - private renderDayPickerElement = (date: D, evaStyle): React.ReactElement => { + const renderDayPickerElement = (date: D, evaStyle: StyleType): React.ReactElement => { return ( <> <CalendarMonthHeader style={evaStyle.daysHeaderContainer} - data={this.dateService.getDayOfWeekNames()} + data={dateService.getDayOfWeekNames()} > - {this.renderWeekdayElement} + {renderWeekdayElement} </CalendarMonthHeader> <Divider style={evaStyle.divider} /> <CalendarPicker rowStyle={evaStyle.row} - data={this.createDates(date)} - onSelect={this.onDaySelect} - isItemSelected={this.isDaySelected} - isItemDisabled={this.isDayDisabled} - isItemToday={this.isDayToday} - shouldItemUpdate={this.shouldUpdateDate} + data={props.createDates(date)} + onSelect={onDaySelect} + isItemSelected={isDaySelected} + isItemDisabled={isDayDisabled} + isItemToday={isDayToday} + shouldItemUpdate={props.shouldUpdateDate} > - {this.renderDayIfNeeded} + {renderDayIfNeeded} </CalendarPicker> </> ); }; - private renderMonthPickerElement = (date: D, evaStyle): CalendarPickerElement<D> => { + const renderMonthPickerElement = (date: D, evaStyle: StyleType): CalendarPickerElement<D> => { return ( <CalendarPicker rowStyle={evaStyle.row} - data={this.dataService.createMonthPickerData(date, PICKER_ROWS, PICKER_COLUMNS)} - onSelect={this.onMonthSelect} - isItemSelected={this.isMonthSelected} - isItemDisabled={this.isMonthDisabled} - isItemToday={this.isMonthToday} + data={dataService.createMonthPickerData(date, PICKER_ROWS, PICKER_COLUMNS)} + onSelect={onMonthSelect} + isItemSelected={isMonthSelected} + isItemDisabled={isMonthDisabled} + isItemToday={isMonthToday} > - {this.props.renderMonth || this.renderMonthElement} + {props.renderMonth || renderMonthElement} </CalendarPicker> ); }; - private renderYearPickerElement = (date: D, style: StyleType): CalendarPickerElement<D> => { + const renderYearPickerElement = (date: D, style: StyleType): CalendarPickerElement<D> => { return ( <CalendarPicker rowStyle={style.row} - data={this.dataService.createYearPickerData(date, PICKER_ROWS, PICKER_COLUMNS)} - onSelect={this.onYearSelect} - isItemSelected={this.isYearSelected} - isItemDisabled={this.isYearDisabled} - isItemToday={this.isYearToday} + data={dataService.createYearPickerData(date, PICKER_ROWS, PICKER_COLUMNS)} + onSelect={onYearSelect} + isItemSelected={isYearSelected} + isItemDisabled={isYearDisabled} + isItemToday={isYearToday} > - {this.props.renderYear || this.renderYearElement} + {props.renderYear || renderYearElement} </CalendarPicker> ); }; - 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 ( <CalendarHeader - viewModeId={this.state.viewMode.id} + viewModeId={viewMode.id} style={evaStyle.headerContainer} - title={titleSelector(this.state.visibleDate, this.state.pickerDate, this.state.viewMode)} + title={titleSelector(visibleDate, pickerDate, viewMode)} titleStyle={evaStyle.title} iconStyle={evaStyle.icon} - lateralNavigationAllowed={this.isHeaderNavigationAllowed()} - onTitlePress={this.onPickerNavigationPress} - onNavigationLeftPress={this.onHeaderNavigationLeftPress} - onNavigationRightPress={this.onHeaderNavigationRightPress} - arrowLeftComponent={this.props.renderArrowLeft} - arrowRightComponent={this.props.renderArrowRight} + lateralNavigationAllowed={isHeaderNavigationAllowed()} + onTitlePress={onPickerNavigationPress} + onNavigationLeftPress={onHeaderNavigationLeftPress} + onNavigationRightPress={onHeaderNavigationRightPress} + arrowLeftComponent={props.renderArrowLeft} + arrowRightComponent={props.renderArrowRight} /> ); }; - public render(): React.ReactElement<ViewProps> { - const { eva, style, ...viewProps } = this.props; - const evaStyle = this.getCalendarStyle(eva.style); + const { eva, style, ...viewProps } = props; + const evaStyle = getCalendarStyle(eva.style); - return ( - <View - {...viewProps} - style={[evaStyle.container, style]} - > - {this.renderHeaderElement(evaStyle)} - {this.renderPickerElement(evaStyle)} - {this.renderFooterElement()} - </View> - ); - } + return ( + <View + {...viewProps} + style={[evaStyle.container, style]} + > + {renderHeaderElement(evaStyle)} + {renderPickerElement(evaStyle)} + {renderFooterElement()} + </View> + ); } + +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..f27fc75e4 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,9 +13,10 @@ import { import { BaseCalendarComponent, BaseCalendarProps, + 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<D = Date> extends StyledComponentProps, BaseCalendarProps<D> { date?: D; @@ -23,6 +25,10 @@ export interface CalendarProps<D = Date> extends StyledComponentProps, BaseCalen export type CalendarElement<D = Date> = React.ReactElement<CalendarProps<D>>; +export type CalendarRef<D = Date> = BaseCalendarRef<D> & { + dataService: CalendarDataService<D>; +}; + /** * Calendar provides a simple way to select a date. * @@ -127,47 +133,46 @@ export type CalendarElement<D = Date> = React.ReactElement<CalendarProps<D>>; * @overview-example CalendarTheming * Styling of the calendar is possible with [configuring a custom theme](guides/branding). */ - -@styled('Calendar') -export class Calendar<D = Date> extends BaseCalendarComponent<CalendarProps<D>, D> { - - constructor(props: CalendarProps<D>) { - 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<D> { - return this.dataService.createDayPickerData(date); - } - - protected selectedDate(): D | undefined { - return this.props.date; - } - - protected onDateSelect(date: D): void { - this.props.onSelect?.(date); - } - - protected isDateSelected(date: D): boolean { - return this.dateService.isSameDaySafe(date, this.selectedDate()); - } - - protected shouldUpdateDate(props: CalendarPickerCellProps<D>, nextProps: CalendarPickerCellProps<D>): boolean { - const dateChanged: boolean = this.dateService.compareDatesSafe(props.date.date, nextProps.date.date) !== 0; +function Calendar <D = Date> ( + props: CalendarProps<D>, + ref: React.RefObject<CalendarRef<D>>, +): CalendarElement<D> { + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService<D>; + const dataService: CalendarDataService<D> = new CalendarDataService(dateService); + + React.useImperativeHandle(ref, () => ({ + ...ref.current, + dataService, + }), [dataService]); + + const createDates = (date: D): DateBatch<D> => { + return dataService.createDayPickerData(date); + }; + + const selectedDate = (): D | undefined => { + return props.date; + }; + + const onDateSelect = (date: D): void => { + props.onSelect?.(date); + }; + + const isDateSelected = (date: D): boolean => { + return dateService.isSameDaySafe(date, selectedDate()); + }; + + const shouldUpdateDate = (prevProps: CalendarPickerCellProps<D>, nextProps: CalendarPickerCellProps<D>): 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,26 @@ export class Calendar<D = Date> extends BaseCalendarComponent<CalendarProps<D>, return true; } - return props.eva.theme !== nextProps.eva.theme; - } + return prevProps.eva.theme !== nextProps.eva.theme; + }; + + return ( + <BaseCalendarComponent + {...props} + dateService={dateService} + dataService={dataService} + ref={ref} + createDates={createDates} + selectedDate={selectedDate} + onDateSelect={onDateSelect} + isDateSelected={isDateSelected} + shouldUpdateDate={shouldUpdateDate} + /> + ); } + +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<CalendarProps<Date | Moment>>, - ref: React.Ref<Calendar>, + ref: React.Ref<CalendarRef>, ) => { const [date, setDate] = React.useState<Date | Moment>(props.date); @@ -125,7 +125,7 @@ describe('@calendar: component checks', () => { }); it('should be rendered with view passed to startView prop', () => { - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); render( <TestCalendar ref={componentRef} @@ -137,7 +137,7 @@ describe('@calendar: component checks', () => { }); it('should change month to next when navigation button pressed', () => { - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); const component = render( <TestCalendar ref={componentRef} />, ); @@ -153,7 +153,7 @@ describe('@calendar: component checks', () => { }); it('should change month to previous when navigation button pressed', () => { - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); const component = render( <TestCalendar ref={componentRef} />, ); @@ -169,7 +169,7 @@ describe('@calendar: component checks', () => { }); it('should change year to next when navigation button pressed', () => { - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); const component = render( <TestCalendar ref={componentRef} @@ -189,7 +189,7 @@ describe('@calendar: component checks', () => { }); it('should change year to previous when navigation button pressed', () => { - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); const component = render( <TestCalendar ref={componentRef} @@ -210,7 +210,7 @@ describe('@calendar: component checks', () => { it('should show the selected date on load provided by date prop', () => { const date = new Date(2021, 2, 1); - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); render( <TestCalendar ref={componentRef} @@ -225,7 +225,7 @@ describe('@calendar: component checks', () => { it('should show the specific date on load provided by initialVisibleDate prop', () => { const initialDate = new Date(2021, 2, 1); - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); render( <TestCalendar ref={componentRef} @@ -240,7 +240,7 @@ describe('@calendar: component checks', () => { }); it('should scroll to current month when scrollToToday called', () => { - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); render( <TestCalendar ref={componentRef} @@ -255,7 +255,7 @@ describe('@calendar: component checks', () => { it('should scroll to the specific date when scrollToDate called', () => { const dateToScroll = new Date(2021, 2, 1); - const componentRef = React.createRef<Calendar>(); + const componentRef = React.createRef<CalendarRef>(); render( <TestCalendar ref={componentRef} diff --git a/src/components/ui/calendar/rangeCalendar.component.tsx b/src/components/ui/calendar/rangeCalendar.component.tsx index 7cf0261d5..98f106821 100644 --- a/src/components/ui/calendar/rangeCalendar.component.tsx +++ b/src/components/ui/calendar/rangeCalendar.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,9 +13,10 @@ import { import { BaseCalendarComponent, BaseCalendarProps, + BaseCalendarRef, } from './baseCalendar.component'; import { CalendarPickerCellProps } from './components/picker/calendarPickerCell.component'; -import { DateBatch } from './service/calendarData.service'; +import { CalendarDataService, DateBatch } from './service/calendarData.service'; import { RangeDateService } from './service/rangeDate.service'; import { CalendarRange } from './type'; @@ -25,6 +27,8 @@ export interface RangeCalendarProps<D = Date> extends StyledComponentProps, Base export type RangeCalendarElement<D = Date> = React.ReactElement<RangeCalendarProps<D>>; +export type RangeCalendarRef<D = Date> = BaseCalendarRef<D>; + /** * Range Calendar provides a simple way to select a date range. * @@ -109,59 +113,53 @@ export type RangeCalendarElement<D = Date> = React.ReactElement<RangeCalendarPro * } * ``` */ -@styled('Calendar') -export class RangeCalendar<D = Date> extends BaseCalendarComponent<RangeCalendarProps<D>, D> { - - static defaultProps: Partial<RangeCalendarProps> = { - ...BaseCalendarComponent.defaultProps, - range: {}, +function RangeCalendar <D = Date> ( + { + range = {}, + ...props + }: RangeCalendarProps<D>, + ref: React.RefObject<RangeCalendarRef<D>>, +): RangeCalendarElement { + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService<D>; + const rangeDateService: RangeDateService<D> = new RangeDateService(dateService); + const dataService: CalendarDataService<D> = new CalendarDataService(dateService); + + React.useImperativeHandle(ref, () => ({ + ...ref.current, + dataService, + }), [dataService]); + + const createDates = (date: D): DateBatch<D> => { + return dataService.createDayPickerData(date, range); }; - private rangeDateService: RangeDateService<D> = new RangeDateService(this.dateService); - - constructor(props: RangeCalendarProps<D>) { - 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<D> { - 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<D> = this.rangeDateService.createRange(this.props.range, date); - this.props.onSelect(range); + const onDateSelect = (date: D): void => { + if (props.onSelect) { + const calendarRange: CalendarRange<D> = rangeDateService.createRange(range, date); + props.onSelect(calendarRange); } - } + }; - protected isDateSelected(): boolean { + const isDateSelected = (): boolean => { return false; - } + }; - protected shouldUpdateDate(props: CalendarPickerCellProps<D>, nextProps: CalendarPickerCellProps<D>): boolean { - const dateChanged: boolean = this.dateService.compareDatesSafe(props.date.date, nextProps.date.date) !== 0; + const shouldUpdateDate = (prevProps: CalendarPickerCellProps<D>, nextProps: CalendarPickerCellProps<D>): 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 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,26 @@ export class RangeCalendar<D = Date> extends BaseCalendarComponent<RangeCalendar return true; } - return props.eva.theme !== nextProps.eva.theme; - } + return prevProps.eva.theme !== nextProps.eva.theme; + }; + + return ( + <BaseCalendarComponent + {...props} + dateService={dateService} + dataService={dataService} + ref={ref} + createDates={createDates} + selectedDate={selectedDate} + onDateSelect={onDateSelect} + isDateSelected={isDateSelected} + shouldUpdateDate={shouldUpdateDate} + /> + ); } + +const Component = styled('Calendar')(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<RangeCalendarProps>, - ref: React.Ref<RangeCalendar>) => { + ref: React.Ref<RangeCalendarRef>) => { const [range, setRange] = React.useState<CalendarRange<Date>>(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<RangeCalendar>(); + const componentRef = React.createRef<RangeCalendarRef>(); render( <TestRangeCalendar ref={componentRef} diff --git a/src/components/ui/datepicker/baseDatepicker.component.tsx b/src/components/ui/datepicker/baseDatepicker.component.tsx index c6a99f37e..b5e12f3bb 100644 --- a/src/components/ui/datepicker/baseDatepicker.component.tsx +++ b/src/components/ui/datepicker/baseDatepicker.component.tsx @@ -12,7 +12,6 @@ import { StyleSheet, TouchableOpacityProps, View, - ViewProps, ViewStyle, } from 'react-native'; import { @@ -26,252 +25,186 @@ import { import { Interaction, StyledComponentProps, - StyleType, } from '../../theme'; import { BaseCalendarProps } from '../calendar/baseCalendar.component'; import { CalendarElement } from '../calendar/calendar.component'; import { RangeCalendarElement } from '../calendar/rangeCalendar.component'; -import { NativeDateService } from '../calendar/service/nativeDate.service'; import { Popover } from '../popover/popover.component'; import { PopoverPlacement, PopoverPlacements, } from '../popover/type'; import { TextProps } from '../text/text.component'; +import { getComponentStyle } from './baseDatepicker.utils'; export interface BaseDatepickerProps<D = Date> extends StyledComponentProps, TouchableOpacityProps, BaseCalendarProps<D> { - controlStyle?: StyleProp<ViewStyle>; - label?: RenderProp<TextProps> | React.ReactText; - caption?: RenderProp<TextProps> | React.ReactText; + label?: RenderProp<TextProps> | string | number; + caption?: RenderProp<TextProps> | string | number; accessoryLeft?: RenderProp<Partial<ImageProps>>; accessoryRight?: RenderProp<Partial<ImageProps>>; status?: EvaStatus; size?: EvaInputSize; - placeholder?: RenderProp<TextProps> | React.ReactText; + placeholder?: RenderProp<TextProps> | string | number; placement?: PopoverPlacement | string; backdropStyle?: StyleProp<ViewStyle>; onFocus?: () => void; onBlur?: () => void; } -interface State { - visible: boolean; +interface DerivedDatepickerProps<D = Date> extends BaseDatepickerProps<D> { + children: CalendarElement<D> | RangeCalendarElement<D>; + getComponentTitle: () => RenderProp<TextProps> | string | number; + clear: () => void; } -export abstract class BaseDatepickerComponent<P, D = Date> extends React.Component<BaseDatepickerProps<D> & P, State> { +export interface BaseDatepickerRef { + focus: () => void; + blur: () => void; + isFocused: () => boolean; +} - static defaultProps: Partial<BaseDatepickerProps> = { - dateService: new NativeDateService(), - placeholder: 'dd/mm/yyyy', - placement: PopoverPlacements.BOTTOM_START, - }; +function BaseDatepickerComponent<D = Date>( + props: DerivedDatepickerProps<D>, + ref: React.RefObject<BaseDatepickerRef>, +): React.ReactElement<DerivedDatepickerProps<D>> { + const { + eva, + style, + testID, + backdropStyle, + label, + caption, + placement = PopoverPlacements.BOTTOM_START, + onFocus, + onBlur, + getComponentTitle, + children, + } = props; + const evaStyle = getComponentStyle(eva.style); - public state: State = { - visible: false, - }; + const [visible, setVisible] = React.useState(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected calendarRef = React.createRef<any>(); - - public scrollToToday = (): void => { - this.calendarRef.current?.scrollToToday(); + const focus = (): void => { + setVisible(true); + onPickerVisible(); }; - public scrollToDate = (date: Date): void => { - this.calendarRef.current?.scrollToDate(date); + const blur = (): void => { + setVisible(false); + onPickerInvisible(); }; - public focus = (): void => { - this.setState({ visible: true }, this.onPickerVisible); + const isFocused = (): boolean => { + return visible; }; - public blur = (): void => { - this.setState({ visible: false }, this.onPickerInvisible); - }; + React.useImperativeHandle(ref, () => ({ + ...ref.current, + focus, + blur, + isFocused, + })); - public isFocused = (): boolean => { - return this.state.visible; + const onPress = (event: GestureResponderEvent): void => { + setPickerVisible(); + props.onPress?.(event); }; - public abstract clear(): void; - - protected abstract getComponentTitle(): RenderProp<TextProps> | React.ReactText; - - protected abstract renderCalendar(): CalendarElement<D> | RangeCalendarElement<D>; - - 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 onPressIn = (event: GestureResponderEvent): void => { + eva.dispatch([Interaction.ACTIVE]); + props.onPressIn?.(event); }; - private onPress = (event: GestureResponderEvent): void => { - this.setPickerVisible(); - this.props.onPress?.(event); + const onPressOut = (event: GestureResponderEvent): void => { + eva.dispatch([]); + props.onPressOut?.(event); }; - private onPressIn = (event: GestureResponderEvent): void => { - this.props.eva.dispatch([Interaction.ACTIVE]); - this.props.onPressIn?.(event); + const onPickerVisible = (): void => { + eva.dispatch([Interaction.ACTIVE]); + onFocus?.(); }; - private onPressOut = (event: GestureResponderEvent): void => { - this.props.eva.dispatch([]); - this.props.onPressOut?.(event); + const onPickerInvisible = (): void => { + eva.dispatch([]); + onBlur?.(); }; - private onPickerVisible = (): void => { - this.props.eva.dispatch([Interaction.ACTIVE]); - this.props.onFocus?.(); + const setPickerVisible = (): void => { + setVisible(true); + onPickerVisible(); }; - private onPickerInvisible = (): void => { - this.props.eva.dispatch([]); - this.props.onBlur?.(); + const setPickerInvisible = (): void => { + setVisible(false); + onPickerInvisible(); }; - private setPickerVisible = (): void => { - this.setState({ visible: true }, this.onPickerVisible); - }; - - private setPickerInvisible = (): void => { - this.setState({ visible: false }, this.onPickerInvisible); - }; - - private renderInputElement = (props, evaStyle): React.ReactElement => { + const renderInputElement = (): React.ReactElement => { return ( <TouchableWithoutFeedback {...props} - style={[evaStyle.control, styles.control, this.props.controlStyle]} - onPress={this.onPress} - onPressIn={this.onPressIn} - onPressOut={this.onPressOut} + style={[evaStyle.control, styles.control, props.controlStyle]} + onPress={onPress} + onPressIn={onPressIn} + onPressOut={onPressOut} > <FalsyFC style={evaStyle.icon} - component={this.props.accessoryLeft} + component={props.accessoryLeft} /> <FalsyText style={evaStyle.text} numberOfLines={1} ellipsizeMode='tail' - component={this.getComponentTitle()} + component={getComponentTitle()} /> <FalsyFC style={evaStyle.icon} - component={this.props.accessoryRight} + component={props.accessoryRight} /> </TouchableWithoutFeedback> ); }; - public render(): React.ReactElement<ViewProps> { - const { - eva, - style, - testID, - backdropStyle, - controlStyle, - placement, - label, - accessoryLeft, - accessoryRight, - caption, - ...touchableProps - } = this.props; - - const evaStyle = this.getComponentStyle(eva.style); - - return ( - <View - style={style} - testID={testID} + return ( + <View + style={style} + testID={testID} + > + <FalsyText + style={[evaStyle.label, styles.label]} + component={label} + /> + <Popover + style={[evaStyle.popover, styles.popover]} + backdropStyle={backdropStyle} + placement={placement} + visible={visible} + anchor={renderInputElement} + onBackdropPress={setPickerInvisible} > - <FalsyText - style={[evaStyle.label, styles.label]} - component={label} - /> - <Popover - style={[evaStyle.popover, styles.popover]} - backdropStyle={backdropStyle} - placement={placement} - visible={this.state.visible} - anchor={() => this.renderInputElement(touchableProps, evaStyle)} - onBackdropPress={this.setPickerInvisible} - > - {this.renderCalendar()} - </Popover> - <FalsyText - style={[evaStyle.captionLabel, styles.captionLabel]} - component={caption} - /> - </View> - ); - } + {children} + </Popover> + <FalsyText + style={[evaStyle.captionLabel, styles.captionLabel]} + component={caption} + /> + </View> + ); } +BaseDatepickerComponent.displayName = 'BaseDatepickerComponent'; + +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..2e8d128d9 100644 --- a/src/components/ui/datepicker/datepicker.component.tsx +++ b/src/components/ui/datepicker/datepicker.component.tsx @@ -4,17 +4,19 @@ * 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 +26,10 @@ export interface DatepickerProps<D = Date> extends BaseDatepickerProps<D>, Calen export type DatepickerElement<D = Date> = React.ReactElement<DatepickerProps<D>>; +export type DatepickerRef<D = Date> = CalendarRef<D> & BaseDatepickerRef & { + clear: () => void; +}; + /** * Date picker provides a simple way to select a date within a picker displayed in modal. * @@ -187,69 +193,84 @@ export type DatepickerElement<D = Date> = React.ReactElement<DatepickerProps<D>> * @overview-example DatepickerTheming * In most cases this is redundant, if [custom theme is configured](guides/branding). */ -@styled('Datepicker') -export class Datepicker<D = Date> extends BaseDatepickerComponent<DatepickerProps<D>, D> { - - static defaultProps: DatepickerProps = { - ...BaseDatepickerComponent.defaultProps, - autoDismiss: true, - }; +function Datepicker<D = Date>( + { + autoDismiss = true, + placeholder = 'dd/mm/yyyy', + ...props + }: DatepickerProps<D>, + ref: React.MutableRefObject<DatepickerRef<D>> +): DatepickerElement<D> { + const baseDatepickerRef = React.useRef<BaseDatepickerRef>(); + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService<D>; - constructor(props: DatepickerProps<D>) { - super(props); - this.clear = this.clear.bind(this); - } + React.useImperativeHandle(ref, () => ({ + ...ref.current, + ...baseDatepickerRef.current, + clear, + })); - private get calendarProps(): CalendarProps<D> { - 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<D> = ({ + 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<TextProps> | React.ReactText { - if (this.props.date) { - return this.props.dateService.format(this.props.date, null); + const getComponentTitle = (): RenderProp<TextProps> | 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<D> { - return ( + return ( + <BaseDatepickerComponent + {...props} + dateService={dateService} + placeholder={placeholder} + ref={baseDatepickerRef} + getComponentTitle={getComponentTitle} + clear={clear} + > <Calendar - {...this.calendarProps} - ref={this.calendarRef} - onSelect={this.onSelect} + {...calendarProps} + ref={ref} + onSelect={onSelect} /> - ); - } + </BaseDatepickerComponent> + ); } + +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..19239d386 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<DatepickerProps>, ref: React.Ref<Datepicker>) => { + const TestDatepicker = React.forwardRef(( + props: Partial<DatepickerProps>, + ref: React.Ref<DatepickerRef>, + ) => { 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<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); const component = render( <TestDatepicker ref={componentRef} />, @@ -392,7 +397,7 @@ describe('@datepicker: component checks', () => { }); it('should hide calendar by calling `blur` with ref', async () => { - const componentRef: React.RefObject<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); const component = render( <TestDatepicker ref={componentRef} />, @@ -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<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); render( <TestDatepicker ref={componentRef} />, @@ -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<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); render( <TestDatepicker ref={componentRef} />, @@ -431,7 +436,7 @@ describe('@datepicker: component checks', () => { }); it('should call onSelect with null when calling `clear` with ref', async () => { - const componentRef: React.RefObject<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = 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<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); render( <TestDatepicker @@ -490,15 +495,14 @@ describe('@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()); }); it('should show the specific date on load provided by initialVisibleDate prop', () => { const initialDate = new Date(2021, 2, 1); - const componentRef: React.RefObject<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); render( <TestDatepicker @@ -510,14 +514,13 @@ describe('@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()); }); - it('should scroll to current month when scrollToToday called', () => { - const componentRef: React.RefObject<Datepicker> = React.createRef(); + it('should scroll to current month when scrollToToday called', async () => { + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); render( <TestDatepicker @@ -529,15 +532,14 @@ describe('@datepicker: component checks', () => { 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', () => { const dateToScroll = new Date(2021, 2, 1); - const componentRef: React.RefObject<Datepicker> = React.createRef(); + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); render( <TestDatepicker @@ -549,14 +551,13 @@ describe('@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()); }); - it('should render custom left arrow', () => { - const componentRef: React.RefObject<Datepicker> = React.createRef(); + it('should render custom left arrow', async () => { + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); const onVisibleDateChange = jest.fn(); @@ -581,7 +582,9 @@ describe('@datepicker: component checks', () => { /> ); - componentRef.current?.focus(); + await act(() => { + componentRef.current.focus(); + }); const leftArrow = component.queryByTestId('@arrow/left'); fireEvent.press(leftArrow); @@ -589,33 +592,33 @@ describe('@datepicker: component checks', () => { expect(onVisibleDateChange).toBeCalled(); }); - it('should render custom right arrow', () => { - const componentRef: React.RefObject<Datepicker> = React.createRef(); + it('should render custom right arrow', async () => { + const componentRef: React.RefObject<DatepickerRef> = React.createRef(); const onVisibleDateChange = jest.fn(); - const renderArrow = (props: { onPress: () => void }): React.ReactElement => { - return ( - <TouchableOpacity - testID='@arrow/right' - onPress={props.onPress} - > - <Text> - RIGHT - </Text> - </TouchableOpacity> - ); - }; - const component = render( <TestDatepicker ref={componentRef} - renderArrowRight={renderArrow} + renderArrowRight={(props: { onPress: () => void }): React.ReactElement => { + return ( + <TouchableOpacity + testID='@arrow/right' + onPress={props.onPress} + > + <Text> + RIGHT + </Text> + </TouchableOpacity> + ); + }} 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..ce00c75b9 100644 --- a/src/components/ui/datepicker/rangeDatepicker.component.tsx +++ b/src/components/ui/datepicker/rangeDatepicker.component.tsx @@ -9,18 +9,24 @@ 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'; +import { DateService, NativeDateService, TextProps } from '@ui-kitten/components'; export type RangeDatepickerProps<D = Date> = BaseDatepickerProps<D> & RangeCalendarProps<D>; export type RangeDatepickerElement<D = Date> = React.ReactElement<RangeDatepickerProps<D>>; +export type RangeDatepickerRef<D = Date> = RangeCalendarRef<D> & BaseDatepickerRef & { + clear: () => void; +}; + /** * Range date picker provides a simple way to select a date range within a picker displayed in modal. * @@ -137,63 +143,75 @@ export type RangeDatepickerElement<D = Date> = React.ReactElement<RangeDatepicke * Ranged picker works with special range object - CalendarRange: `{ startDate: Date, endDate: Date }`. * For incomplete ranges, there is only a `startDate` property. */ -@styled('Datepicker') -export class RangeDatepicker<D = Date> extends BaseDatepickerComponent<RangeDatepickerProps<D>, D> { - - static styledComponentName = 'Datepicker'; +function RangeDatepicker<D = Date> ( + { + placeholder = 'dd/mm/yyyy', + ...props + }: RangeDatepickerProps<D>, + ref: React.MutableRefObject<RangeDatepickerRef<D>>, +): RangeCalendarElement { + const dateService = props.dateService ?? new NativeDateService() as unknown as DateService<D>; - constructor(props: RangeDatepickerProps<D>) { - super(props); - this.clear = this.clear.bind(this); - } + React.useImperativeHandle(ref, () => ({ + ...ref.current, + clear, + })); - private get calendarProps(): RangeCalendarProps<D> { - 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, - }; - } + const calendarProps: RangeCalendarProps<D> = ({ + 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, + }); - public clear = (): void => { - this.props.onSelect?.({}); + const clear = (): void => { + props.onSelect?.({}); }; - // BaseDatepickerComponent - - protected getComponentTitle(): RenderProp<TextProps> | React.ReactText { - const { startDate, endDate } = this.props.range; + const getComponentTitle = (): RenderProp<TextProps> | 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 ? dateService.format(startDate, null) : ''; + const end: string = endDate ? dateService.format(endDate, null) : ''; return `${start} - ${end}`; } else { - return this.props.placeholder; + return placeholder; } - } + }; - protected renderCalendar(): RangeCalendarElement<D> { - return ( + return ( + <BaseDatepickerComponent + {...props} + placeholder={placeholder} + ref={ref} + getComponentTitle={getComponentTitle} + clear={clear} + > <RangeCalendar - ref={this.calendarRef} - {...this.calendarProps} + {...calendarProps} + ref={ref} /> - ); - } + </BaseDatepickerComponent> + ); } + +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 dbefd4f07..1886d083b 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 { @@ -50,8 +51,10 @@ describe('@range-datepicker: component checks', () => { jest.clearAllMocks(); }); - const TestRangeDatepicker = React.forwardRef((props: Partial<RangeDatepickerProps>, - ref: React.Ref<RangeDatepicker>) => { + const TestRangeDatepicker = React.forwardRef(( + props: Partial<RangeDatepickerProps>, + ref: React.Ref<RangeDatepickerRef>, + ) => { const [range, setRange] = React.useState(props.range || {}); const onSelect = (nextRange: CalendarRange<Date>): void => { @@ -127,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( <TestRangeDatepicker label={( <Text> @@ -372,7 +375,7 @@ describe('@range-datepicker: component checks', () => { }); it('should show calendar by calling `focus` with ref', async () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); const component = render( <TestRangeDatepicker ref={componentRef} />, ); @@ -384,7 +387,7 @@ describe('@range-datepicker: component checks', () => { }); it('should hide calendar by calling `blur` with ref', async () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); const component = render( <TestRangeDatepicker ref={componentRef} />, ); @@ -399,7 +402,7 @@ describe('@range-datepicker: component checks', () => { }); it('should return false if calendar not visible by calling `isFocused` with ref', async () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); render( <TestRangeDatepicker ref={componentRef} />, ); @@ -408,7 +411,7 @@ describe('@range-datepicker: component checks', () => { }); it('should return true if calendar visible by calling `isFocused` with ref', async () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); render( <TestRangeDatepicker ref={componentRef} />, ); @@ -420,7 +423,7 @@ describe('@range-datepicker: component checks', () => { }); it('should call onSelect with empty object when calling `clear` with ref', async () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); const onSelect = jest.fn(); render( @@ -468,7 +471,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<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); render( <TestRangeDatepicker @@ -482,15 +485,14 @@ 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()); }); it('should show the specific date on load provided by initialVisibleDate prop', () => { const initialDate = new Date(2021, 2, 1); - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); render( <TestRangeDatepicker @@ -501,14 +503,13 @@ 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()); }); it('should scroll to current month when scrollToToday called', () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); render( <TestRangeDatepicker @@ -521,14 +522,14 @@ 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()); }); it('should scroll to the specific date when scrollToDate called', () => { const dateToScroll = new Date(2020, 1, 1); - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); render( <TestRangeDatepicker @@ -540,14 +541,13 @@ 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()); }); it('should render custom left arrow', () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); const onVisibleDateChange = jest.fn(); @@ -572,7 +572,7 @@ describe('@range-datepicker: component checks', () => { /> ); - componentRef.current?.focus(); + componentRef.current.focus(); const leftArrow = component.queryByTestId('@arrow/left'); fireEvent.press(leftArrow); @@ -581,7 +581,7 @@ describe('@range-datepicker: component checks', () => { }); it('should render custom right arrow', () => { - const componentRef: React.RefObject<RangeDatepicker> = React.createRef(); + const componentRef: React.RefObject<RangeDatepickerRef> = React.createRef(); const onVisibleDateChange = jest.fn(); @@ -606,7 +606,7 @@ describe('@range-datepicker: component checks', () => { /> ); - componentRef.current?.focus(); + componentRef.current.focus(); const leftArrow = component.queryByTestId('@arrow/right'); fireEvent.press(leftArrow); 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<Calendar>(); + const componentRef = React.useRef<CalendarRef>(); 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<Datepicker>(); + const componentRef = React.useRef<DatepickerRef>(); const scrollToSelected = (): void => { if (componentRef.current) {