UNPKG

react-native-ui-lib

Version:

[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://stand-with-ukraine.pp.ua)

194 lines • 7.69 kB
import React, { useCallback, useEffect, useMemo, useRef, useState, useImperativeHandle, forwardRef } from 'react'; import { StyleSheet } from 'react-native'; import { DateTimePickerPackage as RNDateTimePicker } from "../../optionalDependencies"; import { useDidUpdate } from "../../hooks"; import { Colors } from "../../style"; import Assets from "../../assets"; import { Constants, asBaseComponent } from "../../commons/new"; import TextField from "../textField"; import View from "../view"; import Button from "../button"; import ExpandableOverlay from "../../incubator/expandableOverlay"; import useOldApi from "./useOldApi"; import { isSameDate, isSameHourAndMinute } from "../../utils/dateUtils"; import { LogService } from "../../services"; /*eslint-disable*/ /** * @description: Date and Time Picker Component that wraps RNDateTimePicker for date and time modes. * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/DateTimePickerScreen.tsx * @important: DateTimePicker uses a native library. You MUST add and link the native library to both iOS and Android projects. * @extends: TextField, react-native-community/datetimepicker * @extendsLink: https://github.com/react-native-community/react-native-datetimepicker#react-native-datetimepicker * @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/DateTimePicker/DateTimePicker_iOS.gif?raw=true, https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/DateTimePicker/DateTimePicker_Android.gif?raw=true */ /*eslint-enable*/ const DateTimePicker = forwardRef((props, ref) => { const { value: propsValue, renderInput, editable, mode = 'date', dateFormat, timeFormat, dateFormatter, timeFormatter, dateTimeFormatter, minimumDate, maximumDate, locale, is24Hour, minuteInterval, timeZoneOffsetInMinutes, themeVariant = Colors.getScheme(), onChange, dialogProps, migrateDialog, textColor = Colors.$textDefault, backgroundColor = Colors.$backgroundDefault, headerStyle, testID, display = Constants.isIOS ? 'spinner' : undefined, confirmButtonProps, cancelButtonProps, ...others } = props; const [value, setValue] = useState(propsValue); const chosenDate = useRef(propsValue); const expandable = useRef(); const textField = useRef(); useImperativeHandle(ref, () => { return { isValid: () => textField.current?.isValid(), validate: () => textField.current?.validate() }; }); useEffect(() => { if (!RNDateTimePicker) { // eslint-disable-next-line max-len LogService.error(`RNUILib DateTimePicker component requires installing "@react-native-community/datetimepicker" dependency`); } }, []); useDidUpdate(() => { setValue(propsValue); }, [propsValue]); const _dialogProps = useMemo(() => { return { width: '100%', height: null, bottom: true, centerH: true, containerStyle: styles.dialog, testID: `${testID}.dialog`, supportedOrientations: ['portrait', 'landscape', 'landscape-left', 'landscape-right'], ...dialogProps }; }, [dialogProps, testID]); const dateTimePickerStyle = useMemo(() => { return { backgroundColor }; }, [backgroundColor]); const { getStringValue: getStringValueOld } = useOldApi({ dateFormat, dateFormatter, timeFormat, timeFormatter }); const getStringValue = () => { if (value) { if (dateTimeFormatter) { return dateTimeFormatter(value, mode); } else { return getStringValueOld(value, mode); // TODO: once we remove the old implementation, add the following: // return mode === 'time' ? value.toLocaleTimeString() : value.toLocaleDateString(); } } }; const toggleExpandableOverlay = useCallback(() => { expandable.current?.toggleExpandable?.(); }, []); const isValueChanged = useCallback(() => { return mode === 'time' ? !isSameHourAndMinute(chosenDate.current, value) : !isSameDate(chosenDate.current, value); }, [mode, value]); const onDonePressed = useCallback(() => { toggleExpandableOverlay(); if (Constants.isIOS && !chosenDate.current) { // since handleChange() is not called on iOS when there is no actual change chosenDate.current = new Date(); } if (chosenDate.current && isValueChanged()) { onChange?.(chosenDate?.current); } setValue(chosenDate.current); }, [toggleExpandableOverlay, onChange, isValueChanged]); const handleChange = useCallback((event = {}, date) => { // NOTE: will be called on Android even when there was no actual change if (event.type !== 'dismissed' && date !== undefined) { chosenDate.current = date; if (Constants.isAndroid) { onDonePressed(); } } else if (event.type === 'dismissed' && Constants.isAndroid) { toggleExpandableOverlay(); } }, [onDonePressed, toggleExpandableOverlay]); const renderHeader = () => { return <View row spread bg-$backgroundDefault backgroundColor={backgroundColor} paddingH-20 style={[styles.header, headerStyle]} testID={`${testID}.header`}> <Button link iconSource={Assets.internal.icons.x} iconStyle={{ tintColor: Colors.$iconDefault }} testID={`${testID}.cancel`} {...cancelButtonProps} onPress={toggleExpandableOverlay} /> <Button link iconSource={Assets.internal.icons.check} testID={`${testID}.done`} {...confirmButtonProps} onPress={onDonePressed} /> </View>; }; const renderDateTimePicker = useCallback(() => { if (!RNDateTimePicker) { return null; } return <RNDateTimePicker mode={mode} value={value || new Date()} onChange={handleChange} minimumDate={minimumDate} maximumDate={maximumDate} locale={locale} is24Hour={is24Hour} minuteInterval={minuteInterval} timeZoneOffsetInMinutes={timeZoneOffsetInMinutes} display={display} textColor={textColor} style={dateTimePickerStyle} themeVariant={themeVariant} testID={`${testID}.picker`} />; }, [mode, value, handleChange, minimumDate, maximumDate, locale, is24Hour, minuteInterval, timeZoneOffsetInMinutes, themeVariant]); const renderIOSExpandableOverlay = () => { return <> {renderHeader()} {renderDateTimePicker()} </>; }; const renderAndroidDateTimePicker = useCallback(({ visible }) => { if (visible) { return renderDateTimePicker(); } }, [renderDateTimePicker]); return <> <ExpandableOverlay // @ts-expect-error ref={expandable} expandableContent={Constants.isIOS ? renderIOSExpandableOverlay() : undefined} useDialog dialogProps={_dialogProps} migrateDialog={migrateDialog} disabled={editable === false} // NOTE: Android picker comes with its own overlay built-in therefor we're not using ExpandableOverlay for it renderCustomOverlay={Constants.isAndroid ? renderAndroidDateTimePicker : undefined} testID={`${testID}.overlay`}> {renderInput ? renderInput({ ...props, value: getStringValue() }) : <TextField {...others} // @ts-expect-error ref={textField} testID={testID} editable={editable} value={getStringValue()} />} </ExpandableOverlay> </>; }); DateTimePicker.displayName = 'DateTimePicker'; export { DateTimePicker }; // For tests export default asBaseComponent(DateTimePicker); const styles = StyleSheet.create({ header: { height: 56, borderBottomWidth: 1, borderBottomColor: Colors.$outlineDefault }, dialog: { backgroundColor: Colors.$backgroundDefault, borderTopLeftRadius: 12, borderTopRightRadius: 12 } });