feat(SW-350): Implemented tooltip and booking widget tablet design

This commit is contained in:
Pontus Dreij
2024-10-04 10:00:52 +02:00
parent 04406d3865
commit aee9e94f7a
17 changed files with 530 additions and 137 deletions

View File

@@ -0,0 +1,77 @@
"use client"
import { useIntl } from "react-intl"
import inputStyles from "@/components/Forms/BookingWidget/FormContent/Search/search.module.css"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
import styles from "./voucher.module.css"
export default function Voucher() {
const intl = useIntl()
const vouchers = intl.formatMessage({ id: "Code / Voucher" })
const useVouchers = intl.formatMessage({ id: "Use code/voucher" })
const addVouchers = intl.formatMessage({ id: "Add code" })
const bonus = intl.formatMessage({ id: "Use bonus cheque" })
const reward = intl.formatMessage({ id: "Book reward night" })
const disabledBookingOptionsHeader = intl.formatMessage({
id: "Disabled booking options header",
})
const disabledBookingOptionsText = intl.formatMessage({
id: "Disabled booking options text",
})
return (
<div className={styles.optionsContainer}>
<Tooltip
heading={disabledBookingOptionsHeader}
text={disabledBookingOptionsText}
position="bottom"
arrow="left"
>
<div className={styles.vouchers}>
<div className={styles.vouchersHeader}>
<Caption color="disabled" textTransform="bold">
{vouchers}
</Caption>
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
</div>
<Body asChild>
<input
type="text"
placeholder={addVouchers}
className={inputStyles.input}
disabled
/>
</Body>
</div>
</Tooltip>
<Tooltip
heading={disabledBookingOptionsHeader}
text={disabledBookingOptionsText}
position="bottom"
arrow="left"
>
<div className={styles.options}>
<label className={`${styles.option} ${styles.checkboxVoucher}`}>
<input type="checkbox" disabled className={styles.checkbox} />
<Caption color="disabled">{useVouchers}</Caption>
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
</label>
<label className={styles.option}>
<input type="checkbox" disabled className={styles.checkbox} />
<Caption color="disabled">{bonus}</Caption>
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
</label>
<label className={styles.option}>
<input type="checkbox" disabled className={styles.checkbox} />
<Caption color="disabled">{reward}</Caption>
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
</label>
</div>
</Tooltip>
</div>
)
}

View File

@@ -0,0 +1,82 @@
.vouchers {
display: block;
}
.options {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
}
.option {
display: flex;
gap: var(--Spacing-x2);
margin-top: var(--Spacing-x2);
align-items: center;
}
.vouchers {
width: 100%;
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
border-radius: var(--Corner-radius-Small);
}
.optionsContainer {
display: flex;
flex-direction: column;
}
.checkbox {
width: var(--Spacing-x3);
height: var(--Spacing-x3);
}
.checkboxVoucher {
display: none;
}
@media screen and (min-width: 768px) {
.vouchers {
display: none;
}
.options {
flex-direction: row;
gap: var(--Spacing-x4);
}
.option {
margin-top: 0;
gap: var(--Spacing-x-one-and-half);
}
.checkboxVoucher {
display: flex;
}
}
@media screen and (max-width: 1366px) {
.vouchers {
background-color: var(--Base-Background-Primary-Normal);
border-radius: var(--Corner-radius-Medium);
}
}
@media screen and (min-width: 1367px) {
.vouchers {
display: block;
max-width: 200px;
}
.options {
flex-direction: column;
max-width: 190px;
gap: 0;
}
.vouchers:hover,
.option:hover {
cursor: not-allowed;
}
.optionsContainer {
flex-direction: row;
}
.checkboxVoucher {
display: none;
}
}

View File

