diff --git a/components/calendar/package.json b/components/calendar/package.json index dc6cdc5e97..fc94c283f2 100644 --- a/components/calendar/package.json +++ b/components/calendar/package.json @@ -38,7 +38,7 @@ "@dhis2-ui/input": "9.9.0-alpha.1", "@dhis2-ui/layer": "9.9.0-alpha.1", "@dhis2-ui/popper": "9.9.0-alpha.1", - "@dhis2/multi-calendar-dates": "^1.1.1", + "@dhis2/multi-calendar-dates": "v1.0.0-alpha.26", "@dhis2/prop-types": "^3.1.2", "@dhis2/ui-constants": "9.9.0-alpha.1", "@dhis2/ui-icons": "9.9.0-alpha.1", diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js index a2753110b2..c670653b9c 100644 --- a/components/calendar/src/calendar-input/calendar-input.js +++ b/components/calendar/src/calendar-input/calendar-input.js @@ -1,11 +1,16 @@ import { Button } from '@dhis2-ui/button' import { Card } from '@dhis2-ui/card' -import { InputField, InputFieldProps } from '@dhis2-ui/input' +import { InputField } from '@dhis2-ui/input' import { Layer } from '@dhis2-ui/layer' import { Popper } from '@dhis2-ui/popper' +import { + useDatePicker, + useResolvedDirection, +} from '@dhis2/multi-calendar-dates' import cx from 'classnames' -import React, { useRef, useState } from 'react' -import { Calendar, CalendarProps } from '../calendar/calendar.js' +import React, { useRef, useState, useMemo } from 'react' +import { CalendarContainer } from '../calendar/calendar-container.js' +import { CalendarProps } from '../calendar/calendar.js' import i18n from '../locales/index.js' const offsetModifier = { @@ -16,7 +21,7 @@ const offsetModifier = { } export const CalendarInput = ({ - onDateSelect, + onDateSelect: parentOnDateSelect, calendar, date, dir, @@ -27,45 +32,67 @@ export const CalendarInput = ({ width, cellSize, clearable, + minDate, + maxDate, + format, // todo: props and types for format and validation + strictValidation, ...rest } = {}) => { const ref = useRef() const [open, setOpen] = useState(false) - const calendarProps = React.useMemo(() => { - const onDateSelectWrapper = (selectedDate) => { - setOpen(false) - onDateSelect?.(selectedDate) - } - return { - onDateSelect: onDateSelectWrapper, + const useDatePickerOptions = useMemo( + () => ({ calendar, - date, - dir, locale, + timeZone, // todo: we probably shouldn't have had timezone here in the first place numberingSystem, weekDayFormat, - timeZone, - width, - cellSize, - } - }, [ - calendar, - cellSize, + }), + [calendar, locale, numberingSystem, timeZone, weekDayFormat] + ) + + const pickerResults = useDatePicker({ + onDateSelect: (result) => { + setOpen(false) + parentOnDateSelect?.(result) + }, date, - dir, - locale, - numberingSystem, - onDateSelect, - timeZone, - weekDayFormat, - width, - ]) + minDate: minDate, + maxDate: maxDate, + strictValidation: strictValidation, + format: format, + options: useDatePickerOptions, + }) + + const handleChange = (e) => { + parentOnDateSelect?.({ calendarDateString: e.value }) + } const onFocus = () => { setOpen(true) } + const languageDirection = useResolvedDirection(dir, locale) + + const calendarProps = useMemo(() => { + return { + date, + width, + cellSize, + isValid: pickerResults.isValid, + calendarWeekDays: pickerResults.calendarWeekDays, + weekDayLabels: pickerResults.weekDayLabels, + currMonth: pickerResults.currMonth, + currYear: pickerResults.currYear, + nextMonth: pickerResults.nextMonth, + nextYear: pickerResults.nextYear, + prevMonth: pickerResults.prevMonth, + prevYear: pickerResults.prevYear, + languageDirection, + } + }, [cellSize, date, pickerResults, width, languageDirection]) + return ( <>
@@ -75,13 +102,17 @@ export const CalendarInput = ({ type="text" onFocus={onFocus} value={date} + onChange={handleChange} + validationText={ + pickerResults.errorMessage || + pickerResults.warningMessage + } + error={!!pickerResults.errorMessage} + warning={!!pickerResults.warningMessage} /> {clearable && (
calendarProps.onDateSelect(null)} + onClick={() => { + parentOnDateSelect?.(null) + }} type="button" > {i18n.t('Clear')} @@ -114,7 +147,7 @@ export const CalendarInput = ({ modifiers={[offsetModifier]} > - + @@ -130,7 +163,6 @@ export const CalendarInput = ({ inset-inline-end: 6px; inset-block-start: 27px; } - .calendar-clear-button.with-icon { inset-inline-end: 36px; } @@ -148,5 +180,4 @@ CalendarInput.defaultProps = { } CalendarInput.propTypes = { ...CalendarProps, - ...InputFieldProps, } diff --git a/components/calendar/src/calendar/calendar-container.js b/components/calendar/src/calendar/calendar-container.js new file mode 100644 index 0000000000..4d8fb004b2 --- /dev/null +++ b/components/calendar/src/calendar/calendar-container.js @@ -0,0 +1,92 @@ +import { colors } from '@dhis2/ui-constants' +import PropTypes from 'prop-types' +import React, { useMemo } from 'react' +import { CalendarTable, CalendarTableProps } from './calendar-table.js' +import { + NavigationContainer, + NavigationContainerProps, +} from './navigation-container.js' + +const wrapperBorderColor = colors.grey300 +const backgroundColor = 'none' + +export const CalendarContainer = ({ + date, + width, + cellSize, + calendarWeekDays, + weekDayLabels, + currMonth, + currYear, + nextMonth, + nextYear, + prevMonth, + prevYear, + languageDirection, +}) => { + const navigationProps = useMemo(() => { + return { + currMonth, + currYear, + nextMonth, + nextYear, + prevMonth, + prevYear, + languageDirection, + } + }, [ + currMonth, + currYear, + languageDirection, + nextMonth, + nextYear, + prevMonth, + prevYear, + ]) + return ( +
+
+ + +
+ +
+ ) +} + +CalendarContainer.defaultProps = { + cellSize: '32px', + width: '240px', +} + +CalendarContainer.propTypes = { + /** the currently selected date using an iso-like format YYYY-MM-DD, in the calendar system provided (not iso8601) */ + date: PropTypes.string, + ...CalendarTableProps, + ...NavigationContainerProps, +} diff --git a/components/calendar/src/calendar/calendar-table.js b/components/calendar/src/calendar/calendar-table.js index b4d0ea1940..a9ea2ac5cf 100644 --- a/components/calendar/src/calendar/calendar-table.js +++ b/components/calendar/src/calendar/calendar-table.js @@ -48,7 +48,7 @@ export const CalendarTable = ({
) -CalendarTable.propTypes = { +export const CalendarTableProps = { calendarWeekDays: PropTypes.arrayOf( PropTypes.arrayOf( PropTypes.shape({ @@ -70,3 +70,5 @@ CalendarTable.propTypes = { weekDayLabels: PropTypes.arrayOf(PropTypes.string), width: PropTypes.string, } + +CalendarTable.propTypes = CalendarTableProps diff --git a/components/calendar/src/calendar/calendar.js b/components/calendar/src/calendar/calendar.js index 51176ea25d..f03a415293 100644 --- a/components/calendar/src/calendar/calendar.js +++ b/components/calendar/src/calendar/calendar.js @@ -2,11 +2,9 @@ import { useDatePicker, useResolvedDirection, } from '@dhis2/multi-calendar-dates' -import { colors } from '@dhis2/ui-constants' import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { CalendarTable } from './calendar-table.js' -import { NavigationContainer } from './navigation-container.js' +import React, { useMemo, useState } from 'react' +import { CalendarContainer } from './calendar-container.js' export const Calendar = ({ onDateSelect, @@ -20,9 +18,6 @@ export const Calendar = ({ width, cellSize, }) => { - const wrapperBorderColor = colors.grey300 - const backgroundColor = 'none' - const [selectedDateString, setSelectedDateString] = useState(date) const languageDirection = useResolvedDirection(dir, locale) @@ -34,7 +29,7 @@ export const Calendar = ({ weekDayFormat, } - const pickerOptions = useDatePicker({ + const pickerResults = useDatePicker({ onDateSelect: (result) => { const { calendarDateString } = result setSelectedDateString(calendarDateString) @@ -44,43 +39,33 @@ export const Calendar = ({ options, }) - const { calendarWeekDays, weekDayLabels } = pickerOptions + const calendarProps = useMemo(() => { + return { + date, + dir, + locale, + width, + cellSize, + // minDate, + // maxDate, + // validation, // todo: clarify how we use validation props (and format) in Calendar (not CalendarInput) + // format, + isValid: pickerResults.isValid, + calendarWeekDays: pickerResults.calendarWeekDays, + weekDayLabels: pickerResults.weekDayLabels, + currMonth: pickerResults.currMonth, + currYear: pickerResults.currYear, + nextMonth: pickerResults.nextMonth, + nextYear: pickerResults.nextYear, + prevMonth: pickerResults.prevMonth, + prevYear: pickerResults.prevYear, + languageDirection, + } + }, [cellSize, date, dir, locale, pickerResults, width, languageDirection]) return (
-
- - -
- +
) } diff --git a/components/calendar/src/calendar/navigation-container.js b/components/calendar/src/calendar/navigation-container.js index 933f9d6e0d..87f9a1953d 100644 --- a/components/calendar/src/calendar/navigation-container.js +++ b/components/calendar/src/calendar/navigation-container.js @@ -7,15 +7,20 @@ import i18n from '../locales/index.js' const wrapperBorderColor = colors.grey300 const headerBackground = colors.grey050 -export const NavigationContainer = ({ languageDirection, pickerOptions }) => { +export const NavigationContainer = ({ + languageDirection, + currMonth, + currYear, + nextMonth, + nextYear, + prevMonth, + prevYear, +}) => { const PreviousIcon = languageDirection === 'ltr' ? IconChevronLeft16 : IconChevronRight16 const NextIcon = languageDirection === 'ltr' ? IconChevronRight16 : IconChevronLeft16 - const { currMonth, currYear, nextMonth, nextYear, prevMonth, prevYear } = - pickerOptions - // Ethiopic years - when localised to English - add the era (i.e. 2015 ERA1), which is redundant in practice (like writing AD for gregorian years) // there is an ongoing discussion in JS-Temporal polyfill whether the era should be included or not, but for our case, it's safer to remove it const currentYearLabel = currYear.label?.toString().replace(/ERA1/, '') @@ -155,30 +160,30 @@ export const NavigationContainer = ({ languageDirection, pickerOptions }) => { ) } -NavigationContainer.propTypes = { +export const NavigationContainerProps = { + currMonth: PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + }), + currYear: PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + }), languageDirection: PropTypes.oneOf(['ltr', 'rtl']), - pickerOptions: PropTypes.shape({ - currMonth: PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - }), - currYear: PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - }), - nextMonth: PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - navigateTo: PropTypes.func, - }), - nextYear: PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - navigateTo: PropTypes.func, - }), - prevMonth: PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - navigateTo: PropTypes.func, - }), - prevYear: PropTypes.shape({ - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - navigateTo: PropTypes.func, - }), + nextMonth: PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + navigateTo: PropTypes.func, + }), + nextYear: PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + navigateTo: PropTypes.func, + }), + prevMonth: PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + navigateTo: PropTypes.func, + }), + prevYear: PropTypes.shape({ + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + navigateTo: PropTypes.func, }), } + +NavigationContainer.propTypes = NavigationContainerProps diff --git a/components/calendar/src/stories/calendar-input.stories.js b/components/calendar/src/stories/calendar-input.stories.js index 13cc451670..7d3917ad00 100644 --- a/components/calendar/src/stories/calendar-input.stories.js +++ b/components/calendar/src/stories/calendar-input.stories.js @@ -114,3 +114,27 @@ export const CalendarWithClearButton = ({ ) } + +export function CalendarWithEditiableInput() { + const [date, setDate] = useState('2020-07-03') + return ( +
+ <> + { + const date = selectedDate?.calendarDateString + setDate(date) + }} + width={'700px'} + inputWidth="900px" + timeZone={'UTC'} + minDate={'2020-07-01'} + maxDate={'2020-07-09'} + /> + +
+ ) +} diff --git a/yarn.lock b/yarn.lock index 15c684e11d..d2ef2cfceb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2629,12 +2629,12 @@ i18next "^10.3" moment "^2.24.0" -"@dhis2/multi-calendar-dates@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@dhis2/multi-calendar-dates/-/multi-calendar-dates-1.1.1.tgz#fb76a77114ce0b757db7dd9f588d1a47809732da" - integrity sha512-kaisVuRGfdqY/Up6sWqgc81K67ymPVoRYgYRcT29z61ol2WhiTXTSTuRX/gDO1VKjmskeB5/badRrdLMf4BBUA== +"@dhis2/multi-calendar-dates@v1.0.0-alpha.26": + version "1.0.0-alpha.26" + resolved "https://registry.yarnpkg.com/@dhis2/multi-calendar-dates/-/multi-calendar-dates-1.0.0-alpha.26.tgz#33c3384ee96219f5500005058a69bd3104d1a5b9" + integrity sha512-85oj4Ji/UOwt4nWDrzUfyl5tkcF1YrB1kBh1kCjPL0Md1+XDzM6nee9DFx4Eh9BNJN/cOgWJo9mWDsPycXO0aA== dependencies: - "@js-temporal/polyfill" "^0.4.2" + "@js-temporal/polyfill" "0.4.3" classnames "^2.3.2" "@dhis2/prop-types@^1.6.4": @@ -3812,7 +3812,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@js-temporal/polyfill@^0.4.2": +"@js-temporal/polyfill@0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@js-temporal/polyfill/-/polyfill-0.4.3.tgz#e8f8cf86745eb5050679c46a5ebedb9a9cc1f09b" integrity sha512-6Fmjo/HlkyVCmJzAPnvtEWlcbQUSRhi8qlN9EtJA/wP7FqXsevLLrlojR44kzNzrRkpf7eDJ+z7b4xQD/Ycypw==