From be7fccc030cd3d0e753807e5e7552ac6efa6a743 Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Tue, 18 Apr 2023 15:14:03 -0700 Subject: [PATCH 1/8] Adding error handling and an example of how to use it --- .../etc/react-datepicker-compat.api.md | 11 +++++- .../react-datepicker-compat/package.json | 1 + .../components/DatePicker/DatePicker.types.ts | 9 +++++ .../src/components/DatePicker/defaults.ts | 15 ++++--- .../components/DatePicker/useDatePicker.tsx | 39 +++++++++++++------ .../react-datepicker-compat/src/index.ts | 6 ++- .../DatePickerErrorHandling.stories.tsx | 39 +++++++++++++++++++ .../stories/DatePicker/index.stories.tsx | 1 + 8 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 packages/react-components/react-datepicker-compat/stories/DatePicker/DatePickerErrorHandling.stories.tsx diff --git a/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md b/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md index 1fb0c3c435cc9..d98886a92eba8 100644 --- a/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md +++ b/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md @@ -152,6 +152,11 @@ export const DatePicker: ForwardRefComponent; // @public (undocumented) export const datePickerClassNames: SlotClassNames; +// @public (undocumented) +export type DatePickerErrorData = { + error: 'required-input' | 'invalid-input' | 'out-of-bounds'; +}; + // @public (undocumented) export type DatePickerProps = Omit>, 'defaultValue' | 'value'> & { componentRef?: React_2.RefObject; @@ -167,6 +172,7 @@ export type DatePickerProps = Omit>, 'de defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; + onValidationError?: (date: DatePickerErrorData) => void; inlinePopup?: boolean; positioning?: PositioningProps; placeholder?: string; @@ -224,7 +230,10 @@ export enum DayOfWeek { export const DAYS_IN_WEEK = 7; // @public (undocumented) -export const defaultCalendarStrings: CalendarStrings; +export const defaultDatePickerErrorStrings: Record; + +// @public (undocumented) +export const defaultDatePickerStrings: CalendarStrings; // @public export enum FirstWeekOfYear { diff --git a/packages/react-components/react-datepicker-compat/package.json b/packages/react-components/react-datepicker-compat/package.json index dcad0c1b4dc09..5818242edf710 100644 --- a/packages/react-components/react-datepicker-compat/package.json +++ b/packages/react-components/react-datepicker-compat/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@fluentui/keyboard-keys": "^9.0.2", + "@fluentui/react-field": "^9.0.0", "@fluentui/react-icons": "^2.0.196", "@fluentui/react-input": "^9.4.10", "@fluentui/react-popover": "^9.5.9", diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts index d4aea2095fdf2..458fc5e9d847f 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts @@ -104,6 +104,11 @@ export type DatePickerProps = Omit>, 'de */ onOpenChange?: (open: boolean) => void; + /** + * Callback to run when the DatePicker encounters an error when validating the input + */ + onValidationError?: (date: DatePickerErrorData) => void; + /** * Whether the DatePicker should render the popup as inline or in a portal * @@ -229,6 +234,10 @@ export type DatePickerState = ComponentState & { inlinePopup: boolean; }; +export type DatePickerErrorData = { + error: 'required-input' | 'invalid-input' | 'out-of-bounds'; +}; + // TODO: remove this once we add error handling hook export interface DatePickerStrings extends CalendarStrings { /** diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts index 73c0431657052..d256d98b6ef24 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts @@ -1,15 +1,18 @@ +import { CalendarStrings } from '../../utils/index'; import { defaultCalendarStrings } from '../Calendar/defaults'; -import type { DatePickerStrings } from './DatePicker.types'; +import { DatePickerErrorData } from './DatePicker.types'; -// TODO: Once we have error handling hook, this needs to be either renamed or removed. -export const defaultDatePickerStrings: DatePickerStrings = { +export const defaultDatePickerStrings: CalendarStrings = { ...defaultCalendarStrings, prevMonthAriaLabel: 'Go to previous month', nextMonthAriaLabel: 'Go to next month', prevYearAriaLabel: 'Go to previous year', nextYearAriaLabel: 'Go to next year', closeButtonAriaLabel: 'Close date picker', - isRequiredErrorMessage: 'Field is required', - invalidInputErrorMessage: 'Invalid date format', - isResetStatusMessage: 'Invalid entry "{0}", date reset to "{1}"', +}; + +export const defaultDatePickerErrorStrings: Record = { + 'required-input': 'Field is required', + 'invalid-input': 'Invalid date format', + 'out-of-bounds': 'Date is out of bounds', }; diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx index 3cfb1323308d2..835fbc3ab37bb 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx @@ -1,8 +1,10 @@ import * as React from 'react'; import { ArrowDown, Enter, Escape } from '@fluentui/keyboard-keys'; +import { Calendar } from '../Calendar/Calendar'; import { CalendarMonthRegular } from '@fluentui/react-icons'; +import { compareDatePart, DayOfWeek, FirstWeekOfYear } from '../../utils'; +import { defaultDatePickerStrings } from './defaults'; import { Input } from '@fluentui/react-input'; -import { useFocusFinders, useModalAttributes } from '@fluentui/react-tabster'; import { mergeCallbacks, resolveShorthand, @@ -13,14 +15,13 @@ import { useOnClickOutside, useOnScrollOutside, } from '@fluentui/react-utilities'; -import { compareDatePart, DayOfWeek, FirstWeekOfYear } from '../../utils'; -import { Calendar } from '../Calendar/Calendar'; -import { usePopupPositioning } from '../../utils/usePopupPositioning'; +import { useFieldContext_unstable as useFieldContext } from '@fluentui/react-field'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; -import type { InputProps, InputOnChangeData } from '@fluentui/react-input'; +import { useFocusFinders, useModalAttributes } from '@fluentui/react-tabster'; +import { usePopupPositioning } from '../../utils/usePopupPositioning'; import type { CalendarProps, ICalendar } from '../Calendar/Calendar.types'; import type { DatePickerProps, DatePickerState } from './DatePicker.types'; -import { defaultCalendarStrings } from '../Calendar/defaults'; +import type { InputProps, InputOnChangeData } from '@fluentui/react-input'; function isDateOutOfBounds(date: Date, minDate?: Date, maxDate?: Date): boolean { return (!!minDate && compareDatePart(minDate!, date) > 0) || (!!maxDate && compareDatePart(maxDate!, date) < 0); @@ -128,12 +129,13 @@ export const useDatePicker_unstable = (props: DatePickerProps, ref: React.Ref { + const styles = useStyles(); + const [error, setError] = React.useState(undefined); + + return ( + + setError(data.error)} + className={styles.control} + /> + + ); +}; diff --git a/packages/react-components/react-datepicker-compat/stories/DatePicker/index.stories.tsx b/packages/react-components/react-datepicker-compat/stories/DatePicker/index.stories.tsx index 3215f6149101d..d19305e309b52 100644 --- a/packages/react-components/react-datepicker-compat/stories/DatePicker/index.stories.tsx +++ b/packages/react-components/react-datepicker-compat/stories/DatePicker/index.stories.tsx @@ -9,6 +9,7 @@ export { FirstDayOfTheWeek } from './DatePickerFirstDayOfTheWeek.stories'; export { WeekNumbers } from './DatePickerWeekNumbers.stories'; export { DateBoundaries } from './DatePickerDateBoundaries.stories'; export { CustomDateFormatting } from './DatePickerCustomDateFormatting.stories'; +export { ErrorHandling } from './DatePickerErrorHandling.stories'; export { Controlled } from './DatePickerControlled.stories'; export { Required } from './DatePickerRequired.stories'; export { Disabled } from './DatePickerDisabled.stories'; From 9a88167a34ac8862185a7571f6022e6dc282eb31 Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Tue, 18 Apr 2023 15:15:14 -0700 Subject: [PATCH 2/8] change files --- ...picker-compat-59a888f8-093f-449c-b49e-4f580894da5e.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-datepicker-compat-59a888f8-093f-449c-b49e-4f580894da5e.json diff --git a/change/@fluentui-react-datepicker-compat-59a888f8-093f-449c-b49e-4f580894da5e.json b/change/@fluentui-react-datepicker-compat-59a888f8-093f-449c-b49e-4f580894da5e.json new file mode 100644 index 0000000000000..162abc60d4804 --- /dev/null +++ b/change/@fluentui-react-datepicker-compat-59a888f8-093f-449c-b49e-4f580894da5e.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat: Add error handling to DatePicker.", + "packageName": "@fluentui/react-datepicker-compat", + "email": "esteban.230@hotmail.com", + "dependentChangeType": "patch" +} From 04e983fa6dab95c5984a98bd22a3ac6acd4c14c4 Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Tue, 18 Apr 2023 15:20:13 -0700 Subject: [PATCH 3/8] reordering values --- .../src/components/DatePicker/DatePicker.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts index 458fc5e9d847f..f4db2da77d056 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts @@ -235,7 +235,7 @@ export type DatePickerState = ComponentState & { }; export type DatePickerErrorData = { - error: 'required-input' | 'invalid-input' | 'out-of-bounds'; + error: 'invalid-input' | 'out-of-bounds' | 'required-input'; }; // TODO: remove this once we add error handling hook From 3bcdc9a4f4494fe6a32777b52460cee95f77fc5b Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Tue, 18 Apr 2023 15:23:10 -0700 Subject: [PATCH 4/8] updating api --- .../etc/react-datepicker-compat.api.md | 2 +- .../src/components/DatePicker/defaults.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md b/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md index d98886a92eba8..a856031639411 100644 --- a/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md +++ b/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md @@ -154,7 +154,7 @@ export const datePickerClassNames: SlotClassNames; // @public (undocumented) export type DatePickerErrorData = { - error: 'required-input' | 'invalid-input' | 'out-of-bounds'; + error: 'invalid-input' | 'out-of-bounds' | 'required-input'; }; // @public (undocumented) diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts index d256d98b6ef24..3dcdb1fbb04c2 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts @@ -1,6 +1,6 @@ import { CalendarStrings } from '../../utils/index'; import { defaultCalendarStrings } from '../Calendar/defaults'; -import { DatePickerErrorData } from './DatePicker.types'; +import type { DatePickerErrorData } from './DatePicker.types'; export const defaultDatePickerStrings: CalendarStrings = { ...defaultCalendarStrings, @@ -12,7 +12,7 @@ export const defaultDatePickerStrings: CalendarStrings = { }; export const defaultDatePickerErrorStrings: Record = { - 'required-input': 'Field is required', 'invalid-input': 'Invalid date format', 'out-of-bounds': 'Date is out of bounds', + 'required-input': 'Field is required', }; From f3bb8a213578c295bb0e18654b001ef933d94e26 Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Tue, 18 Apr 2023 15:29:52 -0700 Subject: [PATCH 5/8] syncpack --- packages/react-components/react-datepicker-compat/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-datepicker-compat/package.json b/packages/react-components/react-datepicker-compat/package.json index 2e4f8c43b78f8..8ff9db76cec40 100644 --- a/packages/react-components/react-datepicker-compat/package.json +++ b/packages/react-components/react-datepicker-compat/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@fluentui/keyboard-keys": "^9.0.2", - "@fluentui/react-field": "^9.0.0", + "@fluentui/react-field": "^9.1.0", "@fluentui/react-icons": "^2.0.196", "@fluentui/react-input": "^9.4.10", "@fluentui/react-jsx-runtime": "9.0.0-alpha.1", From 9887545f3427f761d2e63b5c48ca96802e7fb451 Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Tue, 18 Apr 2023 15:44:29 -0700 Subject: [PATCH 6/8] docs --- .../etc/react-datepicker-compat.api.md | 4 +-- .../components/DatePicker/DatePicker.types.ts | 34 +++++-------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md b/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md index a856031639411..d6ad592e1bb4d 100644 --- a/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md +++ b/packages/react-components/react-datepicker-compat/etc/react-datepicker-compat.api.md @@ -152,7 +152,7 @@ export const DatePicker: ForwardRefComponent; // @public (undocumented) export const datePickerClassNames: SlotClassNames; -// @public (undocumented) +// @public export type DatePickerErrorData = { error: 'invalid-input' | 'out-of-bounds' | 'required-input'; }; @@ -172,7 +172,7 @@ export type DatePickerProps = Omit>, 'de defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; - onValidationError?: (date: DatePickerErrorData) => void; + onValidationError?: (data: DatePickerErrorData) => void; inlinePopup?: boolean; positioning?: PositioningProps; placeholder?: string; diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts index f4db2da77d056..950ec49fa96b1 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts @@ -107,7 +107,7 @@ export type DatePickerProps = Omit>, 'de /** * Callback to run when the DatePicker encounters an error when validating the input */ - onValidationError?: (date: DatePickerErrorData) => void; + onValidationError?: (data: DatePickerErrorData) => void; /** * Whether the DatePicker should render the popup as inline or in a portal @@ -229,36 +229,18 @@ export type DatePickerProps = Omit>, 'de showCloseButton?: boolean; }; +/** + * State used in rendering DatePicker. + */ export type DatePickerState = ComponentState & { disabled: boolean; inlinePopup: boolean; }; +/** + * Data passed to the `onValidationError` callback. + */ export type DatePickerErrorData = { + /** The error found when validating the input. */ error: 'invalid-input' | 'out-of-bounds' | 'required-input'; }; - -// TODO: remove this once we add error handling hook -export interface DatePickerStrings extends CalendarStrings { - /** - * Error message to render for Input if isRequired validation fails. - */ - isRequiredErrorMessage?: string; - - /** - * Error message to render for Input if input date string parsing fails. - */ - invalidInputErrorMessage?: string; - - /** - * Error message to render for Input if date boundary (minDate, maxDate) validation fails. - */ - isOutOfBoundsErrorMessage?: string; - - /** - * Status message to render for Input the input date parsing fails, - * and the typed value is cleared and reset to the previous value. - * e.g. "Invalid entry `{0}`, date reset to `{1}`" - */ - isResetStatusMessage?: string; -} From 4828faf93110fe5adbecbd2bbfc3e0f62d6d295d Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Wed, 19 Apr 2023 13:52:49 -0700 Subject: [PATCH 7/8] remove padding --- .../src/components/DatePicker/useDatePickerStyles.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePickerStyles.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePickerStyles.ts index adda8b1fbbcec..c2baf78218c82 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePickerStyles.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePickerStyles.ts @@ -34,7 +34,6 @@ const usePopupSurfaceClassName = makeResetStyles({ borderColor: tokens.colorTransparentStroke, display: 'inline-flex', color: tokens.colorNeutralForeground1, - padding: '16px', ...typographyStyles.body1, }); From cc9f4f2d265e330f335d87f6634b23a857d398c1 Mon Sep 17 00:00:00 2001 From: Esteban Munoz Date: Wed, 19 Apr 2023 20:03:48 -0700 Subject: [PATCH 8/8] requested changes --- .../src/components/DatePicker/defaults.ts | 2 +- .../src/components/DatePicker/useDatePicker.tsx | 2 +- .../stories/DatePicker/DatePickerErrorHandling.stories.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts index 3dcdb1fbb04c2..6906ad13d407c 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/defaults.ts @@ -1,5 +1,5 @@ -import { CalendarStrings } from '../../utils/index'; import { defaultCalendarStrings } from '../Calendar/defaults'; +import type { CalendarStrings } from '../../utils/index'; import type { DatePickerErrorData } from './DatePicker.types'; export const defaultDatePickerStrings: CalendarStrings = { diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx index 835fbc3ab37bb..09d3846e29fa0 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx @@ -165,7 +165,7 @@ export const useDatePicker_unstable = (props: DatePickerProps, ref: React.Ref