@@ -1,24 +1,7 @@
.options {
flex-direction: column;
justify-content: center;
width: 100%;
}
.option {
display: flex;
gap: var(--Spacing-x-one-and-half);
align-items: center;
}
.infoIcon { .infoIcon {
stroke: var(--Base-Text-Disabled); stroke: var(--Base-Text-Disabled);
} }
.vouchers,
.options {
display: none;
}
.vouchersHeader { .vouchersHeader {
display: flex; display: flex;
gap: var(--Spacing-x-one-and-half); gap: var(--Spacing-x-one-and-half);
@@ -28,9 +11,19 @@
width: var(--Spacing-x3); width: var(--Spacing-x3);
height: var(--Spacing-x3); height: var(--Spacing-x3);
} }
.icon,
.voucherRow {
display: none;
}
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
.input { .voucherContainer {
padding: var(--Spacing-x2) 0 var(--Spacing-x4);
}
}
@media screen and (max-width: 1367px) {
.inputContainer {
display: grid; display: grid;
gap: var(--Spacing-x2); gap: var(--Spacing-x2);
} }
@@ -49,36 +42,39 @@
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
} }
.options { .button {
gap: var(--Spacing-x2); align-self: flex-end;
margin-top: var(--Spacing-x2); justify-content: center;
} width: 100%;
.option {
gap: var(--Spacing-x2);
} }
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.input { .input {
display: flex; display: flex;
align-items: center;
}
.inputContainer {
display: flex;
flex: 2;
gap: var(--Spacing-x2); gap: var(--Spacing-x2);
} }
.voucherContainer {
flex: 1;
}
.rooms, .rooms,
.vouchers,
.when, .when,
.where { .where {
width: 100%; width: 100%;
} }
.input input[type="text"] { .inputContainer input[type="text"] {
border: none; border: none;
height: 24px; height: var(--Spacing-x3);
} }
.rooms, .rooms,
.vouchers,
.when { .when {
max-width: 240px; max-width: 240px;
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
@@ -90,30 +86,43 @@
background-color: var(--Base-Surface-Primary-light-Hover-alt); background-color: var(--Base-Surface-Primary-light-Hover-alt);
} }
.vouchers {
max-width: 200px;
}
.where { .where {
max-width: 280px; max-width: 280px;
position: relative; position: relative;
} }
.options { .button {
max-width: 190px; justify-content: center;
width: 118px;
} }
} }
@media screen and (min-width: 1367px) { @media screen and (min-width: 768px) and (max-width: 1366px) {
.vouchers { .inputContainer {
display: block; padding: var(--Spacing-x2);
} }
.buttonContainer {
.options { padding-right: var(--Spacing-x2);
}
.input .buttonContainer .button {
padding: var(--Spacing-x1);
width: var(--Spacing-x6);
height: var(--Spacing-x6);
}
.buttonText {
display: none;
}
.icon {
display: flex; display: flex;
} }
.vouchers:hover,
.option:hover { .voucherRow {
cursor: not-allowed; display: flex;
background: var(--Base-Surface-Primary-light-Hover);
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
padding: var(--Spacing-x2);
}
.voucherContainer {
display: none;
} }
} }

View File

