feat: add desktop ui to calendar

This commit is contained in:
Simon Emanuelsson
2024-08-23 16:17:35 +02:00
parent a5dff7b97d
commit 99b14304d4
17 changed files with 405 additions and 177 deletions
+91 -8
View File
@@ -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 <></>
},
}}
/>
)
}
+124 -5
View File
@@ -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;
}
+5
View File
@@ -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}
/>