Files
web/packages/booking-flow/lib/components/BookingWidget/DatePicker/index.tsx
Joakim Jäderberg aafad9781f Merged in feat/lokalise-rebuild (pull request #2993)
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
2025-10-22 11:00:03 +00:00

198 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
)
}