@@ -1,85 +1,93 @@
"use client" "use client"
import React from "react"
import { useWatch } from "react-hook-form" import { useWatch } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import DatePicker from "@/components/DatePicker" import DatePicker from "@/components/DatePicker"
import { InfoCircleIcon } from "@/components/Icons" import { SearchIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Search from "./Search" import Search from "./Search"
import Voucher from "./Voucher"
import styles from "./formContent.module.css" import styles from "./formContent.module.css"
import tempStyles from "./Search/search.module.css" // TODO: Remove this when Rooms and Voucher is implemented import inputStyles from "./Search/search.module.css"
import type { BookingWidgetFormContentProps } from "@/types/components/form/bookingwidget" import type { BookingWidgetFormContentProps } from "@/types/components/form/bookingwidget"
export default function FormContent({ export default function FormContent({
locations, locations,
formId,
formState,
}: BookingWidgetFormContentProps) { }: BookingWidgetFormContentProps) {
const intl = useIntl() const intl = useIntl()
const selectedDate = useWatch({ name: "date" }) const selectedDate = useWatch({ name: "date" })
const rooms = intl.formatMessage({ id: "Guests & Rooms" }) const rooms = intl.formatMessage({ id: "Guests & Rooms" })
const vouchers = intl.formatMessage({ id: "Code / Voucher" })
const addVouchers = intl.formatMessage({ id: "Add code" })
const bonus = intl.formatMessage({ id: "Use bonus cheque" })
const reward = intl.formatMessage({ id: "Book reward night" })
const nights = dt(selectedDate.to).diff(dt(selectedDate.from), "days") const nights = dt(selectedDate.to).diff(dt(selectedDate.from), "days")
return ( return (
<div className={styles.input}> <>
<div className={styles.where}> <div className={styles.input}>
<Search locations={locations} /> <div className={styles.inputContainer}>
</div> <div className={styles.where}>
<div className={styles.when}> <Search locations={locations} />
<Caption color="red" textTransform="bold"> </div>
{intl.formatMessage( <div className={styles.when}>
{ id: "booking.nights" }, <Caption color="red" textTransform="bold">
{ totalNights: nights } {intl.formatMessage(
)} { id: "booking.nights" },
</Caption> { totalNights: nights }
<DatePicker /> )}
</div> </Caption>
<div className={styles.rooms}> <DatePicker />
<Caption color="red" textTransform="bold"> </div>
{rooms} <div className={styles.rooms}>
</Caption> <Caption color="red" textTransform="bold">
<Body asChild> {rooms}
<input type="text" placeholder={rooms} className={tempStyles.input} /> </Caption>
</Body> <Body asChild>
</div> <input
<div className={styles.vouchers}> type="text"
<div className={styles.vouchersHeader}> placeholder={rooms}
<Caption color="disabled" textTransform="bold"> className={inputStyles.input}
{vouchers} />
</Caption> </Body>
<InfoCircleIcon color="white" className={styles.infoIcon} /> </div>
</div>
<div className={styles.voucherContainer}>
<Voucher />
</div>
<div className={styles.buttonContainer}>
<Button
className={styles.button}
disabled={!formState.isValid}
form={formId}
intent="primary"
theme="base"
type="submit"
>
<Caption
color="white"
textTransform="bold"
className={styles.buttonText}
>
{intl.formatMessage({ id: "Search" })}
</Caption>
<div className={styles.icon}>
<SearchIcon color="white" width={28} height={28} />
</div>
</Button>
</div> </div>
<Body asChild>
<input
type="text"
placeholder={addVouchers}
className={tempStyles.input}
disabled
/>
</Body>
</div> </div>
<div className={styles.options}> <div className={styles.voucherRow}>
<label className={styles.option}> <Voucher />
<input type="checkbox" disabled className={styles.checkbox} />
<Caption color="disabled">{bonus}</Caption>
<InfoCircleIcon color="white" className={styles.infoIcon} />
</label>
<label className={styles.option}>
<input type="checkbox" disabled className={styles.checkbox} />
<Caption color="disabled">{reward}</Caption>
<InfoCircleIcon color="white" className={styles.infoIcon} />
</label>
</div> </div>
</div> </>
) )
} }

View File

@@ -8,7 +8,6 @@
.form { .form {
display: grid; display: grid;
gap: var(--Spacing-x2);
width: 100%; width: 100%;
} }
@@ -16,27 +15,23 @@
.form { .form {
align-self: flex-start; align-self: flex-start;
} }
.button {
align-self: flex-end;
justify-content: center;
width: 100%;
}
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.section { .section {
display: flex; display: flex;
padding: var(--Spacing-x1) var(--Spacing-x5);
}
.button {
justify-content: center;
width: 118px;
} }
.default { .default {
border-radius: var(--Corner-radius-Medium); border-radius: var(--Corner-radius-Medium);
}
}
@media screen and (min-width: 1367px) {
.section {
padding: var(--Spacing-x1) var(--Spacing-x5);
}
.default {
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) padding: var(--Spacing-x-one-and-half) var(--Spacing-x2)
var(--Spacing-x-one-and-half) var(--Spacing-x1); var(--Spacing-x-one-and-half) var(--Spacing-x1);
} }

View File

