From 600a9e6afffd335676ab637af2298111b0b28af8 Mon Sep 17 00:00:00 2001 From: Raman-Luhach Date: Sun, 28 Jul 2024 19:59:49 +0530 Subject: [PATCH 1/7] refactor DatetimeWidget --- packages/volto/news/6213.feature | 2 + .../manage/Widgets/DatetimeWidget.jsx | 301 +++++++++--------- 2 files changed, 145 insertions(+), 158 deletions(-) create mode 100644 packages/volto/news/6213.feature diff --git a/packages/volto/news/6213.feature b/packages/volto/news/6213.feature new file mode 100644 index 0000000000..76fbceecdc --- /dev/null +++ b/packages/volto/news/6213.feature @@ -0,0 +1,2 @@ + +Refactor the `DatetimeWidget` component from a class component to a functional component. @Raman-Luhach diff --git a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx index 546f57e22a..356463c1d1 100644 --- a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx @@ -2,7 +2,7 @@ * DatetimeWidget component. * @module components/manage/Widgets/DatetimeWidget */ -import React, { Component } from 'react'; +import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { compose } from 'redux'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; @@ -50,6 +50,7 @@ const PrevIcon = () => ( ); + const NextIcon = () => (
{ + const moment = momentLib.default; - this.state = { - focused: false, - // if passed value matches the construction time, we guess it's a default - isDefault: - parseDateTime( - toBackendLang(this.props.lang), - this.props.value, - undefined, - this.moment, - )?.toISOString() === this.moment().utc().toISOString(), - }; - } + const initialInternalValue = useMemo(() => { + return parseDateTime(toBackendLang(lang), value, undefined, moment); + }, [lang, value, moment]); - getInternalValue() { - return parseDateTime( - toBackendLang(this.props.lang), - this.props.value, - undefined, - this.moment, - ); - } + const [focused, setFocused] = useState(false); + const [isDefault, setIsDefault] = useState( + initialInternalValue?.toISOString() === moment().utc().toISOString(), + ); + + const getInternalValue = useCallback(() => { + return parseDateTime(toBackendLang(lang), value, undefined, moment); + }, [lang, value, moment]); - getDateOnly() { - return this.props.dateOnly || this.props.widget === 'date'; - } + const getDateOnly = useCallback(() => { + return dateOnly || props.widget === 'date'; + }, [dateOnly, props.widget]); - /** - * Update date storage - * @method onDateChange - * @param {Object} date updated momentjs Object for date - * @returns {undefined} - */ - onDateChange = (date) => { - if (date) { - const moment = this.props.moment.default; - const isDateOnly = this.getDateOnly(); - const base = (this.getInternalValue() || moment()).set({ - year: date.year(), - month: date.month(), - date: date.date(), - ...(isDateOnly ? defaultTimeDateOnly : {}), - }); - const dateValue = isDateOnly - ? base.format('YYYY-MM-DD') - : base.toISOString(); - this.props.onChange(this.props.id, dateValue); - } - this.setState({ isDefault: false }); - }; + const handleDateChange = useCallback( + (date) => { + if (date) { + const base = (getInternalValue() || moment()).set({ + year: date.year(), + month: date.month(), + date: date.date(), + ...(getDateOnly() ? defaultTimeDateOnly : {}), + }); + const dateValue = getDateOnly() + ? base.format('YYYY-MM-DD') + : base.toISOString(); + onChange(id, dateValue); + } + setIsDefault(false); + }, + [getInternalValue, getDateOnly, onChange, id, moment], + ); - /** - * Update date storage - * @method onTimeChange - * @param {Object} time updated momentjs Object for time - * @returns {undefined} - */ - onTimeChange = (time) => { - const moment = this.props.moment.default; - if (time) { - const base = (this.getInternalValue() || moment()).set({ - hours: time?.hours() ?? 0, - minutes: time?.minutes() ?? 0, - seconds: 0, - }); - const dateValue = base.toISOString(); - this.props.onChange(this.props.id, dateValue); - } - }; + const handleTimeChange = useCallback( + (time) => { + if (time) { + const base = (getInternalValue() || moment()).set({ + hours: time?.hours() ?? 0, + minutes: time?.minutes() ?? 0, + seconds: 0, + }); + const dateValue = base.toISOString(); + onChange(id, dateValue); + } + }, + [getInternalValue, onChange, id, moment], + ); - onResetDates = () => { - this.setState({ isDefault: false }); - this.props.onChange(this.props.id, null); - }; + const handleResetDates = useCallback(() => { + setIsDefault(false); + onChange(id, null); + }, [onChange, id]); - /** - * Handle SingleDatePicker focus - * @method onFocusChange - * @param {boolean} focused component focus state. - * @returns {undefined} - */ - onFocusChange = ({ focused }) => this.setState({ focused }); + const handleFocusChange = useCallback(({ focused }) => { + setFocused(focused); + }, []); + + useEffect(() => { + setIsDefault( + initialInternalValue?.toISOString() === moment().utc().toISOString(), + ); + }, [initialInternalValue, moment]); - render() { - const { id, resettable, intl, reactDates, widgetOptions, lang } = - this.props; - const noPastDates = - this.props.noPastDates || widgetOptions?.pattern_options?.noPastDates; - const moment = this.props.moment.default; - const datetime = this.getInternalValue(); - const dateOnly = this.getDateOnly(); - const { SingleDatePicker } = reactDates; + const { SingleDatePicker } = reactDates; + const datetime = getInternalValue(); + const dateOnlyValue = getDateOnly(); + const noPastDatesValue = + noPastDates || widgetOptions?.pattern_options?.noPastDates; - return ( - -
+ return ( + +
+
+ false })} + onFocusChange={handleFocusChange} + noBorder + displayFormat={moment + .localeData(toBackendLang(lang)) + .longDateFormat('L')} + navPrev={} + navNext={} + id={`${id}-date`} + placeholder={intl.formatMessage(messages.date)} + /> +
+ {!dateOnlyValue && (
- false })} - onFocusChange={this.onFocusChange} - noBorder - displayFormat={moment + } - navNext={} - id={`${id}-date`} - placeholder={intl.formatMessage(messages.date)} + .longDateFormat('LT')} + placeholder={intl.formatMessage(messages.time)} + focusOnOpen + placement="bottomRight" />
- {!dateOnly && ( -
- -
- )} - {resettable && ( - - )} -
-
- ); - } -} + )} + {resettable && ( + + )} +
+
+ ); +}; /** * Property types. From cde430e0728b8592a8f2bf7f589bf1f9e1f26ad1 Mon Sep 17 00:00:00 2001 From: Raman-Luhach Date: Sun, 28 Jul 2024 23:55:44 +0530 Subject: [PATCH 2/7] changes --- .../manage/Widgets/DatetimeWidget.jsx | 198 ++++++++---------- 1 file changed, 82 insertions(+), 116 deletions(-) diff --git a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx index 356463c1d1..848c09d3d6 100644 --- a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx @@ -1,8 +1,4 @@ -/** - * DatetimeWidget component. - * @module components/manage/Widgets/DatetimeWidget - */ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { compose } from 'redux'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; @@ -73,107 +69,88 @@ const defaultTimeDateOnly = { second: 0, }; -/** - * DatetimeWidget component function - * @function DatetimeWidgetComponent - * - * To use it, in schema properties, declare a field like: - * - * ```jsx - * { - * title: "Publish date", - * type: 'datetime', - * } - * ``` - */ -const DatetimeWidgetComponent = ({ - id, - resettable, - intl, - reactDates, - widgetOptions, - lang, - value, - onChange, - dateOnly, - noPastDates, - isDisabled, - moment: momentLib, - ...props -}) => { - const moment = momentLib.default; - - const initialInternalValue = useMemo(() => { - return parseDateTime(toBackendLang(lang), value, undefined, moment); - }, [lang, value, moment]); +const DatetimeWidgetComponent = (props) => { + const { + id, + resettable, + intl, + reactDates, + widgetOptions, + lang, + moment, + value, + onChange, + dateOnly, + widget, + noPastDates: propNoPastDates, + isDisabled, + } = props; const [focused, setFocused] = useState(false); - const [isDefault, setIsDefault] = useState( - initialInternalValue?.toISOString() === moment().utc().toISOString(), - ); - - const getInternalValue = useCallback(() => { - return parseDateTime(toBackendLang(lang), value, undefined, moment); - }, [lang, value, moment]); - - const getDateOnly = useCallback(() => { - return dateOnly || props.widget === 'date'; - }, [dateOnly, props.widget]); - - const handleDateChange = useCallback( - (date) => { - if (date) { - const base = (getInternalValue() || moment()).set({ - year: date.year(), - month: date.month(), - date: date.date(), - ...(getDateOnly() ? defaultTimeDateOnly : {}), - }); - const dateValue = getDateOnly() - ? base.format('YYYY-MM-DD') - : base.toISOString(); - onChange(id, dateValue); - } - setIsDefault(false); - }, - [getInternalValue, getDateOnly, onChange, id, moment], - ); - - const handleTimeChange = useCallback( - (time) => { - if (time) { - const base = (getInternalValue() || moment()).set({ - hours: time?.hours() ?? 0, - minutes: time?.minutes() ?? 0, - seconds: 0, - }); - const dateValue = base.toISOString(); - onChange(id, dateValue); - } - }, - [getInternalValue, onChange, id, moment], - ); - - const handleResetDates = useCallback(() => { - setIsDefault(false); - onChange(id, null); - }, [onChange, id]); + const [isDefault, setIsDefault] = useState(false); - const handleFocusChange = useCallback(({ focused }) => { - setFocused(focused); - }, []); + const { SingleDatePicker } = reactDates; useEffect(() => { + const parsedDateTime = parseDateTime( + toBackendLang(lang), + value, + undefined, + moment.default, + ); setIsDefault( - initialInternalValue?.toISOString() === moment().utc().toISOString(), + parsedDateTime?.toISOString() === moment.default().utc().toISOString(), ); - }, [initialInternalValue, moment]); + }, [value, lang, moment]); + + const getInternalValue = () => { + return parseDateTime(toBackendLang(lang), value, undefined, moment.default); + }; + + const getDateOnly = () => { + return dateOnly || widget === 'date'; + }; + + const onDateChange = (date) => { + if (date) { + const isDateOnly = getDateOnly(); + const base = (getInternalValue() || moment.default()).set({ + year: date.year(), + month: date.month(), + date: date.date(), + ...(isDateOnly ? defaultTimeDateOnly : {}), + }); + const dateValue = isDateOnly + ? base.format('YYYY-MM-DD') + : base.toISOString(); + onChange(id, dateValue); + } + setIsDefault(false); + }; + + const onTimeChange = (time) => { + if (time) { + const base = (getInternalValue() || moment.default()).set({ + hours: time?.hours() ?? 0, + minutes: time?.minutes() ?? 0, + seconds: 0, + }); + const dateValue = base.toISOString(); + onChange(id, dateValue); + } + }; + + const onResetDates = () => { + setIsDefault(false); + onChange(id, null); + }; - const { SingleDatePicker } = reactDates; + const onFocusChange = ({ focused }) => setFocused(focused); + + const noPastDates = + propNoPastDates || widgetOptions?.pattern_options?.noPastDates; const datetime = getInternalValue(); - const dateOnlyValue = getDateOnly(); - const noPastDatesValue = - noPastDates || widgetOptions?.pattern_options?.noPastDates; + const isDateOnly = getDateOnly(); return ( @@ -186,13 +163,13 @@ const DatetimeWidgetComponent = ({ false })} - onFocusChange={handleFocusChange} + {...(noPastDates ? {} : { isOutsideRange: () => false })} + onFocusChange={onFocusChange} noBorder - displayFormat={moment + displayFormat={moment.default .localeData(toBackendLang(lang)) .longDateFormat('L')} navPrev={} @@ -201,7 +178,7 @@ const DatetimeWidgetComponent = ({ placeholder={intl.formatMessage(messages.date)} />
- {!dateOnlyValue && ( + {!isDateOnly && (
@@ -241,11 +217,6 @@ const DatetimeWidgetComponent = ({ ); }; -/** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ DatetimeWidgetComponent.propTypes = { id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, @@ -260,11 +231,6 @@ DatetimeWidgetComponent.propTypes = { resettable: PropTypes.bool, }; -/** - * Default properties. - * @property {Object} defaultProps Default properties. - * @static - */ DatetimeWidgetComponent.defaultProps = { description: null, required: false, From fdbc318047f72bba8d878e12c7fa1cdabe9de2f8 Mon Sep 17 00:00:00 2001 From: Raman-Luhach Date: Thu, 22 Aug 2024 01:25:13 +0530 Subject: [PATCH 3/7] useIntl used --- .../volto/src/components/manage/Widgets/DatetimeWidget.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx index 848c09d3d6..dc605b3d91 100644 --- a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { compose } from 'redux'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; import { connect } from 'react-redux'; import loadable from '@loadable/component'; import cx from 'classnames'; @@ -73,7 +73,6 @@ const DatetimeWidgetComponent = (props) => { const { id, resettable, - intl, reactDates, widgetOptions, lang, @@ -86,6 +85,8 @@ const DatetimeWidgetComponent = (props) => { isDisabled, } = props; + const intl = useIntl(); + const [focused, setFocused] = useState(false); const [isDefault, setIsDefault] = useState(false); @@ -246,5 +247,4 @@ export default compose( connect((state) => ({ lang: state.intl.locale, })), - injectIntl, )(DatetimeWidgetComponent); From 15c5f2c3a3bf4db336b8c21a5de651f29e2432d2 Mon Sep 17 00:00:00 2001 From: Raman-Luhach <150381737+Raman-Luhach@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:01:25 +0530 Subject: [PATCH 4/7] Update packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx Co-authored-by: David Glick --- packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx index dc605b3d91..6991defbbf 100644 --- a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx @@ -75,7 +75,6 @@ const DatetimeWidgetComponent = (props) => { resettable, reactDates, widgetOptions, - lang, moment, value, onChange, From 538493c3795dd22f9e559e9f98defb5fb88a1f3b Mon Sep 17 00:00:00 2001 From: Raman-Luhach <150381737+Raman-Luhach@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:01:32 +0530 Subject: [PATCH 5/7] Update packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx Co-authored-by: David Glick --- packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx index 6991defbbf..d0eb282776 100644 --- a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx @@ -85,6 +85,7 @@ const DatetimeWidgetComponent = (props) => { } = props; const intl = useIntl(); + const lang = intl.locale; const [focused, setFocused] = useState(false); const [isDefault, setIsDefault] = useState(false); From 99644bb6f79601cd248c029b6eb42cdbb893a405 Mon Sep 17 00:00:00 2001 From: Raman-Luhach <150381737+Raman-Luhach@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:02:56 +0530 Subject: [PATCH 6/7] Update DatetimeWidget.jsx --- .../volto/src/components/manage/Widgets/DatetimeWidget.jsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx index d0eb282776..90df9a989d 100644 --- a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx @@ -242,9 +242,4 @@ DatetimeWidgetComponent.defaultProps = { resettable: true, }; -export default compose( - injectLazyLibs(['reactDates', 'moment']), - connect((state) => ({ - lang: state.intl.locale, - })), -)(DatetimeWidgetComponent); +export default injectLazyLibs(['reactDates', 'moment'])(DatetimeWidgetComponent); From 77b6ff42a98426e439f794da4b4f5cdb6f9c5ed4 Mon Sep 17 00:00:00 2001 From: Raman-Luhach Date: Tue, 3 Sep 2024 12:07:44 +0530 Subject: [PATCH 7/7] Updated --- .../volto/src/components/manage/Widgets/DatetimeWidget.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx index 90df9a989d..bfa4cfe6fb 100644 --- a/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/DatetimeWidget.jsx @@ -1,8 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { compose } from 'redux'; import PropTypes from 'prop-types'; import { defineMessages, useIntl } from 'react-intl'; -import { connect } from 'react-redux'; import loadable from '@loadable/component'; import cx from 'classnames'; import { Icon } from '@plone/volto/components'; @@ -242,4 +240,6 @@ DatetimeWidgetComponent.defaultProps = { resettable: true, }; -export default injectLazyLibs(['reactDates', 'moment'])(DatetimeWidgetComponent); +export default injectLazyLibs(['reactDates', 'moment'])( + DatetimeWidgetComponent, +);