From d0e199af12ee41f0581faa68a210e867ee4598ff Mon Sep 17 00:00:00 2001 From: Brandon Fitzwater Date: Sun, 1 Jan 2023 07:36:00 -0500 Subject: [PATCH 1/6] Introduce new date input props and update docs. --- .../docs/date-picker/input-date-picker.md | 16 +++++++++++++ src/Date/DatePickerInput.shared.tsx | 3 +++ src/Date/DatePickerInputWithoutModal.tsx | 9 +++++--- src/Date/inputUtils.ts | 23 ++++++++++++------- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/docusaurus/docs/date-picker/input-date-picker.md b/docusaurus/docs/date-picker/input-date-picker.md index 02fb5572..dbac57f2 100644 --- a/docusaurus/docs/date-picker/input-date-picker.md +++ b/docusaurus/docs/date-picker/input-date-picker.md @@ -63,4 +63,20 @@ The type of input needed for the the picker component. `Type: 'flat' | 'outlined'` See [react-native-paper text-input](https://callstack.github.io/react-native-paper/text-input.html#mode). +**withDateFormatInLabel** +`Type: boolean | undefined` +Flag indicating if the date format should be inside the components label. + +**hasError** +`Type: boolean | undefined` +Flag indicating if the the component should display error styles. + +**hideValidationErrors** +`Type: boolean | undefined` +Flag indicating if the the component should hide error styles along with the `helperText` component displaying the error message. + +**onValidationError** +`Type: Function | undefined` +Callback used to return any error messages from the components validation. + * Other [react-native TextInput props](https://reactnative.dev/docs/textinput#props).* diff --git a/src/Date/DatePickerInput.shared.tsx b/src/Date/DatePickerInput.shared.tsx index 43d822f6..87425ef3 100644 --- a/src/Date/DatePickerInput.shared.tsx +++ b/src/Date/DatePickerInput.shared.tsx @@ -10,6 +10,9 @@ export type DatePickerInputProps = { validRange?: ValidRangeType | undefined withModal?: boolean withDateFormatInLabel?: boolean + hideValidationErrors?: boolean + hasError?: boolean + onValidationError?: ((error: string) => void) | undefined calendarIcon?: string saveLabel?: string } & Omit< diff --git a/src/Date/DatePickerInputWithoutModal.tsx b/src/Date/DatePickerInputWithoutModal.tsx index f9192cbb..f381be43 100644 --- a/src/Date/DatePickerInputWithoutModal.tsx +++ b/src/Date/DatePickerInputWithoutModal.tsx @@ -16,6 +16,9 @@ function DatePickerInputWithoutModal( validRange, inputMode, withDateFormatInLabel = true, + hasError, + hideValidationErrors, + onValidationError, modal, inputButtons, saveLabel, @@ -39,6 +42,7 @@ function DatePickerInputWithoutModal( validRange, inputMode, onChange, + onValidationError, }) return ( @@ -56,16 +60,15 @@ function DatePickerInputWithoutModal( })} value={formattedValue} keyboardType={'number-pad'} - placeholder={inputFormat} mask={inputFormat} onChangeText={onChangeText} keyboardAppearance={theme.dark ? 'dark' : 'default'} - error={!!error} + error={(!!error && !hideValidationErrors) || !!hasError} style={[styles.input, style]} /> {inputButtons} - {error ? ( + {error && !hideValidationErrors ? ( {error} diff --git a/src/Date/inputUtils.ts b/src/Date/inputUtils.ts index 16888b75..02389905 100644 --- a/src/Date/inputUtils.ts +++ b/src/Date/inputUtils.ts @@ -9,12 +9,14 @@ export default function useDateInput({ validRange, inputMode, onChange, + onValidationError, }: { onChange: (d: Date) => void locale: undefined | string value: Date | undefined validRange: ValidRangeType | undefined inputMode: 'start' | 'end' + onValidationError?: ((error: string) => void) | undefined }) { const { isDisabled, isWithinValidRange, validStart, validEnd } = useRangeChecker(validRange) @@ -35,13 +37,13 @@ export default function useDateInput({ const month = Number(date.slice(monthIndex, monthIndex + 2)) if (Number.isNaN(day) || Number.isNaN(year) || Number.isNaN(month)) { - setError( - getTranslation( - locale, - 'notAccordingToDateFormat', - () => 'notAccordingToDateFormat' - )(inputFormat) - ) + const inputError = getTranslation( + locale, + 'notAccordingToDateFormat', + () => 'notAccordingToDateFormat' + )(inputFormat) + setError(inputError) + onValidationError(inputError) return } @@ -51,7 +53,9 @@ export default function useDateInput({ : new Date(year, month - 1, day) if (isDisabled(finalDate)) { - setError(getTranslation(locale, 'dateIsDisabled')) + const inputError = getTranslation(locale, 'dateIsDisabled') + setError(inputError) + onValidationError(inputError) return } if (!isWithinValidRange(finalDate)) { @@ -80,11 +84,14 @@ export default function useDateInput({ )(formatter.format(validEnd)) : '', ] + const inputError = errors.filter((n) => n).join(' ') setError(errors.filter((n) => n).join(' ')) + onValidationError(inputError) return } setError(null) + onValidationError(null) if (inputMode === 'end') { onChange(finalDate) } else { From f75b001bc72b94d6ab70ed6cbdd64a30d3962e49 Mon Sep 17 00:00:00 2001 From: Brandon Fitzwater Date: Sun, 1 Jan 2023 07:39:19 -0500 Subject: [PATCH 2/6] Fix typescript errors. --- src/Date/DatePickerInput.shared.tsx | 2 +- src/Date/inputUtils.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Date/DatePickerInput.shared.tsx b/src/Date/DatePickerInput.shared.tsx index 87425ef3..3c0aa4d2 100644 --- a/src/Date/DatePickerInput.shared.tsx +++ b/src/Date/DatePickerInput.shared.tsx @@ -12,7 +12,7 @@ export type DatePickerInputProps = { withDateFormatInLabel?: boolean hideValidationErrors?: boolean hasError?: boolean - onValidationError?: ((error: string) => void) | undefined + onValidationError?: ((error: string | null) => void) | undefined calendarIcon?: string saveLabel?: string } & Omit< diff --git a/src/Date/inputUtils.ts b/src/Date/inputUtils.ts index 02389905..14f39c66 100644 --- a/src/Date/inputUtils.ts +++ b/src/Date/inputUtils.ts @@ -16,7 +16,7 @@ export default function useDateInput({ value: Date | undefined validRange: ValidRangeType | undefined inputMode: 'start' | 'end' - onValidationError?: ((error: string) => void) | undefined + onValidationError?: ((error: string | null) => void) | undefined }) { const { isDisabled, isWithinValidRange, validStart, validEnd } = useRangeChecker(validRange) @@ -43,7 +43,7 @@ export default function useDateInput({ () => 'notAccordingToDateFormat' )(inputFormat) setError(inputError) - onValidationError(inputError) + onValidationError && onValidationError(inputError) return } @@ -55,7 +55,7 @@ export default function useDateInput({ if (isDisabled(finalDate)) { const inputError = getTranslation(locale, 'dateIsDisabled') setError(inputError) - onValidationError(inputError) + onValidationError && onValidationError(inputError) return } if (!isWithinValidRange(finalDate)) { @@ -86,12 +86,12 @@ export default function useDateInput({ ] const inputError = errors.filter((n) => n).join(' ') setError(errors.filter((n) => n).join(' ')) - onValidationError(inputError) + onValidationError && onValidationError(inputError) return } setError(null) - onValidationError(null) + onValidationError && onValidationError(null) if (inputMode === 'end') { onChange(finalDate) } else { From 8eb6b8837215c1caab583d23769224ee54f6c6a9 Mon Sep 17 00:00:00 2001 From: Brandon Fitzwater Date: Mon, 2 Jan 2023 08:07:08 -0500 Subject: [PATCH 3/6] Add more props to date picker input. --- .../docs/date-picker/input-date-picker.md | 22 +++++++++++- src/Date/DatePickerInput.shared.tsx | 5 +++ src/Date/DatePickerInput.tsx | 17 ++++++++- src/Date/DatePickerInputWithoutModal.tsx | 36 +++++++++++++++---- src/TextInputMask.tsx | 4 +++ .../AnimatedCrossView.test.tsx.snap | 1 + .../__snapshots__/CalendarEdit.test.tsx.snap | 1 + .../DatePickerInput.test.tsx.snap | 1 + .../DatePickerInputWithoutModal.test.tsx.snap | 1 + 9 files changed, 79 insertions(+), 9 deletions(-) diff --git a/docusaurus/docs/date-picker/input-date-picker.md b/docusaurus/docs/date-picker/input-date-picker.md index dbac57f2..d3e64c7f 100644 --- a/docusaurus/docs/date-picker/input-date-picker.md +++ b/docusaurus/docs/date-picker/input-date-picker.md @@ -53,7 +53,11 @@ The value used to populate the component. **onChange** `Type: Function` -Event handler when the component changes. +Callback event when the component date mask length matches the text input length. + +**onChangeText** +`Type: Function` +Callback event when the component text input changes. **inputMode (Required)** `Type: String` @@ -79,4 +83,20 @@ Flag indicating if the the component should hide error styles along with the `he `Type: Function | undefined` Callback used to return any error messages from the components validation. +**saveLabelDisabled** +`Type: boolean | undefined` +Flag indicating if the save label should be disabled and unable to receive events. Defaults to `false`. + +**uppercase** +`Type: boolean | undefined` +Flag indicating if the text in the component should be uppercase. Defaults to `true`. + +**startYear** +`Type: number | undefined` +The start year when the component is rendered. Defaults to `1800`. + +**endYear** +`Type: number | undefined` +The end year when the component is rendered. Defaults to `2200`. + * Other [react-native TextInput props](https://reactnative.dev/docs/textinput#props).* diff --git a/src/Date/DatePickerInput.shared.tsx b/src/Date/DatePickerInput.shared.tsx index 3c0aa4d2..9c906dc0 100644 --- a/src/Date/DatePickerInput.shared.tsx +++ b/src/Date/DatePickerInput.shared.tsx @@ -15,6 +15,11 @@ export type DatePickerInputProps = { onValidationError?: ((error: string | null) => void) | undefined calendarIcon?: string saveLabel?: string + saveLabelDisabled?: boolean + uppercase?: boolean + startYear?: number + endYear?: number + onChangeText?: (text: string | undefined) => void } & Omit< React.ComponentProps, 'value' | 'onChange' | 'onChangeText' diff --git a/src/Date/DatePickerInput.tsx b/src/Date/DatePickerInput.tsx index 5f7dc048..fb2f8082 100644 --- a/src/Date/DatePickerInput.tsx +++ b/src/Date/DatePickerInput.tsx @@ -42,7 +42,18 @@ function DatePickerInput( /> ) : null } - modal={({ value, locale, inputMode, validRange, saveLabel }) => + // eslint-disable-next-line react/no-unstable-nested-components + modal={({ + value, + locale, + inputMode, + validRange, + saveLabel, + saveLabelDisabled, + uppercase, + startYear, + endYear, + }) => withModal ? ( ) : null } diff --git a/src/Date/DatePickerInputWithoutModal.tsx b/src/Date/DatePickerInputWithoutModal.tsx index f381be43..de59df56 100644 --- a/src/Date/DatePickerInputWithoutModal.tsx +++ b/src/Date/DatePickerInputWithoutModal.tsx @@ -22,6 +22,11 @@ function DatePickerInputWithoutModal( modal, inputButtons, saveLabel, + saveLabelDisabled, + uppercase, + startYear, + endYear, + onChangeText, ...rest }: DatePickerInputProps & { modal?: (params: { @@ -30,13 +35,22 @@ function DatePickerInputWithoutModal( inputMode: DatePickerInputProps['inputMode'] validRange: DatePickerInputProps['validRange'] saveLabel: DatePickerInputProps['saveLabel'] + saveLabelDisabled: DatePickerInputProps['saveLabelDisabled'] + uppercase: DatePickerInputProps['uppercase'] + startYear: DatePickerInputProps['startYear'] + endYear: DatePickerInputProps['endYear'] }) => any inputButtons?: any }, ref: any ) { const theme = useTheme() - const { formattedValue, inputFormat, onChangeText, error } = useDateInput({ + const { + formattedValue, + inputFormat, + onChangeText: onDateInputChangeText, + error, + } = useDateInput({ locale, value, validRange, @@ -61,7 +75,8 @@ function DatePickerInputWithoutModal( value={formattedValue} keyboardType={'number-pad'} mask={inputFormat} - onChangeText={onChangeText} + onChangeText={onDateInputChangeText} + onChange={(e) => onChangeText && onChangeText(e.nativeEvent.text)} keyboardAppearance={theme.dark ? 'dark' : 'default'} error={(!!error && !hideValidationErrors) || !!hasError} style={[styles.input, style]} @@ -69,12 +84,22 @@ function DatePickerInputWithoutModal( {inputButtons} {error && !hideValidationErrors ? ( - + {error} ) : null} - {modal?.({ value, locale, inputMode, validRange, saveLabel })} + {modal?.({ + value, + locale, + inputMode, + validRange, + saveLabel, + saveLabelDisabled, + uppercase, + startYear, + endYear, + })} ) } @@ -107,8 +132,5 @@ const styles = StyleSheet.create({ input: { flexGrow: 1, }, - helperText: { - // flex: 1, - }, }) export default React.forwardRef(DatePickerInputWithoutModal) diff --git a/src/TextInputMask.tsx b/src/TextInputMask.tsx index 594db6d7..d415a80f 100644 --- a/src/TextInputMask.tsx +++ b/src/TextInputMask.tsx @@ -69,6 +69,7 @@ function enhanceTextWithMask( function TextInputWithMask( { onChangeText, + onChange, value, mask, ...rest @@ -101,6 +102,9 @@ function TextInputWithMask( {...rest} value={controlledValue} onChangeText={onInnerChange} + onChange={(e) => { + onChange && onChange(e) + }} onBlur={onInnerBlur} /> ) diff --git a/src/__tests__/Date/__snapshots__/AnimatedCrossView.test.tsx.snap b/src/__tests__/Date/__snapshots__/AnimatedCrossView.test.tsx.snap index a6333db1..571a06ad 100644 --- a/src/__tests__/Date/__snapshots__/AnimatedCrossView.test.tsx.snap +++ b/src/__tests__/Date/__snapshots__/AnimatedCrossView.test.tsx.snap @@ -3593,6 +3593,7 @@ exports[`renders collapsed AnimatedCrossView 1`] = ` maxFontSizeMultiplier={1.5} multiline={false} onBlur={[Function]} + onChange={[Function]} onChangeText={[Function]} onFocus={[Function]} onSubmitEditing={[Function]} diff --git a/src/__tests__/Date/__snapshots__/CalendarEdit.test.tsx.snap b/src/__tests__/Date/__snapshots__/CalendarEdit.test.tsx.snap index 964eed14..51b9a6e5 100644 --- a/src/__tests__/Date/__snapshots__/CalendarEdit.test.tsx.snap +++ b/src/__tests__/Date/__snapshots__/CalendarEdit.test.tsx.snap @@ -176,6 +176,7 @@ exports[`renders CalendarEdit 1`] = ` maxFontSizeMultiplier={1.5} multiline={false} onBlur={[Function]} + onChange={[Function]} onChangeText={[Function]} onFocus={[Function]} onSubmitEditing={[Function]} diff --git a/src/__tests__/Date/__snapshots__/DatePickerInput.test.tsx.snap b/src/__tests__/Date/__snapshots__/DatePickerInput.test.tsx.snap index 2c746544..8e8f30b9 100644 --- a/src/__tests__/Date/__snapshots__/DatePickerInput.test.tsx.snap +++ b/src/__tests__/Date/__snapshots__/DatePickerInput.test.tsx.snap @@ -170,6 +170,7 @@ exports[`renders DatePickerInput 1`] = ` maxFontSizeMultiplier={1.5} multiline={false} onBlur={[Function]} + onChange={[Function]} onChangeText={[Function]} onFocus={[Function]} placeholder=" " diff --git a/src/__tests__/Date/__snapshots__/DatePickerInputWithoutModal.test.tsx.snap b/src/__tests__/Date/__snapshots__/DatePickerInputWithoutModal.test.tsx.snap index e3990ba7..e4396842 100644 --- a/src/__tests__/Date/__snapshots__/DatePickerInputWithoutModal.test.tsx.snap +++ b/src/__tests__/Date/__snapshots__/DatePickerInputWithoutModal.test.tsx.snap @@ -169,6 +169,7 @@ exports[`renders DatePickerInput 1`] = ` maxFontSizeMultiplier={1.5} multiline={false} onBlur={[Function]} + onChange={[Function]} onChangeText={[Function]} onFocus={[Function]} placeholder=" " From db065a6904837a8ac17fc3adf4e99ac21a069619 Mon Sep 17 00:00:00 2001 From: Brandon Fitzwater Date: Mon, 2 Jan 2023 18:33:53 -0500 Subject: [PATCH 4/6] Update conditional props logic. --- src/Date/DatePickerInput.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Date/DatePickerInput.tsx b/src/Date/DatePickerInput.tsx index fb2f8082..392e2db6 100644 --- a/src/Date/DatePickerInput.tsx +++ b/src/Date/DatePickerInput.tsx @@ -65,10 +65,10 @@ function DatePickerInput( dateMode={inputMode} validRange={validRange} saveLabel={saveLabel} - saveLabelDisabled={saveLabelDisabled || false} - uppercase={uppercase || true} - startYear={startYear || 1800} - endYear={endYear || 2200} + saveLabelDisabled={saveLabelDisabled ?? false} + uppercase={uppercase ?? true} + startYear={startYear ?? 1800} + endYear={endYear ?? 2200} /> ) : null } From 85e2362fce7ee6c6012a5be12b51986f2112d3ed Mon Sep 17 00:00:00 2001 From: Brandon Fitzwater Date: Tue, 3 Jan 2023 20:38:34 -0500 Subject: [PATCH 5/6] Allow keyboard type to be passed. --- src/Date/DatePickerInputWithoutModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Date/DatePickerInputWithoutModal.tsx b/src/Date/DatePickerInputWithoutModal.tsx index de59df56..3647ec4f 100644 --- a/src/Date/DatePickerInputWithoutModal.tsx +++ b/src/Date/DatePickerInputWithoutModal.tsx @@ -73,7 +73,7 @@ function DatePickerInputWithoutModal( withDateFormatInLabel, })} value={formattedValue} - keyboardType={'number-pad'} + keyboardType={rest.keyboardType ?? 'number-pad'} mask={inputFormat} onChangeText={onDateInputChangeText} onChange={(e) => onChangeText && onChangeText(e.nativeEvent.text)} From d4025bd065aa83a3468fca0369f1babefa5b2c2f Mon Sep 17 00:00:00 2001 From: Brandon Fitzwater Date: Wed, 4 Jan 2023 08:42:22 -0500 Subject: [PATCH 6/6] fix: Empty value is undefined not null. --- src/Date/inputUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Date/inputUtils.ts b/src/Date/inputUtils.ts index 14f39c66..bd11af4e 100644 --- a/src/Date/inputUtils.ts +++ b/src/Date/inputUtils.ts @@ -23,7 +23,7 @@ export default function useDateInput({ const [error, setError] = React.useState(null) const formatter = useInputFormatter({ locale }) const inputFormat = useInputFormat({ formatter, locale }) - const formattedValue = value !== null ? formatter.format(value) : '' + const formattedValue = value ? formatter.format(value) : '' const onChangeText = (date: string) => { const dayIndex = inputFormat.indexOf('DD') const monthIndex = inputFormat.indexOf('MM')