Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support editable input | min & max dates | dd-mm-yyyy format in CalendarInput #1504

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/calendar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
101 changes: 66 additions & 35 deletions components/calendar/src/calendar-input/calendar-input.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -16,7 +21,7 @@ const offsetModifier = {
}

export const CalendarInput = ({
onDateSelect,
onDateSelect: parentOnDateSelect,
calendar,
date,
dir,
Expand All @@ -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 (
<>
<div className="calendar-input-wrapper" ref={ref}>
Expand All @@ -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 && (
<div
className={cx('calendar-clear-button', {
// ToDo: this is a workaround to show the clear button in the correct place when an icon is shown.
// Long-term, we should abstract and share the logic multi-select uses for the input-wrapper
// https://dhis2.atlassian.net/browse/DHIS2-14848
'with-icon':
rest.valid ||
rest.error ||
Expand All @@ -94,7 +125,9 @@ export const CalendarInput = ({
dataTest="calendar-clear-button"
secondary
small
onClick={() => calendarProps.onDateSelect(null)}
onClick={() => {
parentOnDateSelect?.(null)
}}
type="button"
>
{i18n.t('Clear')}
Expand All @@ -114,7 +147,7 @@ export const CalendarInput = ({
modifiers={[offsetModifier]}
>
<Card>
<Calendar {...calendarProps} date={date} />
<CalendarContainer {...calendarProps} />
</Card>
</Popper>
</Layer>
Expand All @@ -130,7 +163,6 @@ export const CalendarInput = ({
inset-inline-end: 6px;
inset-block-start: 27px;
}

.calendar-clear-button.with-icon {
inset-inline-end: 36px;
}
Expand All @@ -148,5 +180,4 @@ CalendarInput.defaultProps = {
}
CalendarInput.propTypes = {
...CalendarProps,
...InputFieldProps,
}
92 changes: 92 additions & 0 deletions components/calendar/src/calendar/calendar-container.js
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<div
className="calendar-wrapper"
dir={languageDirection}
data-test="calendar"
>
<NavigationContainer {...navigationProps} />
<CalendarTable
selectedDate={date}
calendarWeekDays={calendarWeekDays}
weekDayLabels={weekDayLabels}
cellSize={cellSize}
width={width}
/>
</div>
<style jsx>{`
.calendar-wrapper {
font-family: Roboto, sans-serif;
font-weight: 400;
font-size: 14px;
background-color: ${backgroundColor};
display: flex;
flex-direction: column;
border: 1px solid ${wrapperBorderColor};
border-radius: 3px;
min-width: ${width};
width: max-content;
box-shadow: 0px 4px 6px -2px #2129340d;
box-shadow: 0px 10px 15px -3px #2129341a;
}
`}</style>
</div>
)
}

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,
}
4 changes: 3 additions & 1 deletion components/calendar/src/calendar/calendar-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const CalendarTable = ({
</div>
)

CalendarTable.propTypes = {
export const CalendarTableProps = {
calendarWeekDays: PropTypes.arrayOf(
PropTypes.arrayOf(
PropTypes.shape({
Expand All @@ -70,3 +70,5 @@ CalendarTable.propTypes = {
weekDayLabels: PropTypes.arrayOf(PropTypes.string),
width: PropTypes.string,
}

CalendarTable.propTypes = CalendarTableProps
69 changes: 27 additions & 42 deletions components/calendar/src/calendar/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)

Expand All @@ -34,7 +29,7 @@ export const Calendar = ({
weekDayFormat,
}

const pickerOptions = useDatePicker({
const pickerResults = useDatePicker({
onDateSelect: (result) => {
const { calendarDateString } = result
setSelectedDateString(calendarDateString)
Expand All @@ -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 (
<div>
<div
className="calendar-wrapper"
dir={languageDirection}
data-test="calendar"
>
<NavigationContainer
pickerOptions={pickerOptions}
languageDirection={languageDirection}
/>
<CalendarTable
selectedDate={selectedDateString}
calendarWeekDays={calendarWeekDays}
weekDayLabels={weekDayLabels}
cellSize={cellSize}
width={width}
/>
</div>
<style jsx>{`
.calendar-wrapper {
font-family: Roboto, sans-serif;
font-weight: 400;
font-size: 14px;
background-color: ${backgroundColor};
display: flex;
flex-direction: column;
border: 1px solid ${wrapperBorderColor};
border-radius: 3px;
min-width: ${width};
width: max-content;
box-shadow: 0px 4px 6px -2px #2129340d;
box-shadow: 0px 10px 15px -3px #2129341a;
}
`}</style>
<CalendarContainer {...calendarProps} />
</div>
)
}
Expand Down
Loading
Loading