diff --git a/UNRELEASED.md b/UNRELEASED.md index 7168ac76d21..6646cfd3a52 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -22,6 +22,8 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f ### Code quality +- Migrated `DateSelector` to use hooks instead of withAppProvider ([#2193](https://github.com/Shopify/polaris-react/pull/2193)) + ### Deprecations ### Development workflow diff --git a/src/components/ResourceList/components/FilterControl/components/DateSelector/DateSelector.tsx b/src/components/ResourceList/components/FilterControl/components/DateSelector/DateSelector.tsx index c44026586d4..32f587eaebb 100644 --- a/src/components/ResourceList/components/FilterControl/components/DateSelector/DateSelector.tsx +++ b/src/components/ResourceList/components/FilterControl/components/DateSelector/DateSelector.tsx @@ -1,13 +1,10 @@ -import React from 'react'; +import React, {useState, useCallback, useRef, memo} from 'react'; import {CalendarMinor} from '@shopify/polaris-icons'; import {DatePicker, Months, Year, Range} from '../../../../../DatePicker'; import {Select} from '../../../../../Select'; import {TextField} from '../../../../../TextField'; import {Icon} from '../../../../../Icon'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../../../../../utilities/with-app-provider'; +import {useI18n} from '../../../../../../utilities/i18n'; import styles from './DateSelector.scss'; @@ -25,17 +22,6 @@ export interface DateSelectorProps { onFilterKeyChange(filterKey?: string): void; } -interface State { - selectedDate?: Date; - userInputDate?: string; - userInputDateError?: string; - datePickerMonth: Months; - datePickerYear: Year; - initialConsumerFilterKey?: string; -} - -type CombinedProps = DateSelectorProps & WithAppProviderProps; - export enum DateFilterOption { PastWeek = 'past_week', PastMonth = 'past_month', @@ -49,200 +35,300 @@ export enum DateFilterOption { OnOrAfter = 'on_or_after', } -class DateSelector extends React.PureComponent { - state: State = { - datePickerMonth: this.now.getMonth(), - datePickerYear: this.now.getFullYear(), - initialConsumerFilterKey: this.props.filterKey, - }; +// This does have a display name, but the linting has a bug in it +// https://github.com/yannickcr/eslint-plugin-react/issues/2324 +// eslint-disable-next-line react/display-name +export const DateSelector = memo(function DateSelector({ + filterValue, + filterKey, + filterMinKey, + filterMaxKey, + dateOptionType, + onFilterValueChange, + onFilterKeyChange, +}: DateSelectorProps) { + const now = new Date(); + + const i18n = useI18n(); + const initialConsumerFilterKey = useRef(filterKey); + const [datePickerMonth, setDatePickerMonth] = useState(now.getMonth()); + const [datePickerYear, setDatePickerYear] = useState(now.getFullYear()); + const [selectedDate, setSelectedDate] = useState(); + const [userInputDate, setUserInputDate] = useState(); + const [userInputDateError, setUserInputDateError] = useState(); + + const dateTextFieldValue = getDateTextFieldValue(); + + const handleDateFieldChange = useCallback( + (value: string) => { + if (value.length === 0) { + setSelectedDate(undefined); + onFilterValueChange(undefined); + } + + if (userInputDateError && isValidDate(value)) { + setUserInputDateError(undefined); + } + + setUserInputDate(value); + }, + [onFilterValueChange, userInputDateError], + ); + + const handleDateChanged = useCallback( + (date) => { + if (!date) { + return; + } + onFilterValueChange( + stripTimeFromISOString(formatDateForLocalTimezone(date)), + ); + }, + [onFilterValueChange], + ); + + const handleDateBlur = useCallback(() => { + if (!dateTextFieldValue || !isValidDate(dateTextFieldValue)) { + setSelectedDate(undefined); + setUserInputDateError( + i18n.translate('Polaris.ResourceList.DateSelector.dateValueError'), + ); + onFilterValueChange(undefined); - render() { - const { - filterValue, - filterKey, - filterMinKey, - filterMaxKey, - dateOptionType, - polaris: {intl}, - } = this.props; + return; + } - const { - selectedDate, - datePickerMonth, - datePickerYear, - userInputDateError, - } = this.state; - - const dateFilterOption = getDateFilterOption( - filterValue, - filterKey, - filterMinKey, - filterMaxKey, - ); + if (!userInputDate) { + return; + } - const showDatePredicate = - dateFilterOption === DateFilterOption.OnOrBefore || - dateFilterOption === DateFilterOption.OnOrAfter; - - const datePredicateMarkup = showDatePredicate && ( - -
- } - autoComplete={false} - onChange={this.handleDateFieldChange} - onBlur={this.handleDateBlur} - /> -
-
- -
-
+ const formattedDateForTimezone = new Date( + formatDateForLocalTimezone(new Date(userInputDate)), ); - return ( - - + {datePredicateMarkup} + + ); + + function getDateComparatorOptions() { return [ { value: DateFilterOption.OnOrBefore, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.OnOrBefore', ), }, { value: DateFilterOption.OnOrAfter, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.OnOrAfter', ), }, ]; } - private get datePastOptions() { - const { - polaris: {intl}, - } = this.props; - + function getDatePastOptions() { return [ { value: DateFilterOption.PastWeek, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.PastWeek', ), }, { value: DateFilterOption.PastMonth, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.PastMonth', ), }, { value: DateFilterOption.PastQuarter, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.PastQuarter', ), }, { value: DateFilterOption.PastYear, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.PastYear', ), }, ]; } - private get dateFutureOptions() { - const { - polaris: {intl}, - } = this.props; - + function getDateFutureOptions() { return [ { value: DateFilterOption.ComingWeek, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.ComingWeek', ), }, { value: DateFilterOption.ComingMonth, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.ComingMonth', ), }, { value: DateFilterOption.ComingQuarter, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.ComingQuarter', ), }, { value: DateFilterOption.ComingYear, - label: intl.translate( + label: i18n.translate( 'Polaris.ResourceList.DateSelector.SelectOptions.ComingYear', ), }, ]; } - private get dateOptionTypes() { - return { - past: [...this.datePastOptions, ...this.dateComparatorOptions], - future: [...this.dateFutureOptions, ...this.dateComparatorOptions], - full: [ - ...this.datePastOptions, - ...this.dateFutureOptions, - ...this.dateComparatorOptions, - ], - }; - } - - private get now() { - return new Date(); - } - - private get dateTextFieldValue() { - const {userInputDate, selectedDate} = this.state; - + function getDateTextFieldValue() { if (!userInputDate && !selectedDate) { return undefined; } @@ -255,136 +341,7 @@ class DateSelector extends React.PureComponent { return stripTimeFromISOString(formatDateForLocalTimezone(selectedDate)); } } - - private handleDateFilterOptionsChange = (newOption: string) => { - const { - onFilterValueChange, - onFilterKeyChange, - filterMinKey, - filterMaxKey, - } = this.props; - const {initialConsumerFilterKey, selectedDate} = this.state; - - if (!initialConsumerFilterKey) { - return; - } - - if (newOption === DateFilterOption.OnOrBefore) { - onFilterKeyChange(filterMaxKey); - onFilterValueChange( - selectedDate - ? stripTimeFromISOString(formatDateForLocalTimezone(selectedDate)) - : undefined, - ); - return; - } - - if (newOption === DateFilterOption.OnOrAfter) { - onFilterKeyChange(filterMinKey); - onFilterValueChange( - selectedDate - ? stripTimeFromISOString(formatDateForLocalTimezone(selectedDate)) - : undefined, - ); - return; - } - - onFilterKeyChange(initialConsumerFilterKey); - onFilterValueChange(newOption); - }; - - private handleDateFieldChange = (value: string) => { - const {onFilterValueChange} = this.props; - const {userInputDateError} = this.state; - - if (value.length === 0) { - this.setState( - { - selectedDate: undefined, - }, - () => { - onFilterValueChange(undefined); - }, - ); - } - - if (userInputDateError && isValidDate(value)) { - this.setState({ - userInputDateError: undefined, - }); - } - - this.setState({ - userInputDate: value, - }); - }; - - private handleDateBlur = () => { - const { - polaris: {intl}, - onFilterValueChange, - } = this.props; - - if (!this.dateTextFieldValue || !isValidDate(this.dateTextFieldValue)) { - this.setState({ - selectedDate: undefined, - userInputDateError: intl.translate( - 'Polaris.ResourceList.DateSelector.dateValueError', - ), - }); - onFilterValueChange(undefined); - - return; - } - - const {userInputDate} = this.state; - if (!userInputDate) { - return; - } - - const formattedDateForTimezone = new Date( - formatDateForLocalTimezone(new Date(userInputDate)), - ); - - this.setState( - { - selectedDate: formattedDateForTimezone, - datePickerMonth: formattedDateForTimezone.getMonth(), - datePickerYear: formattedDateForTimezone.getFullYear(), - userInputDate: undefined, - userInputDateError: undefined, - }, - this.handleDateChanged, - ); - }; - - private handleDateChanged() { - const {onFilterValueChange} = this.props; - const {selectedDate} = this.state; - - if (!selectedDate) { - return; - } - onFilterValueChange( - stripTimeFromISOString(formatDateForLocalTimezone(selectedDate)), - ); - } - - private handleDatePickerChange = ({end: nextDate}: Range) => { - this.setState( - { - selectedDate: new Date(nextDate), - userInputDate: undefined, - userInputDateError: undefined, - }, - this.handleDateChanged, - ); - }; - - private handleDatePickerMonthChange = (month: Months, year: Year) => { - this.setState({datePickerMonth: month, datePickerYear: year}); - }; -} +}); function isValidDate(date?: string) { if (!date) { @@ -432,7 +389,3 @@ function formatDateForLocalTimezone(date: Date) { formattedDate.setTime(newTime); return formattedDate.toISOString(); } - -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()(DateSelector); diff --git a/src/components/ResourceList/components/FilterControl/components/DateSelector/index.ts b/src/components/ResourceList/components/FilterControl/components/DateSelector/index.ts index 26675fde00b..b1b80a94a05 100644 --- a/src/components/ResourceList/components/FilterControl/components/DateSelector/index.ts +++ b/src/components/ResourceList/components/FilterControl/components/DateSelector/index.ts @@ -1,6 +1,5 @@ -import DateSelector, { +export { + DateSelector, DateSelectorProps, DateFilterOption, } from './DateSelector'; - -export {DateSelector, DateSelectorProps, DateFilterOption}; diff --git a/src/components/ResourceList/components/FilterControl/components/DateSelector/tests/DateSelector.test.tsx b/src/components/ResourceList/components/FilterControl/components/DateSelector/tests/DateSelector.test.tsx index 1f9f9072f92..dcf972c465f 100644 --- a/src/components/ResourceList/components/FilterControl/components/DateSelector/tests/DateSelector.test.tsx +++ b/src/components/ResourceList/components/FilterControl/components/DateSelector/tests/DateSelector.test.tsx @@ -1,7 +1,8 @@ import React from 'react'; import {DatePicker, Select, TextField} from 'components'; import {trigger, mountWithAppProvider} from 'test-utilities/legacy'; -import DateSelector, { +import { + DateSelector, DateSelectorProps, DateFilterOption, } from '../DateSelector';