@@ -1,10 +1,6 @@
"use client" "use client"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import FormContent from "./FormContent" import FormContent from "./FormContent"
import { bookingWidgetVariants } from "./variants" import { bookingWidgetVariants } from "./variants"
@@ -17,7 +13,6 @@ import type { BookingWidgetFormProps } from "@/types/components/form/bookingwidg
const formId = "booking-widget" const formId = "booking-widget"
export default function Form({ locations, type }: BookingWidgetFormProps) { export default function Form({ locations, type }: BookingWidgetFormProps) {
const intl = useIntl()
const router = useRouter() const router = useRouter()
const classNames = bookingWidgetVariants({ const classNames = bookingWidgetVariants({
@@ -43,20 +38,12 @@ export default function Form({ locations, type }: BookingWidgetFormProps) {
id={formId} id={formId}
> >
<input {...register("location")} type="hidden" /> <input {...register("location")} type="hidden" />
<FormContent locations={locations} /> <FormContent
locations={locations}
formId={formId}
formState={formState}
/>
</form> </form>
<Button
className={styles.button}
disabled={!formState.isValid}
form={formId}
intent="primary"
theme="base"
type="submit"
>
<Caption color="white" textTransform="bold">
{intl.formatMessage({ id: "Search" })}
</Caption>
</Button>
</section> </section>
) )
} }

View File

@@ -0,0 +1,30 @@
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { tooltipVariants } from "./variants"
import styles from "./tooltip.module.css"
import { TooltipPosition, TooltipProps } from "@/types/components/tooltip"
export function Tooltip<P extends TooltipPosition>({
heading,
text,
position,
arrow,
children,
}: TooltipProps<P>) {
const className = tooltipVariants({ position, arrow })
return (
<div className={styles.tooltipContainer} role="tooltip" aria-label={text}>
<div className={className}>
{heading && (
<Caption textTransform="bold" color="white">
{heading}
</Caption>
)}
{text && <Caption color="white">{text}</Caption>}
</div>
{children}
</div>
)
}

View File

@@ -0,0 +1,137 @@
.tooltipContainer {
position: relative;
display: inline-block;
}
.tooltip {
padding: var(--Spacing-x1);
background-color: var(--UI-Text-Active);
border: 0.5px solid var(--UI-Border-Active);
border-radius: var(--Corner-radius-Medium);
color: var(--Base-Text-Inverted);
position: absolute;
visibility: hidden;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s;
max-width: 200px;
}
.tooltipContainer:hover .tooltip {
visibility: visible;
opacity: 1;
}
.left {
right: 100%;
}
.right {
left: 100%;
}
.top {
bottom: 100%;
}
.bottom {
top: 100%;
}
.tooltip::before {
content: "";
position: absolute;
border-style: solid;
}
.bottom.arrowLeft::before {
top: -8px;
left: 16px;
border-width: 0 7px 8px 7px;
border-color: transparent transparent var(--UI-Text-Active) transparent;
}
.bottom.arrowCenter::before {
top: -8px;
left: 50%;
transform: translateX(-50%);
border-width: 0 7px 8px 7px;
border-color: transparent transparent var(--UI-Text-Active) transparent;
}
.bottom.arrowRight::before {
top: -8px;
right: 16px;
border-width: 0 7px 8px 7px;
border-color: transparent transparent var(--UI-Text-Active) transparent;
}
.top.arrowLeft::before {
bottom: -8px;
left: 16px;
border-width: 8px 7px 0 7px;
border-color: var(--UI-Text-Active) transparent transparent transparent;
}
.top.arrowCenter::before {
bottom: -8px;
left: 50%;
transform: translateX(-50%);
border-width: 8px 7px 0 7px;
border-color: var(--UI-Text-Active) transparent transparent transparent;
}
.top.arrowRight::before {
bottom: -8px;
right: 16px;
border-width: 8px 7px 0 7px;
border-color: var(--UI-Text-Active) transparent transparent transparent;
}
.left.arrowTop::before {
top: 16px;
right: -8px;
transform: translateY(-50%);
border-width: 7px 0 7px 8px;
border-color: transparent transparent transparent var(--UI-Text-Active);
}
.left.arrowCenter::before {
top: 50%;
right: -8px;
transform: translateY(-50%);
border-width: 7px 0 7px 8px;
border-color: transparent transparent transparent var(--UI-Text-Active);
}
.left.arrowBottom::before {
bottom: 16px;
right: -8px;
transform: translateY(50%);
border-width: 7px 0 7px 8px;
border-color: transparent transparent transparent var(--UI-Text-Active);
}
.right.arrowTop::before {
top: 16px;
left: -8px;
transform: translateY(-50%);
border-width: 7px 8px 7px 0;
border-color: transparent var(--UI-Text-Active) transparent transparent;
}
.right.arrowCenter::before {
top: 50%;
left: -8px;
transform: translateY(-50%);
border-width: 7px 8px 7px 0;
border-color: transparent var(--UI-Text-Active) transparent transparent;
}
.right.arrowBottom::before {
bottom: 16px;
left: -8px;
transform: translateY(50%);
border-width: 7px 8px 7px 0;
border-color: transparent var(--UI-Text-Active) transparent transparent;
}

View File

@@ -0,0 +1,21 @@
import { cva } from "class-variance-authority"
import styles from "./tooltip.module.css"
export const tooltipVariants = cva(styles.tooltip, {
variants: {
position: {
left: styles.left,
right: styles.right,
top: styles.top,
bottom: styles.bottom,
},
arrow: {
left: styles.arrowLeft,
right: styles.arrowRight,
center: styles.arrowCenter,
top: styles.arrowTop,
bottom: styles.arrowBottom,
},
},
})

View File

@@ -18,7 +18,7 @@
"As our Close Friend": "Som vores nære ven", "As our Close Friend": "Som vores nære ven",
"At latest": "Senest", "At latest": "Senest",
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Attraktioner", "Attraction": "Attraktion",
"Back to scandichotels.com": "Tilbage til scandichotels.com", "Back to scandichotels.com": "Tilbage til scandichotels.com",
"Bed type": "Seng type", "Bed type": "Seng type",
"Book": "Book", "Book": "Book",
@@ -60,6 +60,8 @@
"Day": "Dag", "Day": "Dag",
"Description": "Beskrivelse", "Description": "Beskrivelse",
"Destinations & hotels": "Destinationer & hoteller", "Destinations & hotels": "Destinationer & hoteller",
"Disabled booking options header": "Vi beklager",
"Disabled booking options text": "Koder, checks og bonusnætter er endnu ikke tilgængelige på den nye hjemmeside.",
"Discard changes": "Kassér ændringer", "Discard changes": "Kassér ændringer",
"Discard unsaved changes?": "Slette ændringer, der ikke er gemt?", "Discard unsaved changes?": "Slette ændringer, der ikke er gemt?",
"Distance to city centre": "{number}km til centrum", "Distance to city centre": "{number}km til centrum",
@@ -228,6 +230,7 @@
"Type of bed": "Sengtype", "Type of bed": "Sengtype",
"Type of room": "Værelsestype", "Type of room": "Værelsestype",
"Use bonus cheque": "Brug Bonus Cheque", "Use bonus cheque": "Brug Bonus Cheque",
"Use code/voucher": "Brug kode/voucher",
"User information": "Brugeroplysninger", "User information": "Brugeroplysninger",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kort", "View as map": "Vis som kort",

View File

@@ -18,7 +18,7 @@
"As our Close Friend": "Als unser enger Freund", "As our Close Friend": "Als unser enger Freund",
"At latest": "Spätestens", "At latest": "Spätestens",
"At the hotel": "Im Hotel", "At the hotel": "Im Hotel",
"Attractions": "Attraktionen", "Attraction": "Attraktion",
"Back to scandichotels.com": "Zurück zu scandichotels.com", "Back to scandichotels.com": "Zurück zu scandichotels.com",
"Bed type": "Bettentyp", "Bed type": "Bettentyp",
"Book": "Buchen", "Book": "Buchen",
@@ -60,6 +60,8 @@
"Day": "Tag", "Day": "Tag",
"Description": "Beschreibung", "Description": "Beschreibung",
"Destinations & hotels": "Reiseziele & Hotels", "Destinations & hotels": "Reiseziele & Hotels",
"Disabled booking options header": "Es tut uns leid",
"Disabled booking options text": "Codes, Schecks und Bonusnächte sind auf der neuen Website noch nicht verfügbar.",
"Discard changes": "Änderungen verwerfen", "Discard changes": "Änderungen verwerfen",
"Discard unsaved changes?": "Nicht gespeicherte Änderungen verwerfen?", "Discard unsaved changes?": "Nicht gespeicherte Änderungen verwerfen?",
"Distance to city centre": "{number}km zum Stadtzentrum", "Distance to city centre": "{number}km zum Stadtzentrum",
@@ -227,6 +229,7 @@
"Type of bed": "Bettentyp", "Type of bed": "Bettentyp",
"Type of room": "Zimmerart", "Type of room": "Zimmerart",
"Use bonus cheque": "Bonusscheck nutzen", "Use bonus cheque": "Bonusscheck nutzen",
"Use code/voucher": "Code/Gutschein nutzen",
"User information": "Nutzerinformation", "User information": "Nutzerinformation",
"View as list": "Als Liste anzeigen", "View as list": "Als Liste anzeigen",
"View as map": "Als Karte anzeigen", "View as map": "Als Karte anzeigen",

View File

@@ -61,6 +61,8 @@
"Description": "Description", "Description": "Description",
"Destinations & hotels": "Destinations & hotels", "Destinations & hotels": "Destinations & hotels",
"Destination": "Destination", "Destination": "Destination",
"Disabled booking options header": "We're sorry",
"Disabled booking options text": "Codes, cheques and reward nights aren't available on the new website yet.",
"Discard changes": "Discard changes", "Discard changes": "Discard changes",
"Discard unsaved changes?": "Discard unsaved changes?", "Discard unsaved changes?": "Discard unsaved changes?",
"Distance to city centre": "{number}km to city centre", "Distance to city centre": "{number}km to city centre",
@@ -230,6 +232,7 @@
"Type of bed": "Type of bed", "Type of bed": "Type of bed",
"Type of room": "Type of room", "Type of room": "Type of room",
"Use bonus cheque": "Use bonus cheque", "Use bonus cheque": "Use bonus cheque",
"Use code/voucher": "Use code/voucher",
"User information": "User information", "User information": "User information",
"View as list": "View as list", "View as list": "View as list",
"View as map": "View as map", "View as map": "View as map",

View File

@@ -60,6 +60,8 @@
"Day": "Päivä", "Day": "Päivä",
"Description": "Kuvaus", "Description": "Kuvaus",
"Destinations & hotels": "Kohteet ja hotellit", "Destinations & hotels": "Kohteet ja hotellit",
"Disabled booking options header": "Olemme pahoillamme",
"Disabled booking options text": "Koodit, sekit ja palkintoillat eivät ole vielä saatavilla uudella verkkosivustolla.",
"Discard changes": "Hylkää muutokset", "Discard changes": "Hylkää muutokset",
"Discard unsaved changes?": "Hylkäätkö tallentamattomat muutokset?", "Discard unsaved changes?": "Hylkäätkö tallentamattomat muutokset?",
"Distance to city centre": "{number}km Etäisyys kaupunkiin", "Distance to city centre": "{number}km Etäisyys kaupunkiin",
@@ -228,6 +230,7 @@
"Type of bed": "Vuodetyyppi", "Type of bed": "Vuodetyyppi",
"Type of room": "Huonetyyppi", "Type of room": "Huonetyyppi",
"Use bonus cheque": "Käytä bonussekkiä", "Use bonus cheque": "Käytä bonussekkiä",
"Use code/voucher": "Käytä koodia/voucheria",
"User information": "Käyttäjän tiedot", "User information": "Käyttäjän tiedot",
"View as list": "Näytä listana", "View as list": "Näytä listana",
"View as map": "Näytä kartalla", "View as map": "Näytä kartalla",

View File

@@ -60,6 +60,8 @@
"Day": "Dag", "Day": "Dag",
"Description": "Beskrivelse", "Description": "Beskrivelse",
"Destinations & hotels": "Destinasjoner og hoteller", "Destinations & hotels": "Destinasjoner og hoteller",
"Disabled booking options header": "Vi beklager",
"Disabled booking options text": "Koder, checks og belønningsnætter er enda ikke tilgjengelige på den nye nettsiden.",
"Discard changes": "Forkaste endringer", "Discard changes": "Forkaste endringer",
"Discard unsaved changes?": "Forkaste endringer som ikke er lagret?", "Discard unsaved changes?": "Forkaste endringer som ikke er lagret?",
"Distance to city centre": "{number}km til sentrum", "Distance to city centre": "{number}km til sentrum",
@@ -228,6 +230,7 @@
"Type of bed": "Sengtype", "Type of bed": "Sengtype",
"Type of room": "Romtype", "Type of room": "Romtype",
"Use bonus cheque": "Bruk bonussjekk", "Use bonus cheque": "Bruk bonussjekk",
"Use code/voucher": "Bruk kode/voucher",
"User information": "Brukerinformasjon", "User information": "Brukerinformasjon",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kart", "View as map": "Vis som kart",

View File

@@ -60,6 +60,8 @@
"Day": "Dag", "Day": "Dag",
"Description": "Beskrivning", "Description": "Beskrivning",
"Destinations & hotels": "Destinationer & hotell", "Destinations & hotels": "Destinationer & hotell",
"Disabled booking options header": "Vi beklagar",
"Disabled booking options text": "Koder, bonuscheckar och belöningsnätter är inte tillgängliga på den nya webbplatsen än.",
"Discard changes": "Ignorera ändringar", "Discard changes": "Ignorera ändringar",
"Discard unsaved changes?": "Vill du ignorera ändringar som inte har sparats?", "Discard unsaved changes?": "Vill du ignorera ändringar som inte har sparats?",
"Distance to city centre": "{number}km till centrum", "Distance to city centre": "{number}km till centrum",
@@ -227,7 +229,8 @@
"Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)", "Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)",
"Type of bed": "Sängtyp", "Type of bed": "Sängtyp",
"Type of room": "Rumstyp", "Type of room": "Rumstyp",
"Use bonus cheque": "Use bonus cheque", "Use bonus cheque": "Använd bonuscheck",
"Use code/voucher": "Använd kod/voucher",
"User information": "Användarinformation", "User information": "Användarinformation",
"View as list": "Visa som lista", "View as list": "Visa som lista",
"View as map": "Visa som karta", "View as map": "Visa som karta",

View File

@@ -1,4 +1,9 @@
import type { BookingWidgetType } from "@/types/components/bookingWidget" import { FormState, UseFormReturn } from "react-hook-form"
import type {
BookingWidgetSchema,
BookingWidgetType,
} from "@/types/components/bookingWidget"
import type { Location, Locations } from "@/types/trpc/routers/hotel/locations" import type { Location, Locations } from "@/types/trpc/routers/hotel/locations"
export interface BookingWidgetFormProps { export interface BookingWidgetFormProps {
@@ -8,6 +13,8 @@ export interface BookingWidgetFormProps {
export interface BookingWidgetFormContentProps { export interface BookingWidgetFormContentProps {
locations: Locations locations: Locations
formId: string
formState: FormState<BookingWidgetSchema>
} }
export enum ActionType { export enum ActionType {

View File

@@ -0,0 +1,22 @@
export type TooltipPosition = "left" | "right" | "top" | "bottom"
type VerticalArrow = "top" | "bottom" | "center"
type HorizontalArrow = "left" | "right" | "center"
type ValidArrowMap = {
left: VerticalArrow
right: VerticalArrow
top: HorizontalArrow
bottom: HorizontalArrow
}
type ValidArrow<P extends TooltipPosition> = P extends keyof ValidArrowMap
? ValidArrowMap[P]
: never
export interface TooltipProps<P extends TooltipPosition = TooltipPosition> {
heading?: string
text?: string
position: P
arrow: ValidArrow<P>
children: React.ReactNode
}