feat: add desktop ui to calendar
This commit is contained in:
@@ -2,14 +2,24 @@
|
||||
import { da, de, fi, nb, sv } from "date-fns/locale"
|
||||
import { useState } from "react"
|
||||
import { type DateRange, DayPicker } from "react-day-picker"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import { ChevronLeftIcon } from "../Icons"
|
||||
|
||||
import styles from "./date-picker.module.css"
|
||||
import classNames from "react-day-picker/style.module.css"
|
||||
|
||||
import type { DatePickerProps } from "@/types/components/datepicker"
|
||||
|
||||
const locales = {
|
||||
[Lang.da]: da,
|
||||
[Lang.de]: de,
|
||||
@@ -18,12 +28,8 @@ const locales = {
|
||||
[Lang.sv]: sv,
|
||||
}
|
||||
|
||||
export interface DatePickerProps {
|
||||
handleOnSelect: (selected: DateRange) => void
|
||||
initialSelected?: DateRange
|
||||
}
|
||||
|
||||
export default function DatePicker({
|
||||
close,
|
||||
handleOnSelect,
|
||||
initialSelected = {
|
||||
from: undefined,
|
||||
@@ -31,6 +37,7 @@ export default function DatePicker({
|
||||
},
|
||||
}: DatePickerProps) {
|
||||
const lang = useLang()
|
||||
const intl = useIntl()
|
||||
const [selectedDate, setSelectedDate] = useState<DateRange>(initialSelected)
|
||||
|
||||
function handleSelectDate(selected: DateRange) {
|
||||
@@ -40,23 +47,99 @@ export default function DatePicker({
|
||||
|
||||
/** English is default language and doesn't need to be imported */
|
||||
const locale = lang === Lang.en ? undefined : locales[lang]
|
||||
|
||||
const currentDate = dt().toDate()
|
||||
const startOfMonth = dt(currentDate).set("date", 1).toDate()
|
||||
const yesterday = dt(currentDate).subtract(1, "day").toDate()
|
||||
return (
|
||||
<DayPicker
|
||||
classNames={classNames}
|
||||
classNames={{
|
||||
...classNames,
|
||||
caption_label: `${classNames.caption_label} ${styles.captionLabel}`,
|
||||
day: `${classNames.day} ${styles.day}`,
|
||||
day_button: `${classNames.day_button} ${styles.dayButton}`,
|
||||
footer: styles.footer,
|
||||
month_caption: `${classNames.month_caption} ${styles.monthCaption}`,
|
||||
months: `${classNames.months} ${styles.months}`,
|
||||
range_end: styles.rangeEnd,
|
||||
range_middle: styles.rangeMiddle,
|
||||
range_start: styles.rangeStart,
|
||||
week: styles.week,
|
||||
weekday: `${classNames.weekday} ${styles.weekDay}`,
|
||||
}}
|
||||
disabled={{ from: startOfMonth, to: yesterday }}
|
||||
excludeDisabled
|
||||
footer
|
||||
formatters={{
|
||||
formatWeekdayName(weekday) {
|
||||
return dt(weekday).locale(lang).format("ddd")
|
||||
},
|
||||
}}
|
||||
lang={lang}
|
||||
locale={locale}
|
||||
mode="range"
|
||||
numberOfMonths={2}
|
||||
onSelect={handleSelectDate}
|
||||
pagedNavigation
|
||||
required
|
||||
selected={selectedDate}
|
||||
showWeekNumber
|
||||
startMonth={currentDate}
|
||||
weekStartsOn={1}
|
||||
components={{
|
||||
Chevron(props) {
|
||||
return <ChevronLeftIcon {...props} height={20} width={20} />
|
||||
},
|
||||
Footer(props) {
|
||||
return (
|
||||
<>
|
||||
<Divider className={styles.divider} color="primaryLightSubtle" />
|
||||
<footer className={props.className}>
|
||||
<Button
|
||||
intent="tertiary"
|
||||
onPress={close}
|
||||
size="small"
|
||||
theme="base"
|
||||
>
|
||||
<Caption color="white" textTransform="bold">
|
||||
{intl.formatMessage({ id: "Select dates" })}
|
||||
</Caption>
|
||||
</Button>
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
},
|
||||
MonthCaption(props) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<Subtitle asChild type="two">
|
||||
{props.children}
|
||||
</Subtitle>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
Nav(props) {
|
||||
if (Array.isArray(props.children)) {
|
||||
const prevButton = props.children?.[0]
|
||||
const nextButton = props.children?.[1]
|
||||
return (
|
||||
<>
|
||||
{prevButton ? (
|
||||
<nav
|
||||
className={`${props.className} ${styles.previousButton}`}
|
||||
>
|
||||
{prevButton}
|
||||
</nav>
|
||||
) : null}
|
||||
{nextButton ? (
|
||||
<nav className={`${props.className} ${styles.nextButton}`}>
|
||||
{nextButton}
|
||||
</nav>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
return <></>
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,16 @@
|
||||
|
||||
.hideWrapper {
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--Spacing-x-one-and-half);
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
||||
position: absolute;
|
||||
/** BookingWidget padding + border-width */
|
||||
top: calc(100% + var(--Spacing-x2) + 1px);
|
||||
/**
|
||||
BookingWidget padding +
|
||||
border-width +
|
||||
wanted space below booking widget
|
||||
*/
|
||||
top: calc(100% + var(--Spacing-x2) + 1px + var(--Spacing-x4));
|
||||
}
|
||||
|
||||
.btn {
|
||||
@@ -29,3 +33,118 @@
|
||||
.body {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
div.months {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.monthCaption {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.captionLabel {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
td.day,
|
||||
td.rangeEnd,
|
||||
td.rangeStart {
|
||||
font-family: var(--typography-Body-Bold-fontFamily);
|
||||
font-size: var(--typography-Body-Bold-fontSize);
|
||||
font-weight: 500;
|
||||
letter-spacing: var(--typography-Body-Bold-letterSpacing);
|
||||
line-height: var(--typography-Body-Bold-lineHeight);
|
||||
text-decoration: var(--typography-Body-Bold-textDecoration);
|
||||
}
|
||||
|
||||
td.rangeEnd,
|
||||
td.rangeStart {
|
||||
background: var(--Base-Background-Primary-Normal);
|
||||
}
|
||||
|
||||
td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) {
|
||||
border-radius: 0 50% 50% 0;
|
||||
}
|
||||
|
||||
td.rangeStart[aria-selected="true"] {
|
||||
border-radius: 50% 0 0 50%;
|
||||
}
|
||||
|
||||
td.rangeEnd[aria-selected="true"] button.dayButton:hover,
|
||||
td.rangeStart[aria-selected="true"] button.dayButton:hover {
|
||||
background: var(--Primary-Light-On-Surface-Accent);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) button.dayButton,
|
||||
td.rangeStart[aria-selected="true"]:not([data-outside="true"])
|
||||
button.dayButton {
|
||||
background: var(--Primary-Light-On-Surface-Accent);
|
||||
border: none;
|
||||
color: var(--Base-Button-Inverted-Fill-Normal);
|
||||
}
|
||||
|
||||
td.day,
|
||||
td.day[data-today="true"] {
|
||||
color: var(--UI-Text-High-contrast);
|
||||
height: 40px;
|
||||
padding: var(--Spacing-x-half);
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
td.day button.dayButton:hover {
|
||||
background: var(--Base-Surface-Secondary-light-Hover);
|
||||
}
|
||||
|
||||
td.day[data-outside="true"] button.dayButton {
|
||||
border: none;
|
||||
}
|
||||
|
||||
td.day:not(td.rangeEnd, td.rangeStart)[aria-selected="true"],
|
||||
td.rangeMiddle[aria-selected="true"] button.dayButton {
|
||||
background: var(--Base-Background-Primary-Normal);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
td.day[data-disabled="true"],
|
||||
td.day[data-disabled="true"] button.dayButton,
|
||||
td.day[data-outside="true"] ~ td.day[data-disabled="true"],
|
||||
td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton,
|
||||
.week:has(td.day[data-outside="true"] ~ td.day[data-disabled="true"])
|
||||
td.day[data-outside="true"]
|
||||
button.dayButton {
|
||||
background: none;
|
||||
color: var(--Base-Text-Disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.weekDay {
|
||||
color: var(--UI-Text-Placeholder);
|
||||
font-family: var(--typography-Footnote-Labels-fontFamily);
|
||||
font-size: var(--typography-Footnote-Labels-fontSize);
|
||||
font-weight: var(--typography-Footnote-Labels-fontWeight);
|
||||
letter-spacing: var(--typography-Footnote-Labels-letterSpacing);
|
||||
line-height: var(--typography-Footnote-Labels-lineHeight);
|
||||
text-decoration: var(--typography-Footnote-Labels-textDecoration);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.nextButton {
|
||||
transform: rotate(180deg);
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.previousButton {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
|
||||
const { register, setValue } = useFormContext()
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
function close() {
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
function handleOnClick() {
|
||||
setIsOpen((prevIsOpen) => !prevIsOpen)
|
||||
}
|
||||
@@ -64,6 +68,7 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
|
||||
<input {...register("date.to")} type="hidden" />
|
||||
<div aria-modal className={styles.hideWrapper} role="dialog">
|
||||
<DatePicker
|
||||
close={close}
|
||||
handleOnSelect={handleSelectDate}
|
||||
initialSelected={selectedDate}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user