feat: add desktop ui to calendar
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
.container {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
border-top: 1px solid var(--Base-Border-Subtle);
|
||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x5);
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1367px) {
|
||||
@media screen and (min-width: 1367px) {
|
||||
.container {
|
||||
display: none;
|
||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||
border-top: 1px solid var(--Base-Border-Subtle);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
height: 24px;
|
||||
outline: none;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ export default function FormContent({
|
||||
</div>
|
||||
<div className={styles.when}>
|
||||
<Caption color="red" textTransform="bold">
|
||||
{nights}{" "}
|
||||
{nights > 1
|
||||
? intl.formatMessage({ id: "nights" })
|
||||
: intl.formatMessage({ id: "night" })}
|
||||
{intl.formatMessage(
|
||||
{ id: "booking.nights" },
|
||||
{ totalNights: nights }
|
||||
)}
|
||||
</Caption>
|
||||
<DatePicker />
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,9 @@ import { z } from "zod"
|
||||
import type { Location } from "@/types/trpc/routers/hotel/locations"
|
||||
|
||||
export const bookingWidgetSchema = z.object({
|
||||
search: z.string({ coerce: true }).min(1, "Required"),
|
||||
bookingCode: z.string(), // Update this as required when working with booking codes component
|
||||
date: z.object({
|
||||
// Update this as required once started working with Date picker in Nights component
|
||||
from: z.string(),
|
||||
to: z.string(),
|
||||
}),
|
||||
@@ -23,9 +24,7 @@ export const bookingWidgetSchema = z.object({
|
||||
},
|
||||
{ message: "Required" }
|
||||
),
|
||||
bookingCode: z.string(), // Update this as required when working with booking codes component
|
||||
redemption: z.boolean().default(false),
|
||||
voucher: z.boolean().default(false),
|
||||
rooms: z.array(
|
||||
// This will be updated when working in guests component
|
||||
z.object({
|
||||
@@ -38,4 +37,6 @@ export const bookingWidgetSchema = z.object({
|
||||
),
|
||||
})
|
||||
),
|
||||
search: z.string({ coerce: true }).min(1, "Required"),
|
||||
voucher: z.boolean().default(false),
|
||||
})
|
||||
|
||||
@@ -69,19 +69,19 @@ a.default {
|
||||
}
|
||||
|
||||
/* SIZES */
|
||||
.small {
|
||||
.btn.small {
|
||||
gap: var(--Spacing-x-quarter);
|
||||
height: 40px;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.medium {
|
||||
.btn.medium {
|
||||
gap: var(--Spacing-x-half);
|
||||
height: 48px;
|
||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.large {
|
||||
.btn.large {
|
||||
gap: var(--Spacing-x-half);
|
||||
height: 56px;
|
||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
||||
@@ -170,19 +170,19 @@ a.default {
|
||||
fill: var(--Base-Button-Secondary-On-Fill-Disabled);
|
||||
}
|
||||
|
||||
.baseTertiary {
|
||||
.btn.baseTertiary {
|
||||
background-color: var(--Base-Button-Tertiary-Fill-Normal);
|
||||
color: var(--Base-Button-Tertiary-On-Fill-Normal);
|
||||
}
|
||||
|
||||
.baseTertiary:active,
|
||||
.baseTertiary:focus,
|
||||
.baseTertiary:hover {
|
||||
.btn.baseTertiary:active,
|
||||
.btn.baseTertiary:focus,
|
||||
.btn.baseTertiary:hover {
|
||||
background-color: var(--Base-Button-Tertiary-Fill-Hover);
|
||||
color: var(--Base-Button-Tertiary-On-Fill-Hover);
|
||||
}
|
||||
|
||||
.baseTertiary:disabled {
|
||||
.btn.baseTertiary:disabled {
|
||||
background-color: var(--Base-Button-Tertiary-Fill-Disabled);
|
||||
color: var(--Base-Button-Tertiary-On-Fill-Disabled);
|
||||
}
|
||||
@@ -800,4 +800,4 @@ a.default {
|
||||
.icon.tertiaryLightSecondary:disabled svg,
|
||||
.icon.tertiaryLightSecondary:disabled svg * {
|
||||
fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
border-bottom-color: var(--Base-Border-Subtle);
|
||||
}
|
||||
|
||||
.primaryLightSubtle {
|
||||
border-bottom-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
}
|
||||
|
||||
.opacity100 {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import styles from "./divider.module.css"
|
||||
export const dividerVariants = cva(styles.divider, {
|
||||
variants: {
|
||||
color: {
|
||||
burgundy: styles.burgundy,
|
||||
peach: styles.peach,
|
||||
beige: styles.beige,
|
||||
white: styles.white,
|
||||
subtle: styles.subtle,
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
peach: styles.peach,
|
||||
primaryLightSubtle: styles.primaryLightSubtle,
|
||||
subtle: styles.subtle,
|
||||
white: styles.white,
|
||||
},
|
||||
opacity: {
|
||||
100: styles.opacity100,
|
||||
|
||||
Reference in New Issue
Block a user