Feat/lokalise rebuild * chore(lokalise): update translation ids * chore(lokalise): easier to switch between projects * chore(lokalise): update translation ids * . * . * . * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * chore(lokalise): new translations * merge * switch to errors for missing id's * merge * sync translations Approved-by: Linus Flood
198 lines
5.8 KiB
TypeScript
198 lines
5.8 KiB
TypeScript
"use client"
|
||
import { useCallback, useEffect, useRef, useState } from "react"
|
||
import { useFormContext, useWatch } from "react-hook-form"
|
||
import { useIntl } from "react-intl"
|
||
|
||
import { longDateFormat } from "@scandic-hotels/common/constants/dateFormats"
|
||
import { dt } from "@scandic-hotels/common/dt"
|
||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||
|
||
import useLang from "../../../hooks/useLang"
|
||
import DatePickerRangeDesktop from "./Range/Desktop"
|
||
import DatePickerRangeMobile from "./Range/Mobile"
|
||
|
||
import styles from "./date-picker.module.css"
|
||
|
||
import type { DateRange } from "react-day-picker"
|
||
|
||
type DatePickerFormProps = {
|
||
name?: string
|
||
}
|
||
export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
|
||
const lang = useLang()
|
||
const intl = useIntl()
|
||
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
const selectedDate = useWatch({ name })
|
||
const { register, setValue } = useFormContext()
|
||
const ref = useRef<HTMLDivElement | null>(null)
|
||
|
||
const close = useCallback(() => {
|
||
if (!selectedDate.toDate) {
|
||
setValue(
|
||
name,
|
||
{
|
||
fromDate: selectedDate.fromDate,
|
||
toDate: dt(selectedDate.fromDate).add(1, "day").format("YYYY-MM-DD"),
|
||
},
|
||
{ shouldDirty: true }
|
||
)
|
||
}
|
||
|
||
setIsOpen(false)
|
||
}, [name, setValue, selectedDate])
|
||
|
||
function showOnFocus() {
|
||
setIsOpen(true)
|
||
}
|
||
|
||
function handleSelectDate(
|
||
_nextRange: DateRange | undefined,
|
||
selectedDay: Date
|
||
) {
|
||
const now = dt()
|
||
const dateClicked = dt(selectedDay)
|
||
const dateClickedFormatted = dateClicked.format("YYYY-MM-DD")
|
||
/* 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 (!dateClicked.isBefore(now, "day")) {
|
||
// Handle form value updates based on the requirements
|
||
if (selectedDate.fromDate && selectedDate.toDate) {
|
||
// Both dates were previously selected, starting fresh with new date
|
||
setValue(
|
||
name,
|
||
{
|
||
fromDate: dateClickedFormatted,
|
||
toDate: undefined,
|
||
},
|
||
{ shouldDirty: true }
|
||
)
|
||
} else if (selectedDate.fromDate && !selectedDate.toDate) {
|
||
// If the selected day is the same as the first date, we don't need to update the form value
|
||
if (dateClicked.isSame(selectedDate.fromDate)) {
|
||
return
|
||
}
|
||
// We're selecting the second date
|
||
if (dateClicked.isBefore(selectedDate.fromDate)) {
|
||
// If second selected date is before first date, swap them
|
||
setValue(
|
||
name,
|
||
{
|
||
fromDate: dateClickedFormatted,
|
||
toDate: selectedDate.fromDate,
|
||
},
|
||
{ shouldDirty: true }
|
||
)
|
||
} else {
|
||
// If second selected date is after first date, keep order
|
||
setValue(
|
||
name,
|
||
{
|
||
fromDate: selectedDate.fromDate,
|
||
toDate: dateClickedFormatted,
|
||
},
|
||
{ shouldDirty: 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(longDateFormat[lang])
|
||
const selectedToDate = !!selectedDate.toDate
|
||
? dt(selectedDate.toDate).locale(lang).format(longDateFormat[lang])
|
||
: ""
|
||
|
||
return (
|
||
<div
|
||
className={styles.container}
|
||
onBlur={(e) => {
|
||
closeOnBlur(e.nativeEvent)
|
||
}}
|
||
data-datepicker-open={isOpen}
|
||
ref={ref}
|
||
>
|
||
<button
|
||
className={styles.btn}
|
||
onFocus={showOnFocus}
|
||
onClick={() => setIsOpen(true)}
|
||
type="button"
|
||
>
|
||
<Typography variant="Body/Paragraph/mdRegular" className={styles.body}>
|
||
<span>
|
||
{intl.formatMessage(
|
||
{
|
||
id: "booking.selectedDateRange",
|
||
defaultMessage: "{selectedFromDate} – {selectedToDate}",
|
||
},
|
||
{
|
||
selectedFromDate,
|
||
selectedToDate,
|
||
}
|
||
)}
|
||
</span>
|
||
</Typography>
|
||
</button>
|
||
<input {...register("date.fromDate")} type="hidden" />
|
||
<input {...register("date.toDate")} type="hidden" />
|
||
<div aria-modal className={styles.hideWrapper} role="dialog">
|
||
<DatePickerRangeDesktop
|
||
close={close}
|
||
handleOnSelect={handleSelectDate}
|
||
// DayPicker lib needs Daterange in form as below to show appropriate UI
|
||
selectedRange={{
|
||
from: dt(selectedDate.fromDate).toDate(),
|
||
to: selectedDate.toDate
|
||
? dt(selectedDate.toDate).toDate()
|
||
: undefined,
|
||
}}
|
||
/>
|
||
{isOpen && (
|
||
<DatePickerRangeMobile
|
||
close={close}
|
||
handleOnSelect={handleSelectDate}
|
||
// DayPicker lib needs Daterange in form as below to show appropriate UI
|
||
selectedRange={{
|
||
from: dt(selectedDate.fromDate).toDate(),
|
||
to: selectedDate.toDate
|
||
? dt(selectedDate.toDate).toDate()
|
||
: undefined,
|
||
}}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|