Files
web/components/DatePicker/index.tsx
Niclas Edenvin a69d14ff61 Merged in fix/sw-1011-calendar (pull request #975)
fix(SW-1011): when only choosing one date on mobile

* fix(SW-1011): when only choosing one date on mobile

When choosing only from date on mobile the to date is set to the next
day. This was only the case on desktop before, no it's on mobile too.

* Remove unnecessary dependency


Approved-by: Pontus Dreij
Approved-by: Hrishikesh Vaipurkar
2024-11-27 08:38:29 +00:00

165 lines
4.6 KiB
TypeScript

"use client"
import { da, de, fi, nb, sv } from "date-fns/locale"
import { useCallback, useEffect, useRef, useState } from "react"
import { useFormContext, useWatch } from "react-hook-form"
import { Lang } from "@/constants/languages"
import { dt } from "@/lib/dt"
import Body from "@/components/TempDesignSystem/Text/Body"
import useLang from "@/hooks/useLang"
import DatePickerDesktop from "./Screen/Desktop"
import DatePickerMobile from "./Screen/Mobile"
import styles from "./date-picker.module.css"
import type { DatePickerFormProps } from "@/types/components/datepicker"
const locales = {
[Lang.da]: da,
[Lang.de]: de,
[Lang.fi]: fi,
[Lang.no]: nb,
[Lang.sv]: sv,
}
export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
const lang = useLang()
const [isOpen, setIsOpen] = useState(false)
const selectedDate = useWatch({ name })
const { register, setValue } = useFormContext()
const ref = useRef<HTMLDivElement | null>(null)
const [isSelectingFrom, setIsSelectingFrom] = useState(true)
const close = useCallback(() => {
if (!selectedDate.toDate) {
setValue(name, {
fromDate: selectedDate.fromDate,
toDate: dt(selectedDate.fromDate).add(1, "day").format("YYYY-MM-DD"),
})
setIsSelectingFrom(true)
}
setIsOpen(false)
}, [name, setValue, selectedDate])
function showOnFocus() {
setIsOpen(true)
}
function handleSelectDate(selected: Date) {
/* check if selected date is not before todays date,
which happens when "Enter" key is pressed in any other input field of the form */
if (!dt(selected).isBefore(dt(), "day")) {
if (isSelectingFrom) {
setValue(name, {
fromDate: dt(selected).format("YYYY-MM-DD"),
toDate: undefined,
})
setIsSelectingFrom(false)
} else if (!dt(selectedDate.fromDate).isSame(dt(selected))) {
const fromDate = dt(selectedDate.fromDate)
const toDate = dt(selected)
if (toDate.isAfter(fromDate)) {
setValue(name, {
fromDate: selectedDate.fromDate,
toDate: toDate.format("YYYY-MM-DD"),
})
} else {
setValue(name, {
fromDate: toDate.format("YYYY-MM-DD"),
toDate: selectedDate.fromDate,
})
}
setIsSelectingFrom(true)
}
}
}
const closeIfOutside = useCallback(
(target: HTMLElement) => {
if (ref.current && target && !ref.current.contains(target)) {
close()
}
},
[close, ref]
)
function closeOnBlur(evt: FocusEvent) {
if (isOpen) {
const target = evt.relatedTarget as HTMLElement
closeIfOutside(target)
}
}
useEffect(() => {
function handleClickOutside(evt: Event) {
if (isOpen) {
const target = evt.target as HTMLElement
closeIfOutside(target)
}
}
document.body.addEventListener("click", handleClickOutside)
return () => {
document.body.removeEventListener("click", handleClickOutside)
}
}, [closeIfOutside, isOpen])
const selectedFromDate = dt(selectedDate.fromDate)
.locale(lang)
.format("ddd D MMM")
const selectedToDate = !!selectedDate.toDate
? dt(selectedDate.toDate).locale(lang).format("ddd D MMM")
: ""
return (
<div
className={styles.container}
onBlur={(e) => {
closeOnBlur(e.nativeEvent)
}}
data-isopen={isOpen}
ref={ref}
>
<button
className={styles.btn}
onFocus={showOnFocus}
onClick={() => setIsOpen(true)}
type="button"
>
<Body className={styles.body} asChild>
<span>
{selectedFromDate} - {selectedToDate}
</span>
</Body>
</button>
<input {...register("date.fromDate")} type="hidden" />
<input {...register("date.toDate")} type="hidden" />
<div aria-modal className={styles.hideWrapper} role="dialog">
<DatePickerDesktop
close={close}
handleOnSelect={handleSelectDate}
locales={locales}
// DayPicker lib needs Daterange in form as below to show appropriate UI
selectedDate={{
from: selectedDate.fromDate,
to: selectedDate.toDate,
}}
/>
<DatePickerMobile
close={close}
handleOnSelect={handleSelectDate}
locales={locales}
// DayPicker lib needs Daterange in form as below to show appropriate UI
selectedDate={{
from: selectedDate.fromDate,
to: selectedDate.toDate,
}}
/>
</div>
</div>
)
}