From c17fdf0afaf595b4ab7ea86deddd5f395ff6037c Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Mon, 20 May 2024 16:22:09 +0300
Subject: [PATCH 01/11] feat: support min & max dates

---
 .../src/calendar-input/calendar-input.js      | 65 +++++++++++++++++--
 components/calendar/src/calendar/calendar.js  |  6 +-
 2 files changed, 63 insertions(+), 8 deletions(-)

diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js
index a2753110b2..0be3e42cdd 100644
--- a/components/calendar/src/calendar-input/calendar-input.js
+++ b/components/calendar/src/calendar-input/calendar-input.js
@@ -3,8 +3,9 @@ import { Card } from '@dhis2-ui/card'
 import { InputField, InputFieldProps } from '@dhis2-ui/input'
 import { Layer } from '@dhis2-ui/layer'
 import { Popper } from '@dhis2-ui/popper'
+import { validateDateString } from '@dhis2/multi-calendar-dates'
 import cx from 'classnames'
-import React, { useRef, useState } from 'react'
+import React, { useRef, useState, useEffect } from 'react'
 import { Calendar, CalendarProps } from '../calendar/calendar.js'
 import i18n from '../locales/index.js'
 
@@ -27,10 +28,16 @@ export const CalendarInput = ({
     width,
     cellSize,
     clearable,
+    editable,
+    minDate,
+    maxDate,
     ...rest
 } = {}) => {
     const ref = useRef()
+    const [tempDate, setTempDate] = useState(date)
     const [open, setOpen] = useState(false)
+    const [error, setError] = useState('')
+    const [warning, setWarning] = useState('')
 
     const calendarProps = React.useMemo(() => {
         const onDateSelectWrapper = (selectedDate) => {
@@ -66,6 +73,47 @@ export const CalendarInput = ({
         setOpen(true)
     }
 
+    useEffect(() => {
+        setTempDate(date)
+        const { isValid, errorMessage, warningMessage } = validateDateString(
+            date,
+            {
+                minDateString: minDate,
+                maxDateString: maxDate,
+                validationType: 'throw',
+            }
+        )
+        if (isValid) {
+            setError('')
+            setWarning(warningMessage || '')
+        } else {
+            setError(errorMessage)
+            setOpen(true)
+        }
+    }, [date, maxDate, minDate])
+
+    const handleChange = (e) => {
+        setOpen(false)
+        const newDate = e.value
+        setTempDate(newDate)
+        const { isValid, errorMessage, warningMessage } = validateDateString(
+            newDate,
+            {
+                minDateString: minDate,
+                maxDateString: maxDate,
+                validationType: 'throw',
+            }
+        )
+        setOpen(true)
+        if (isValid) {
+            setError('')
+            setWarning(warningMessage || '')
+            onDateSelect({ calendarDateString: newDate })
+        } else {
+            setError(errorMessage)
+        }
+    }
+
     return (
         <>
             <div className="calendar-input-wrapper" ref={ref}>
@@ -74,14 +122,15 @@ export const CalendarInput = ({
                     {...rest}
                     type="text"
                     onFocus={onFocus}
-                    value={date}
+                    value={tempDate}
+                    onChange={editable ? handleChange : undefined}
+                    validationText={error || warning}
+                    error={!!error}
+                    warning={!!warning}
                 />
                 {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 ||
@@ -94,7 +143,10 @@ export const CalendarInput = ({
                             dataTest="calendar-clear-button"
                             secondary
                             small
-                            onClick={() => calendarProps.onDateSelect(null)}
+                            onClick={() => {
+                                setTempDate('')
+                                onDateSelect?.(null)
+                            }}
                             type="button"
                         >
                             {i18n.t('Clear')}
@@ -130,7 +182,6 @@ export const CalendarInput = ({
                         inset-inline-end: 6px;
                         inset-block-start: 27px;
                     }
-
                     .calendar-clear-button.with-icon {
                         inset-inline-end: 36px;
                     }
diff --git a/components/calendar/src/calendar/calendar.js b/components/calendar/src/calendar/calendar.js
index 51176ea25d..2cd8ecba01 100644
--- a/components/calendar/src/calendar/calendar.js
+++ b/components/calendar/src/calendar/calendar.js
@@ -4,7 +4,7 @@ import {
 } from '@dhis2/multi-calendar-dates'
 import { colors } from '@dhis2/ui-constants'
 import PropTypes from 'prop-types'
-import React, { useState } from 'react'
+import React, { useEffect, useState } from 'react'
 import { CalendarTable } from './calendar-table.js'
 import { NavigationContainer } from './navigation-container.js'
 
@@ -26,6 +26,10 @@ export const Calendar = ({
     const [selectedDateString, setSelectedDateString] = useState(date)
     const languageDirection = useResolvedDirection(dir, locale)
 
+    useEffect(() => {
+        setSelectedDateString(date)
+    }, [date])
+
     const options = {
         locale,
         calendar,

From 14f8bde6274efb84d5a3639a3c324d8247dc2152 Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Tue, 21 May 2024 17:47:14 +0300
Subject: [PATCH 02/11] chore: add story CalendarWithEditiableInput

---
 .../src/stories/calendar-input.stories.js     | 50 +++++++++++++------
 1 file changed, 36 insertions(+), 14 deletions(-)

diff --git a/components/calendar/src/stories/calendar-input.stories.js b/components/calendar/src/stories/calendar-input.stories.js
index 13cc451670..493cb15021 100644
--- a/components/calendar/src/stories/calendar-input.stories.js
+++ b/components/calendar/src/stories/calendar-input.stories.js
@@ -22,20 +22,17 @@ export default {
     },
 }
 
-const buildCalendar =
-    ({ date, locale, calendar }) =>
-    () =>
-        (
-            <CalendarStoryWrapper
-                component={CalendarInput}
-                dir="ltr"
-                timeZone="Africa/Khartoum"
-                weekDayFormat="short"
-                date={date}
-                locale={locale}
-                calendar={calendar}
-            />
-        )
+const buildCalendar = ({ date, locale, calendar }) => () => (
+    <CalendarStoryWrapper
+        component={CalendarInput}
+        dir="ltr"
+        timeZone="Africa/Khartoum"
+        weekDayFormat="short"
+        date={date}
+        locale={locale}
+        calendar={calendar}
+    />
+)
 
 export const EthiopicWithAmharic = buildCalendar({
     calendar: 'ethiopic',
@@ -114,3 +111,28 @@ export const CalendarWithClearButton = ({
         </>
     )
 }
+
+export function CalendarWithEditiableInput() {
+    const [date, setDate] = useState('2020-07-03')
+    return (
+        <div>
+            <>
+                <CalendarInput
+                    editable
+                    date={date}
+                    calendar="gregory"
+                    onDateSelect={(selectedDate) => {
+                        const date = selectedDate?.calendarDateString
+                        setDate(date)
+                    }}
+                    label="ooo"
+                    width={'700px'}
+                    inputWidth="900px"
+                    timeZone={'UTC'}
+                    minDate={'2020-07-01'}
+                    maxDate={'2020-07-09'}
+                />
+            </>
+        </div>
+    )
+}

From dd1c85fb3670c3539b8da7fcce669735483fc79e Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Wed, 29 May 2024 12:23:45 +0300
Subject: [PATCH 03/11] chore: refactor code

---
 .../src/calendar-input/calendar-input.js      | 78 ++++++++-----------
 components/calendar/src/calendar/calendar.js  | 70 +++++------------
 2 files changed, 51 insertions(+), 97 deletions(-)

diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js
index 0be3e42cdd..871e2f34b7 100644
--- a/components/calendar/src/calendar-input/calendar-input.js
+++ b/components/calendar/src/calendar-input/calendar-input.js
@@ -1,9 +1,9 @@
 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 { validateDateString } from '@dhis2/multi-calendar-dates'
+import { useDatePicker } from '@dhis2/multi-calendar-dates'
 import cx from 'classnames'
 import React, { useRef, useState, useEffect } from 'react'
 import { Calendar, CalendarProps } from '../calendar/calendar.js'
@@ -31,10 +31,11 @@ export const CalendarInput = ({
     editable,
     minDate,
     maxDate,
+    format,
+    validation,
     ...rest
 } = {}) => {
     const ref = useRef()
-    const [tempDate, setTempDate] = useState(date)
     const [open, setOpen] = useState(false)
     const [error, setError] = useState('')
     const [warning, setWarning] = useState('')
@@ -69,49 +70,34 @@ export const CalendarInput = ({
         width,
     ])
 
-    const onFocus = () => {
-        setOpen(true)
-    }
+    const pickerOptions = useDatePicker({
+        onDateSelect: (result) => {
+            setOpen(false)
+            onDateSelect(result)
+        },
+        date: date,
+        minDate: minDate,
+        maxDate: maxDate,
+        validation: validation,
+        format: format,
+        options: calendarProps,
+    })
 
     useEffect(() => {
-        setTempDate(date)
-        const { isValid, errorMessage, warningMessage } = validateDateString(
-            date,
-            {
-                minDateString: minDate,
-                maxDateString: maxDate,
-                validationType: 'throw',
-            }
-        )
-        if (isValid) {
-            setError('')
-            setWarning(warningMessage || '')
-        } else {
-            setError(errorMessage)
-            setOpen(true)
-        }
-    }, [date, maxDate, minDate])
+        setError(pickerOptions.errorMessage || '')
+        setWarning(pickerOptions.warningMessage || '')
+    }, [
+        pickerOptions.errorMessage,
+        pickerOptions.warningMessage,
+        pickerOptions.isValid,
+    ])
 
     const handleChange = (e) => {
-        setOpen(false)
-        const newDate = e.value
-        setTempDate(newDate)
-        const { isValid, errorMessage, warningMessage } = validateDateString(
-            newDate,
-            {
-                minDateString: minDate,
-                maxDateString: maxDate,
-                validationType: 'throw',
-            }
-        )
+        onDateSelect?.({ calendarDateString: e.value })
+    }
+
+    const onFocus = () => {
         setOpen(true)
-        if (isValid) {
-            setError('')
-            setWarning(warningMessage || '')
-            onDateSelect({ calendarDateString: newDate })
-        } else {
-            setError(errorMessage)
-        }
     }
 
     return (
@@ -122,7 +108,7 @@ export const CalendarInput = ({
                     {...rest}
                     type="text"
                     onFocus={onFocus}
-                    value={tempDate}
+                    value={date}
                     onChange={editable ? handleChange : undefined}
                     validationText={error || warning}
                     error={!!error}
@@ -144,7 +130,6 @@ export const CalendarInput = ({
                             secondary
                             small
                             onClick={() => {
-                                setTempDate('')
                                 onDateSelect?.(null)
                             }}
                             type="button"
@@ -166,7 +151,11 @@ export const CalendarInput = ({
                         modifiers={[offsetModifier]}
                     >
                         <Card>
-                            <Calendar {...calendarProps} date={date} />
+                            <Calendar
+                                {...calendarProps}
+                                {...pickerOptions}
+                                date={date}
+                            />
                         </Card>
                     </Popper>
                 </Layer>
@@ -199,5 +188,4 @@ CalendarInput.defaultProps = {
 }
 CalendarInput.propTypes = {
     ...CalendarProps,
-    ...InputFieldProps,
 }
diff --git a/components/calendar/src/calendar/calendar.js b/components/calendar/src/calendar/calendar.js
index 2cd8ecba01..1c88a8f1fb 100644
--- a/components/calendar/src/calendar/calendar.js
+++ b/components/calendar/src/calendar/calendar.js
@@ -1,55 +1,27 @@
-import {
-    useDatePicker,
-    useResolvedDirection,
-} from '@dhis2/multi-calendar-dates'
+import { useResolvedDirection } from '@dhis2/multi-calendar-dates'
 import { colors } from '@dhis2/ui-constants'
 import PropTypes from 'prop-types'
-import React, { useEffect, useState } from 'react'
+import React from 'react'
 import { CalendarTable } from './calendar-table.js'
 import { NavigationContainer } from './navigation-container.js'
 
-export const Calendar = ({
-    onDateSelect,
-    calendar,
-    date,
-    dir,
-    locale,
-    numberingSystem,
-    weekDayFormat,
-    timeZone,
-    width,
-    cellSize,
-}) => {
+export const Calendar = (calendarProps) => {
+    const {
+        date,
+        dir,
+        locale,
+        width,
+        cellSize,
+        calendarWeekDays,
+        weekDayLabels,
+        isValid,
+    } = calendarProps
+
     const wrapperBorderColor = colors.grey300
     const backgroundColor = 'none'
 
-    const [selectedDateString, setSelectedDateString] = useState(date)
     const languageDirection = useResolvedDirection(dir, locale)
 
-    useEffect(() => {
-        setSelectedDateString(date)
-    }, [date])
-
-    const options = {
-        locale,
-        calendar,
-        timeZone,
-        numberingSystem,
-        weekDayFormat,
-    }
-
-    const pickerOptions = useDatePicker({
-        onDateSelect: (result) => {
-            const { calendarDateString } = result
-            setSelectedDateString(calendarDateString)
-            onDateSelect(result)
-        },
-        date: selectedDateString,
-        options,
-    })
-
-    const { calendarWeekDays, weekDayLabels } = pickerOptions
-
     return (
         <div>
             <div
@@ -58,11 +30,11 @@ export const Calendar = ({
                 data-test="calendar"
             >
                 <NavigationContainer
-                    pickerOptions={pickerOptions}
+                    pickerOptions={calendarProps}
                     languageDirection={languageDirection}
                 />
                 <CalendarTable
-                    selectedDate={selectedDateString}
+                    selectedDate={isValid ? date : null}
                     calendarWeekDays={calendarWeekDays}
                     weekDayLabels={weekDayLabels}
                     cellSize={cellSize}
@@ -96,10 +68,6 @@ Calendar.defaultProps = {
 }
 
 export const CalendarProps = {
-    /** the calendar to use such gregory, ethiopic, nepali - full supported list here: https://github.com/dhis2/multi-calendar-dates/blob/main/src/constants/calendars.ts  */
-    calendar: PropTypes.any.isRequired,
-    /** Called with signature `(null)` \|\| `({ dateCalendarString: string, dateCalendar: Temporal.ZonedDateTime })` with `dateCalendarString` being the stringified date in the specified calendar in the format `yyyy-MM-dd` */
-    onDateSelect: PropTypes.func.isRequired,
     /** the size of a single cell in the table forming the calendar */
     cellSize: PropTypes.string,
     /** the currently selected date using an iso-like format YYYY-MM-DD, in the calendar system provided (not iso8601) */
@@ -108,14 +76,12 @@ export const CalendarProps = {
     dir: PropTypes.oneOf(['ltr', 'rtl']),
     /** any valid locale -  if none provided, the internal library will fallback to the user locale (more info here: https://github.com/dhis2/multi-calendar-dates/blob/main/src/hooks/internal/useResolvedLocaleOptions.ts#L15) */
     locale: PropTypes.string,
-    /** numbering system to use - full list here https://github.com/dhis2/multi-calendar-dates/blob/main/src/constants/numberingSystems.ts */
-    numberingSystem: PropTypes.string,
-    /** the timeZone to use */
-    timeZone: PropTypes.string,
     /** the format to display for the week day, i.e. Monday (long), Mon (short), M (narrow) */
     weekDayFormat: PropTypes.oneOf(['narrow', 'short', 'long']),
     /** the width of the calendar component */
     width: PropTypes.string,
+    /** is date valid and within range */
+    isValid: PropTypes.bool,
 }
 
 Calendar.propTypes = CalendarProps

From af16a74c5339931f45e2ba539b858c54f2290d36 Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Mon, 10 Jun 2024 13:11:58 +0300
Subject: [PATCH 04/11] fix: improve calendar component for independent use

---
 components/calendar/package.json             | 12 +++---
 components/calendar/src/calendar/calendar.js | 44 +++++++++++++++++---
 yarn.lock                                    | 12 +++---
 3 files changed, 51 insertions(+), 17 deletions(-)

diff --git a/components/calendar/package.json b/components/calendar/package.json
index 5f3ce1604c..0a8c439fb4 100644
--- a/components/calendar/package.json
+++ b/components/calendar/package.json
@@ -33,12 +33,12 @@
         "styled-jsx": "^4"
     },
     "dependencies": {
-        "@dhis2-ui/button": "9.6.0",
-        "@dhis2-ui/card": "9.6.0",
-        "@dhis2-ui/input": "9.6.0",
-        "@dhis2-ui/layer": "9.6.0",
-        "@dhis2-ui/popper": "9.6.0",
-        "@dhis2/multi-calendar-dates": "^1.1.1",
+        "@dhis2-ui/button": "9.4.6",
+        "@dhis2-ui/card": "9.4.6",
+        "@dhis2-ui/input": "9.4.6",
+        "@dhis2-ui/layer": "9.4.6",
+        "@dhis2-ui/popper": "9.4.6",
+        "@dhis2/multi-calendar-dates": "v1.0.0-alpha.26",
         "@dhis2/prop-types": "^3.1.2",
         "@dhis2/ui-constants": "9.6.0",
         "@dhis2/ui-icons": "9.6.0",
diff --git a/components/calendar/src/calendar/calendar.js b/components/calendar/src/calendar/calendar.js
index 1c88a8f1fb..536fb6ad05 100644
--- a/components/calendar/src/calendar/calendar.js
+++ b/components/calendar/src/calendar/calendar.js
@@ -1,4 +1,7 @@
-import { useResolvedDirection } from '@dhis2/multi-calendar-dates'
+import {
+    useResolvedDirection,
+    useDatePicker,
+} from '@dhis2/multi-calendar-dates'
 import { colors } from '@dhis2/ui-constants'
 import PropTypes from 'prop-types'
 import React from 'react'
@@ -12,11 +15,32 @@ export const Calendar = (calendarProps) => {
         locale,
         width,
         cellSize,
-        calendarWeekDays,
-        weekDayLabels,
         isValid,
+        onDateSelect,
+        minDate,
+        maxDate,
+        validation,
+        format,
     } = calendarProps
+    let { calendarWeekDays, weekDayLabels } = calendarProps
+    const pickerOptions = useDatePicker({
+        onDateSelect: (result) => {
+            onDateSelect(result)
+        },
+        date: date,
+        minDate: minDate,
+        maxDate: maxDate,
+        validation: validation,
+        format: format,
+        options: calendarProps,
+    })
 
+    if (!calendarWeekDays) {
+        calendarWeekDays = pickerOptions.calendarWeekDays
+    }
+    if (!weekDayLabels) {
+        weekDayLabels = pickerOptions.weekDayLabels
+    }
     const wrapperBorderColor = colors.grey300
     const backgroundColor = 'none'
 
@@ -30,7 +54,11 @@ export const Calendar = (calendarProps) => {
                 data-test="calendar"
             >
                 <NavigationContainer
-                    pickerOptions={calendarProps}
+                    pickerOptions={
+                        calendarProps.calendarWeekDays
+                            ? calendarProps
+                            : pickerOptions
+                    }
                     languageDirection={languageDirection}
                 />
                 <CalendarTable
@@ -82,6 +110,12 @@ export const CalendarProps = {
     width: PropTypes.string,
     /** is date valid and within range */
     isValid: PropTypes.bool,
+    calendarWeekDays: PropTypes.array,
+    weekDayLabels: PropTypes.arrayOf(PropTypes.string),
+    minDate: PropTypes.string,
+    maxDate: PropTypes.string,
+    validation: PropTypes.string,
+    format: PropTypes.string,
 }
 
-Calendar.propTypes = CalendarProps
+Calendar.propTypes = { ...CalendarProps }
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==

From 21d37f1753ea8b133d1a69c98f88fc2ed2e3c06a Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Mon, 10 Jun 2024 13:32:41 +0300
Subject: [PATCH 05/11] fix: lint warnings

---
 .../src/stories/calendar-input.stories.js     | 25 +++++++++++--------
 1 file changed, 14 insertions(+), 11 deletions(-)

diff --git a/components/calendar/src/stories/calendar-input.stories.js b/components/calendar/src/stories/calendar-input.stories.js
index 493cb15021..be4a73db98 100644
--- a/components/calendar/src/stories/calendar-input.stories.js
+++ b/components/calendar/src/stories/calendar-input.stories.js
@@ -22,17 +22,20 @@ export default {
     },
 }
 
-const buildCalendar = ({ date, locale, calendar }) => () => (
-    <CalendarStoryWrapper
-        component={CalendarInput}
-        dir="ltr"
-        timeZone="Africa/Khartoum"
-        weekDayFormat="short"
-        date={date}
-        locale={locale}
-        calendar={calendar}
-    />
-)
+const buildCalendar =
+    ({ date, locale, calendar }) =>
+    () =>
+        (
+            <CalendarStoryWrapper
+                component={CalendarInput}
+                dir="ltr"
+                timeZone="Africa/Khartoum"
+                weekDayFormat="short"
+                date={date}
+                locale={locale}
+                calendar={calendar}
+            />
+        )
 
 export const EthiopicWithAmharic = buildCalendar({
     calendar: 'ethiopic',

From 00e2cb5647d85b1c02c600ed3e6d3e6f7151ea92 Mon Sep 17 00:00:00 2001
From: Mozafar Haider <mozafar@dhis2.org>
Date: Thu, 25 Jul 2024 11:18:40 +0100
Subject: [PATCH 06/11] refactor: use calendar inner components directly

to avoid double calls to multi-calendar library
---
 .../src/calendar-input/calendar-input.js      | 160 ++++++++++++------
 components/calendar/src/calendar/calendar.js  |  90 +++++-----
 2 files changed, 148 insertions(+), 102 deletions(-)

diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js
index 871e2f34b7..29f8033b4e 100644
--- a/components/calendar/src/calendar-input/calendar-input.js
+++ b/components/calendar/src/calendar-input/calendar-input.js
@@ -3,10 +3,17 @@ import { Card } from '@dhis2-ui/card'
 import { InputField } from '@dhis2-ui/input'
 import { Layer } from '@dhis2-ui/layer'
 import { Popper } from '@dhis2-ui/popper'
-import { useDatePicker } from '@dhis2/multi-calendar-dates'
+import {
+    useDatePicker,
+    useResolvedDirection,
+} from '@dhis2/multi-calendar-dates'
+import { colors } from '@dhis2/ui-constants'
 import cx from 'classnames'
-import React, { useRef, useState, useEffect } from 'react'
-import { Calendar, CalendarProps } from '../calendar/calendar.js'
+// import { debounce } from 'lodash'
+import React, { useRef, useState, useMemo, useCallback } from 'react'
+import { CalendarTable } from '../calendar/calendar-table.js'
+import { CalendarProps } from '../calendar/calendar.js'
+import { NavigationContainer } from '../calendar/navigation-container.js'
 import i18n from '../locales/index.js'
 
 const offsetModifier = {
@@ -17,7 +24,7 @@ const offsetModifier = {
 }
 
 export const CalendarInput = ({
-    onDateSelect,
+    onDateSelect: parentOnDateSelect,
     calendar,
     date,
     dir,
@@ -31,67 +38,42 @@ export const CalendarInput = ({
     editable,
     minDate,
     maxDate,
-    format,
+    format, // todo: props and types for format and validation
     validation,
     ...rest
 } = {}) => {
     const ref = useRef()
     const [open, setOpen] = useState(false)
-    const [error, setError] = useState('')
-    const [warning, setWarning] = useState('')
 
-    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,
-        date,
-        dir,
-        locale,
-        numberingSystem,
-        onDateSelect,
-        timeZone,
-        weekDayFormat,
-        width,
-    ])
+        }),
+        [calendar, locale, numberingSystem, timeZone, weekDayFormat]
+    )
 
-    const pickerOptions = useDatePicker({
-        onDateSelect: (result) => {
-            setOpen(false)
-            onDateSelect(result)
+    const onDateSelect = useCallback(
+        (result, keepPopperOpen = false) => {
+            setOpen(keepPopperOpen)
+            parentOnDateSelect?.(result)
         },
-        date: date,
+        [parentOnDateSelect]
+    )
+
+    const pickerResults = useDatePicker({
+        onDateSelect,
+        date,
         minDate: minDate,
         maxDate: maxDate,
         validation: validation,
         format: format,
-        options: calendarProps,
+        options: useDatePickerOptions,
     })
 
-    useEffect(() => {
-        setError(pickerOptions.errorMessage || '')
-        setWarning(pickerOptions.warningMessage || '')
-    }, [
-        pickerOptions.errorMessage,
-        pickerOptions.warningMessage,
-        pickerOptions.isValid,
-    ])
-
     const handleChange = (e) => {
         onDateSelect?.({ calendarDateString: e.value })
     }
@@ -100,6 +82,41 @@ export const CalendarInput = ({
         setOpen(true)
     }
 
+    const calendarProps = useMemo(() => {
+        return {
+            date,
+            dir,
+            locale,
+            width,
+            cellSize,
+            minDate,
+            maxDate,
+            validation, // todo: clarify "validation" type in the hook
+            format,
+            calendarWeekDays: pickerResults.calendarWeekDays,
+            weekDayLabels: pickerResults.weekDayLabels,
+            currMonth: pickerResults.currMonth,
+            currYear: pickerResults.currYear,
+            nextMonth: pickerResults.nextMonth,
+            nextYear: pickerResults.nextYear,
+            prevMonth: pickerResults.prevMonth,
+            prevYear: pickerResults.prevYear,
+        }
+    }, [
+        cellSize,
+        date,
+        dir,
+        format,
+        locale,
+        maxDate,
+        minDate,
+        pickerResults,
+        validation,
+        width,
+    ])
+
+    const languageDirection = useResolvedDirection(dir, locale)
+
     return (
         <>
             <div className="calendar-input-wrapper" ref={ref}>
@@ -110,9 +127,12 @@ export const CalendarInput = ({
                     onFocus={onFocus}
                     value={date}
                     onChange={editable ? handleChange : undefined}
-                    validationText={error || warning}
-                    error={!!error}
-                    warning={!!warning}
+                    validationText={
+                        pickerResults.errorMessage ||
+                        pickerResults.warningMessage
+                    }
+                    error={!!pickerResults.errorMessage}
+                    warning={!!pickerResults.warningMessage}
                 />
                 {clearable && (
                     <div
@@ -151,11 +171,27 @@ export const CalendarInput = ({
                         modifiers={[offsetModifier]}
                     >
                         <Card>
-                            <Calendar
-                                {...calendarProps}
-                                {...pickerOptions}
-                                date={date}
-                            />
+                            <div
+                                className="calendar-wrapper"
+                                dir={languageDirection}
+                                data-test="calendar"
+                            >
+                                <NavigationContainer
+                                    pickerOptions={calendarProps}
+                                    languageDirection={languageDirection}
+                                />
+                                <CalendarTable
+                                    selectedDate={
+                                        calendarProps.isValid ? date : null
+                                    }
+                                    calendarWeekDays={
+                                        calendarProps.calendarWeekDays
+                                    }
+                                    weekDayLabels={calendarProps.weekDayLabels}
+                                    cellSize={cellSize}
+                                    width={width}
+                                />
+                            </div>
                         </Card>
                     </Popper>
                 </Layer>
@@ -177,6 +213,20 @@ export const CalendarInput = ({
                     .calendar-clear-button.with-dense-wrapper {
                         inset-block-start: 23px;
                     }
+                    .calendar-wrapper {
+                        font-family: Roboto, sans-serif;
+                        font-weight: 400;
+                        font-size: 14px;
+                        background-color: none;
+                        display: flex;
+                        flex-direction: column;
+                        border: 1px solid ${colors.grey300};
+                        border-radius: 3px;
+                        min-width: ${width};
+                        width: max-content;
+                        box-shadow: 0px 4px 6px -2px #2129340d;
+                        box-shadow: 0px 10px 15px -3px #2129341a;
+                    }
                 `}
             </style>
         </>
diff --git a/components/calendar/src/calendar/calendar.js b/components/calendar/src/calendar/calendar.js
index 536fb6ad05..51176ea25d 100644
--- a/components/calendar/src/calendar/calendar.js
+++ b/components/calendar/src/calendar/calendar.js
@@ -1,50 +1,50 @@
 import {
-    useResolvedDirection,
     useDatePicker,
+    useResolvedDirection,
 } from '@dhis2/multi-calendar-dates'
 import { colors } from '@dhis2/ui-constants'
 import PropTypes from 'prop-types'
-import React from 'react'
+import React, { useState } from 'react'
 import { CalendarTable } from './calendar-table.js'
 import { NavigationContainer } from './navigation-container.js'
 
-export const Calendar = (calendarProps) => {
-    const {
-        date,
-        dir,
+export const Calendar = ({
+    onDateSelect,
+    calendar,
+    date,
+    dir,
+    locale,
+    numberingSystem,
+    weekDayFormat,
+    timeZone,
+    width,
+    cellSize,
+}) => {
+    const wrapperBorderColor = colors.grey300
+    const backgroundColor = 'none'
+
+    const [selectedDateString, setSelectedDateString] = useState(date)
+    const languageDirection = useResolvedDirection(dir, locale)
+
+    const options = {
         locale,
-        width,
-        cellSize,
-        isValid,
-        onDateSelect,
-        minDate,
-        maxDate,
-        validation,
-        format,
-    } = calendarProps
-    let { calendarWeekDays, weekDayLabels } = calendarProps
+        calendar,
+        timeZone,
+        numberingSystem,
+        weekDayFormat,
+    }
+
     const pickerOptions = useDatePicker({
         onDateSelect: (result) => {
+            const { calendarDateString } = result
+            setSelectedDateString(calendarDateString)
             onDateSelect(result)
         },
-        date: date,
-        minDate: minDate,
-        maxDate: maxDate,
-        validation: validation,
-        format: format,
-        options: calendarProps,
+        date: selectedDateString,
+        options,
     })
 
-    if (!calendarWeekDays) {
-        calendarWeekDays = pickerOptions.calendarWeekDays
-    }
-    if (!weekDayLabels) {
-        weekDayLabels = pickerOptions.weekDayLabels
-    }
-    const wrapperBorderColor = colors.grey300
-    const backgroundColor = 'none'
-
-    const languageDirection = useResolvedDirection(dir, locale)
+    const { calendarWeekDays, weekDayLabels } = pickerOptions
 
     return (
         <div>
@@ -54,15 +54,11 @@ export const Calendar = (calendarProps) => {
                 data-test="calendar"
             >
                 <NavigationContainer
-                    pickerOptions={
-                        calendarProps.calendarWeekDays
-                            ? calendarProps
-                            : pickerOptions
-                    }
+                    pickerOptions={pickerOptions}
                     languageDirection={languageDirection}
                 />
                 <CalendarTable
-                    selectedDate={isValid ? date : null}
+                    selectedDate={selectedDateString}
                     calendarWeekDays={calendarWeekDays}
                     weekDayLabels={weekDayLabels}
                     cellSize={cellSize}
@@ -96,6 +92,10 @@ Calendar.defaultProps = {
 }
 
 export const CalendarProps = {
+    /** the calendar to use such gregory, ethiopic, nepali - full supported list here: https://github.com/dhis2/multi-calendar-dates/blob/main/src/constants/calendars.ts  */
+    calendar: PropTypes.any.isRequired,
+    /** Called with signature `(null)` \|\| `({ dateCalendarString: string, dateCalendar: Temporal.ZonedDateTime })` with `dateCalendarString` being the stringified date in the specified calendar in the format `yyyy-MM-dd` */
+    onDateSelect: PropTypes.func.isRequired,
     /** the size of a single cell in the table forming the calendar */
     cellSize: PropTypes.string,
     /** the currently selected date using an iso-like format YYYY-MM-DD, in the calendar system provided (not iso8601) */
@@ -104,18 +104,14 @@ export const CalendarProps = {
     dir: PropTypes.oneOf(['ltr', 'rtl']),
     /** any valid locale -  if none provided, the internal library will fallback to the user locale (more info here: https://github.com/dhis2/multi-calendar-dates/blob/main/src/hooks/internal/useResolvedLocaleOptions.ts#L15) */
     locale: PropTypes.string,
+    /** numbering system to use - full list here https://github.com/dhis2/multi-calendar-dates/blob/main/src/constants/numberingSystems.ts */
+    numberingSystem: PropTypes.string,
+    /** the timeZone to use */
+    timeZone: PropTypes.string,
     /** the format to display for the week day, i.e. Monday (long), Mon (short), M (narrow) */
     weekDayFormat: PropTypes.oneOf(['narrow', 'short', 'long']),
     /** the width of the calendar component */
     width: PropTypes.string,
-    /** is date valid and within range */
-    isValid: PropTypes.bool,
-    calendarWeekDays: PropTypes.array,
-    weekDayLabels: PropTypes.arrayOf(PropTypes.string),
-    minDate: PropTypes.string,
-    maxDate: PropTypes.string,
-    validation: PropTypes.string,
-    format: PropTypes.string,
 }
 
-Calendar.propTypes = { ...CalendarProps }
+Calendar.propTypes = CalendarProps

From e2b9176be156fe9093a11e0d679f691184a572eb Mon Sep 17 00:00:00 2001
From: Mozafar Haider <mozafar@dhis2.org>
Date: Mon, 29 Jul 2024 09:36:17 +0100
Subject: [PATCH 07/11] fix: pass isValid to calendarProps

---
 components/calendar/src/calendar-input/calendar-input.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js
index 29f8033b4e..cb52892925 100644
--- a/components/calendar/src/calendar-input/calendar-input.js
+++ b/components/calendar/src/calendar-input/calendar-input.js
@@ -93,6 +93,7 @@ export const CalendarInput = ({
             maxDate,
             validation, // todo: clarify "validation" type in the hook
             format,
+            isValid: pickerResults.isValid,
             calendarWeekDays: pickerResults.calendarWeekDays,
             weekDayLabels: pickerResults.weekDayLabels,
             currMonth: pickerResults.currMonth,

From e69edb8f054ebcdd9a7b5ee40a81c05bf2855619 Mon Sep 17 00:00:00 2001
From: Mozafar Haider <mozafar@dhis2.org>
Date: Mon, 29 Jul 2024 10:45:09 +0100
Subject: [PATCH 08/11] refactor: extract calendar table into shared component

---
 .../src/calendar-input/calendar-input.js      | 80 +++-------------
 .../src/calendar/calendar-container.js        | 92 +++++++++++++++++++
 .../calendar/src/calendar/calendar-table.js   |  4 +-
 components/calendar/src/calendar/calendar.js  | 69 ++++++--------
 .../src/calendar/navigation-container.js      | 61 ++++++------
 5 files changed, 166 insertions(+), 140 deletions(-)
 create mode 100644 components/calendar/src/calendar/calendar-container.js

diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js
index cb52892925..34be79361c 100644
--- a/components/calendar/src/calendar-input/calendar-input.js
+++ b/components/calendar/src/calendar-input/calendar-input.js
@@ -7,13 +7,10 @@ import {
     useDatePicker,
     useResolvedDirection,
 } from '@dhis2/multi-calendar-dates'
-import { colors } from '@dhis2/ui-constants'
 import cx from 'classnames'
-// import { debounce } from 'lodash'
 import React, { useRef, useState, useMemo, useCallback } from 'react'
-import { CalendarTable } from '../calendar/calendar-table.js'
+import { CalendarContainer } from '../calendar/calendar-container.js'
 import { CalendarProps } from '../calendar/calendar.js'
-import { NavigationContainer } from '../calendar/navigation-container.js'
 import i18n from '../locales/index.js'
 
 const offsetModifier = {
@@ -56,16 +53,11 @@ export const CalendarInput = ({
         [calendar, locale, numberingSystem, timeZone, weekDayFormat]
     )
 
-    const onDateSelect = useCallback(
-        (result, keepPopperOpen = false) => {
-            setOpen(keepPopperOpen)
+    const pickerResults = useDatePicker({
+        onDateSelect: (result) => {
+            setOpen(false)
             parentOnDateSelect?.(result)
         },
-        [parentOnDateSelect]
-    )
-
-    const pickerResults = useDatePicker({
-        onDateSelect,
         date,
         minDate: minDate,
         maxDate: maxDate,
@@ -75,24 +67,20 @@ export const CalendarInput = ({
     })
 
     const handleChange = (e) => {
-        onDateSelect?.({ calendarDateString: e.value })
+        parentOnDateSelect?.({ calendarDateString: e.value })
     }
 
     const onFocus = () => {
         setOpen(true)
     }
 
+    const languageDirection = useResolvedDirection(dir, locale)
+
     const calendarProps = useMemo(() => {
         return {
             date,
-            dir,
-            locale,
             width,
             cellSize,
-            minDate,
-            maxDate,
-            validation, // todo: clarify "validation" type in the hook
-            format,
             isValid: pickerResults.isValid,
             calendarWeekDays: pickerResults.calendarWeekDays,
             weekDayLabels: pickerResults.weekDayLabels,
@@ -102,21 +90,9 @@ export const CalendarInput = ({
             nextYear: pickerResults.nextYear,
             prevMonth: pickerResults.prevMonth,
             prevYear: pickerResults.prevYear,
+            languageDirection,
         }
-    }, [
-        cellSize,
-        date,
-        dir,
-        format,
-        locale,
-        maxDate,
-        minDate,
-        pickerResults,
-        validation,
-        width,
-    ])
-
-    const languageDirection = useResolvedDirection(dir, locale)
+    }, [cellSize, date, pickerResults, width, languageDirection])
 
     return (
         <>
@@ -151,7 +127,7 @@ export const CalendarInput = ({
                             secondary
                             small
                             onClick={() => {
-                                onDateSelect?.(null)
+                                parentOnDateSelect?.(null)
                             }}
                             type="button"
                         >
@@ -172,27 +148,7 @@ export const CalendarInput = ({
                         modifiers={[offsetModifier]}
                     >
                         <Card>
-                            <div
-                                className="calendar-wrapper"
-                                dir={languageDirection}
-                                data-test="calendar"
-                            >
-                                <NavigationContainer
-                                    pickerOptions={calendarProps}
-                                    languageDirection={languageDirection}
-                                />
-                                <CalendarTable
-                                    selectedDate={
-                                        calendarProps.isValid ? date : null
-                                    }
-                                    calendarWeekDays={
-                                        calendarProps.calendarWeekDays
-                                    }
-                                    weekDayLabels={calendarProps.weekDayLabels}
-                                    cellSize={cellSize}
-                                    width={width}
-                                />
-                            </div>
+                            <CalendarContainer {...calendarProps} />
                         </Card>
                     </Popper>
                 </Layer>
@@ -214,20 +170,6 @@ export const CalendarInput = ({
                     .calendar-clear-button.with-dense-wrapper {
                         inset-block-start: 23px;
                     }
-                    .calendar-wrapper {
-                        font-family: Roboto, sans-serif;
-                        font-weight: 400;
-                        font-size: 14px;
-                        background-color: none;
-                        display: flex;
-                        flex-direction: column;
-                        border: 1px solid ${colors.grey300};
-                        border-radius: 3px;
-                        min-width: ${width};
-                        width: max-content;
-                        box-shadow: 0px 4px 6px -2px #2129340d;
-                        box-shadow: 0px 10px 15px -3px #2129341a;
-                    }
                 `}
             </style>
         </>
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 (
+        <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,
+}
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 = ({
     </div>
 )
 
-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 (
         <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>
     )
 }
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

From 0831239aa0acdd484f270f708bc8c54dee4f5dc6 Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Wed, 31 Jul 2024 00:43:35 +0300
Subject: [PATCH 09/11] chore: rename passed props

---
 components/calendar/src/calendar-input/calendar-input.js | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js
index 34be79361c..c670653b9c 100644
--- a/components/calendar/src/calendar-input/calendar-input.js
+++ b/components/calendar/src/calendar-input/calendar-input.js
@@ -8,7 +8,7 @@ import {
     useResolvedDirection,
 } from '@dhis2/multi-calendar-dates'
 import cx from 'classnames'
-import React, { useRef, useState, useMemo, useCallback } from 'react'
+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'
@@ -32,11 +32,10 @@ export const CalendarInput = ({
     width,
     cellSize,
     clearable,
-    editable,
     minDate,
     maxDate,
     format, // todo: props and types for format and validation
-    validation,
+    strictValidation,
     ...rest
 } = {}) => {
     const ref = useRef()
@@ -61,7 +60,7 @@ export const CalendarInput = ({
         date,
         minDate: minDate,
         maxDate: maxDate,
-        validation: validation,
+        strictValidation: strictValidation,
         format: format,
         options: useDatePickerOptions,
     })
@@ -103,7 +102,7 @@ export const CalendarInput = ({
                     type="text"
                     onFocus={onFocus}
                     value={date}
-                    onChange={editable ? handleChange : undefined}
+                    onChange={handleChange}
                     validationText={
                         pickerResults.errorMessage ||
                         pickerResults.warningMessage

From b845ae1529ee12cb3154e4d8078e2ac6df521b5b Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Wed, 31 Jul 2024 21:59:40 +0300
Subject: [PATCH 10/11] fix: label in storybook

---
 .../src/stories/calendar-input.stories.js     | 26 ++++++++-----------
 1 file changed, 11 insertions(+), 15 deletions(-)

diff --git a/components/calendar/src/stories/calendar-input.stories.js b/components/calendar/src/stories/calendar-input.stories.js
index be4a73db98..472489a8f7 100644
--- a/components/calendar/src/stories/calendar-input.stories.js
+++ b/components/calendar/src/stories/calendar-input.stories.js
@@ -22,20 +22,17 @@ export default {
     },
 }
 
-const buildCalendar =
-    ({ date, locale, calendar }) =>
-    () =>
-        (
-            <CalendarStoryWrapper
-                component={CalendarInput}
-                dir="ltr"
-                timeZone="Africa/Khartoum"
-                weekDayFormat="short"
-                date={date}
-                locale={locale}
-                calendar={calendar}
-            />
-        )
+const buildCalendar = ({ date, locale, calendar }) => () => (
+    <CalendarStoryWrapper
+        component={CalendarInput}
+        dir="ltr"
+        timeZone="Africa/Khartoum"
+        weekDayFormat="short"
+        date={date}
+        locale={locale}
+        calendar={calendar}
+    />
+)
 
 export const EthiopicWithAmharic = buildCalendar({
     calendar: 'ethiopic',
@@ -128,7 +125,6 @@ export function CalendarWithEditiableInput() {
                         const date = selectedDate?.calendarDateString
                         setDate(date)
                     }}
-                    label="ooo"
                     width={'700px'}
                     inputWidth="900px"
                     timeZone={'UTC'}

From e77b9ced64724850827c298a9b059d06ea3c8d4d Mon Sep 17 00:00:00 2001
From: Alaa Yahia <alaay873@live.com>
Date: Wed, 31 Jul 2024 22:44:29 +0300
Subject: [PATCH 11/11] fix: lint issue

---
 .../src/stories/calendar-input.stories.js     | 25 +++++++++++--------
 1 file changed, 14 insertions(+), 11 deletions(-)

diff --git a/components/calendar/src/stories/calendar-input.stories.js b/components/calendar/src/stories/calendar-input.stories.js
index 472489a8f7..7d3917ad00 100644
--- a/components/calendar/src/stories/calendar-input.stories.js
+++ b/components/calendar/src/stories/calendar-input.stories.js
@@ -22,17 +22,20 @@ export default {
     },
 }
 
-const buildCalendar = ({ date, locale, calendar }) => () => (
-    <CalendarStoryWrapper
-        component={CalendarInput}
-        dir="ltr"
-        timeZone="Africa/Khartoum"
-        weekDayFormat="short"
-        date={date}
-        locale={locale}
-        calendar={calendar}
-    />
-)
+const buildCalendar =
+    ({ date, locale, calendar }) =>
+    () =>
+        (
+            <CalendarStoryWrapper
+                component={CalendarInput}
+                dir="ltr"
+                timeZone="Africa/Khartoum"
+                weekDayFormat="short"
+                date={date}
+                locale={locale}
+                calendar={calendar}
+            />
+        )
 
 export const EthiopicWithAmharic = buildCalendar({
     calendar: 'ethiopic',