feat:SW-1422 Updated Tablet mode

This commit is contained in:
Hrishikesh Vaipurkar
2025-02-03 23:34:04 +01:00
parent 63f456da5a
commit 304fc7f1c9
7 changed files with 185 additions and 125 deletions

View File

@@ -0,0 +1,25 @@
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import Input from "@/components/TempDesignSystem/Form/Input"
import type { BookingWidgetSchema } from "@/types/components/bookingWidget"
export default function TabletCodeInput({
updateValue,
defaultValue,
}: {
updateValue: (val: string) => void
defaultValue?: string
}) {
const intl = useIntl()
const { register } = useFormContext<BookingWidgetSchema>()
return (
<Input
label={intl.formatMessage({ id: "Add code" })}
{...register("bookingCode.value", {
onChange: (e) => updateValue(e.target.value),
})}
/>
)
}

View File

@@ -31,20 +31,46 @@
width: 100%; width: 100%;
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 767px) {
.hideOnMobile { .hideOnMobile {
display: none; display: none;
} }
} }
@media screen and (min-width: 768px) {
.bookingCodeRememberVisible {
align-items: center;
background: var(--Base-Surface-Primary-light-Normal);
justify-content: space-between;
border-radius: var(--Spacing-x-one-and-half);
}
}
@media screen and (min-width: 768px) and (max-width: 1367px) {
.container {
display: flex;
gap: var(--Spacing-x1);
}
.codePopover {
background: var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Spacing-x-one-and-half);
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
padding: var(--Spacing-x2);
width: 320px;
}
.popover {
display: grid;
gap: var(--Spacing-x2);
}
.bookingCodeRememberVisible {
position: static;
}
}
@media screen and (min-width: 1367px) { @media screen and (min-width: 1367px) {
.bookingCodeRememberVisible { .bookingCodeRememberVisible {
padding: var(--Spacing-x2); padding: var(--Spacing-x2);
border-radius: var(--Spacing-x-one-and-half);
width: 320px; width: 320px;
background: white;
top: calc(100% + 24px); top: calc(100% + 24px);
justify-content: space-between;
align-items: center;
} }
} }

View File

@@ -13,6 +13,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Input from "../Input" import Input from "../Input"
import TabletCodeInput from "./TabletCodeInput"
import styles from "./booking-code.module.css" import styles from "./booking-code.module.css"
@@ -33,6 +34,7 @@ export default function BookingCode() {
setValue, setValue,
formState: { errors }, formState: { errors },
getValues, getValues,
register,
} = useFormContext<BookingWidgetSchema>() } = useFormContext<BookingWidgetSchema>()
const bookingCode: BookingCodeSchema = getValues("bookingCode") const bookingCode: BookingCodeSchema = getValues("bookingCode")
@@ -42,9 +44,6 @@ export default function BookingCode() {
const codeError = errors["bookingCode"]?.value const codeError = errors["bookingCode"]?.value
const codeVoucher = intl.formatMessage({ id: "Code / Voucher" }) const codeVoucher = intl.formatMessage({ id: "Code / Voucher" })
const addCode = intl.formatMessage({ id: "Add code" }) const addCode = intl.formatMessage({ id: "Add code" })
const bookingCodeTooltipText = intl.formatMessage({
id: "If you're booking a promotional offer or a Corporate negotiated rate you'll need a special booking code. Don't use any special characters such as (.) (,) (-) (:). If you would like to make a booking with code VOF, please call us +46 8 517 517 20.Save your booking code for the next time you visit the page by ticking the box “Remember”. Don't tick the box if you're using a public computer to avoid unauthorized access to your booking code.",
})
const ref = useRef<HTMLDivElement | null>(null) const ref = useRef<HTMLDivElement | null>(null)
function updateBookingCodeFormValue(value: string) { function updateBookingCodeFormValue(value: string) {
@@ -52,13 +51,10 @@ export default function BookingCode() {
} }
function toggleModal(isOpen: boolean) { function toggleModal(isOpen: boolean) {
if (!isOpen && codeError) { if (!isOpen && !bookingCode?.value) {
// console.log("Cannot be closed!!")
// setValue("bookingCode.flag", false)
} else if (!isOpen && !bookingCode?.value) {
setValue("bookingCode.flag", false) setValue("bookingCode.flag", false)
setIsOpen(isOpen) setIsOpen(isOpen)
} else { } else if (!codeError || isOpen) {
setIsOpen(isOpen) setIsOpen(isOpen)
if (isOpen || bookingCode?.value) { if (isOpen || bookingCode?.value) {
setValue("bookingCode.flag", true) setValue("bookingCode.flag", true)
@@ -106,97 +102,70 @@ export default function BookingCode() {
}, [closeIfOutside, showRemember]) }, [closeIfOutside, showRemember])
return isTablet ? ( return isTablet ? (
<> <div className={styles.container}>
<DialogTrigger isOpen={isOpen} onOpenChange={toggleModal}> <DialogTrigger isOpen={isOpen} onOpenChange={toggleModal}>
<Button type="button" intent="text"> <Button type="button" intent="text">
<Checkbox name="bookingCode.flag" checked={!!bookingCode?.value}> <Checkbox
checked={!!bookingCode?.value}
{...register("bookingCode.flag", {
onChange: function (e) {
if (bookingCode?.value || isOpen) {
setValue("bookingCode.flag", true)
}
},
})}
>
<Caption color="uiTextMediumContrast" type="bold" asChild> <Caption color="uiTextMediumContrast" type="bold" asChild>
<span>{codeVoucher}</span> <span>{codeVoucher}</span>
</Caption> </Caption>
</Checkbox> </Checkbox>
</Button> </Button>
<Popover <Popover
className="guests_picker_popover" className={styles.codePopover}
placement="bottom start" placement="bottom start"
offset={36} offset={36}
> >
<Dialog className={styles.pickerContainerDesktop}> <Dialog>
{({ close }) => ( {({ close }) => (
<div className={styles.popover}> <div className={styles.popover}>
<Input <TabletCodeInput
type="text" updateValue={updateBookingCodeFormValue}
placeholder={addCode}
name="bookingCode"
id="booking-code"
onChange={(event) =>
updateBookingCodeFormValue(event.target.value)
}
defaultValue={bookingCode?.value} defaultValue={bookingCode?.value}
/> />
{codeError?.message ? ( <div className={styles.bookingCodeRememberVisible}>
<Caption color="red" className={styles.error}> <CodeRemember
<ErrorCircleIcon color="red" className={styles.errorIcon} /> bookingCodeValue={bookingCode?.value}
{intl.formatMessage({ id: codeError.message })} onApplyClick={close}
</Caption> />
) : null}
<div className={styles.bookingCode}>
<Checkbox name="bookingCode.remember">
<Caption asChild>
<span>{intl.formatMessage({ id: "Remember code" })}</span>
</Caption>
</Checkbox>
<Button
size="small"
intent="primary"
type="button"
onClick={close}
disabled={codeError ? true : undefined}
>
{intl.formatMessage({ id: "Apply" })}
</Button>
</div> </div>
</div> </div>
)} )}
</Dialog> </Dialog>
</Popover> </Popover>
</DialogTrigger> </DialogTrigger>
</> <CodeRulesModal />
</div>
) : ( ) : (
<div <div
className={styles.container} className={styles.container}
ref={ref} ref={ref}
onFocus={showRememberCheck}
onBlur={(e) => closeIfOutside(e.nativeEvent.relatedTarget as HTMLElement)} onBlur={(e) => closeIfOutside(e.nativeEvent.relatedTarget as HTMLElement)}
> >
<div className={styles.bookingCodeLabel}> <div className={styles.bookingCodeLabel}>
<Caption color="uiTextMediumContrast" type="bold" asChild> <Caption color="uiTextMediumContrast" type="bold" asChild>
<span>{codeVoucher}</span> <span>{codeVoucher}</span>
</Caption> </Caption>
<Modal <CodeRulesModal />
trigger={
<Button intent="text">
<InfoCircleIcon
color="uiTextPlaceholder"
height={20}
width={20}
/>
</Button>
}
title={codeVoucher}
>
<Body color="uiTextHighContrast">{bookingCodeTooltipText}</Body>
</Modal>
</div> </div>
<Input <Input
type="text" className="input"
type="search"
placeholder={addCode} placeholder={addCode}
name="bookingCode" name="bookingCode.value"
id="booking-code" id="booking-code"
onChange={(event) => updateBookingCodeFormValue(event.target.value)} onChange={(event) => updateBookingCodeFormValue(event.target.value)}
defaultValue={bookingCode?.value} defaultValue={bookingCode?.value}
onFocus={showRememberCheck}
onBlur={(e) =>
closeIfOutside(e.nativeEvent.relatedTarget as HTMLElement)
}
/> />
{codeError?.message ? ( {codeError?.message ? (
<Caption color="red" className={styles.error}> <Caption color="red" className={styles.error}>
@@ -212,25 +181,12 @@ export default function BookingCode() {
: styles.bookingCodeRemember : styles.bookingCodeRemember
} }
> >
<Checkbox name="bookingCode.remember"> <CodeRemember
<Caption asChild> bookingCodeValue={bookingCode?.value}
<span>{intl.formatMessage({ id: "Remember code" })}</span> onApplyClick={() => setShowRemember(false)}
</Caption> />
</Checkbox>
{bookingCode?.value ? (
<Button
size="small"
className={styles.hideOnMobile}
intent="secondary"
type="button"
onClick={() => setShowRemember(false)}
>
{intl.formatMessage({ id: "Apply" })}
</Button>
) : null}
</div> </div>
) : null} ) : (
{!isDesktop ? (
<div <div
className={ className={
showRememberMobile showRememberMobile
@@ -244,7 +200,58 @@ export default function BookingCode() {
</Caption> </Caption>
</Switch> </Switch>
</div> </div>
) : null} )}
</div> </div>
) )
} }
type CodeRememberProps = {
bookingCodeValue: string | undefined
onApplyClick: () => void
}
function CodeRulesModal() {
const intl = useIntl()
const codeVoucher = intl.formatMessage({ id: "Code / Voucher" })
const bookingCodeTooltipText = intl.formatMessage({
id: "If you're booking a promotional offer or a Corporate negotiated rate you'll need a special booking code. Don't use any special characters such as (.) (,) (-) (:). If you would like to make a booking with code VOF, please call us +46 8 517 517 20.Save your booking code for the next time you visit the page by ticking the box “Remember”. Don't tick the box if you're using a public computer to avoid unauthorized access to your booking code.",
})
return (
<Modal
trigger={
<Button intent="text">
<InfoCircleIcon color="uiTextPlaceholder" height={20} width={20} />
</Button>
}
title={codeVoucher}
>
<Body color="uiTextHighContrast">{bookingCodeTooltipText}</Body>
</Modal>
)
}
function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) {
const intl = useIntl()
return (
<>
<Checkbox name="bookingCode.remember">
<Caption asChild>
<span>{intl.formatMessage({ id: "Remember code" })}</span>
</Caption>
</Checkbox>
{bookingCodeValue ? (
<Button
size="small"
className={styles.hideOnMobile}
intent="secondary"
type="button"
onClick={onApplyClick}
>
{intl.formatMessage({ id: "Apply" })}
</Button>
) : null}
</>
)
}

View File

@@ -14,7 +14,6 @@ import styles from "./voucher.module.css"
export default function Voucher() { export default function Voucher() {
const intl = useIntl() const intl = useIntl()
const useVouchers = intl.formatMessage({ id: "Use code/voucher" })
const bonus = intl.formatMessage({ id: "Use bonus cheque" }) const bonus = intl.formatMessage({ id: "Use bonus cheque" })
const reward = intl.formatMessage({ id: "Book reward night" }) const reward = intl.formatMessage({ id: "Book reward night" })
@@ -32,14 +31,6 @@ export default function Voucher() {
<BookingCode /> <BookingCode />
</div> </div>
<div className={styles.options}> <div className={styles.options}>
<div className={`${styles.option} ${styles.checkboxVoucher}`}>
<Checkbox name="useVouchers" registerOptions={{ disabled: true }}>
<Caption color="disabled" asChild>
<span>{useVouchers}</span>
</Caption>
</Checkbox>
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
</div>
<div className={styles.option}> <div className={styles.option}>
<Tooltip <Tooltip
heading={disabledBookingOptionsHeader} heading={disabledBookingOptionsHeader}
@@ -91,17 +82,16 @@ export function VoucherSkeleton() {
<div className={styles.optionsContainer}> <div className={styles.optionsContainer}>
<div className={styles.vouchers}> <div className={styles.vouchers}>
<label> <label>
<Caption color="disabled" type="bold" asChild> <Caption type="bold" asChild>
<span>{vouchers}</span> <span>{vouchers}</span>
</Caption> </Caption>
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
</label> </label>
<Input type="text" placeholder={addVouchers} disabled /> <Input type="text" placeholder={addVouchers} disabled />
</div> </div>
<div className={styles.options}> <div className={styles.options}>
<div className={`${styles.option} ${styles.checkboxVoucher}`}> <div className={`${styles.option} ${styles.checkboxVoucher}`}>
<Checkbox name="useVouchers" registerOptions={{ disabled: true }}> <Checkbox name="useVouchers">
<Caption color="disabled" asChild> <Caption asChild>
<span>{useVouchers}</span> <span>{useVouchers}</span>
</Caption> </Caption>
</Checkbox> </Checkbox>

View File

@@ -28,10 +28,6 @@
height: 24px; height: 24px;
} }
.checkboxVoucher {
display: none;
}
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
.vouchers { .vouchers {
margin-bottom: var(--Spacing-x5); margin-bottom: var(--Spacing-x5);
@@ -39,9 +35,6 @@
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.vouchers {
display: none;
}
.options { .options {
flex-direction: row; flex-direction: row;
gap: var(--Spacing-x4); gap: var(--Spacing-x4);
@@ -50,8 +43,15 @@
margin-top: 0; margin-top: 0;
gap: var(--Spacing-x-one-and-half); gap: var(--Spacing-x-one-and-half);
} }
.checkboxVoucher { .optionsContainer {
display: flex; display: grid;
grid-template-columns: auto auto;
column-gap: var(--Spacing-x2);
}
.vouchers:hover,
.vouchers:focus-within,
.vouchers:has([data-focused="true"], [data-pressed="true"]) {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
} }
} }
@@ -72,15 +72,7 @@
max-width: 190px; max-width: 190px;
gap: var(--Spacing-x-half); gap: var(--Spacing-x-half);
} }
.vouchers:hover,
.option:hover { .option:hover {
cursor: not-allowed; cursor: not-allowed;
} }
.optionsContainer {
flex-direction: row;
align-items: center;
}
.checkboxVoucher {
display: none;
}
} }

View File

@@ -7,8 +7,7 @@
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
.icon, .icon {
.voucherRow {
display: none; display: none;
} }
@@ -17,6 +16,10 @@
position: relative; position: relative;
} }
.showOnTablet {
display: none;
}
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
.voucherContainer { .voucherContainer {
padding: var(--Spacing-x2) 0 var(--Spacing-x4); padding: var(--Spacing-x2) 0 var(--Spacing-x4);
@@ -103,9 +106,13 @@
} }
@media screen and (min-width: 768px) and (max-width: 1366px) { @media screen and (min-width: 768px) and (max-width: 1366px) {
.input {
flex-wrap: wrap;
}
.inputContainer { .inputContainer {
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x2) padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x2)
var(--Layout-Tablet-Margin-Margin-min); var(--Layout-Tablet-Margin-Margin-min);
flex-basis: 80%;
} }
.buttonContainer { .buttonContainer {
padding-right: var(--Layout-Tablet-Margin-Margin-min); padding-right: var(--Layout-Tablet-Margin-Margin-min);
@@ -128,7 +135,11 @@
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
padding: var(--Spacing-x2) var(--Layout-Tablet-Margin-Margin-min); padding: var(--Spacing-x2) var(--Layout-Tablet-Margin-Margin-min);
} }
.voucherContainer {
.showOnTablet {
display: flex;
}
.hideOnTablet {
display: none; display: none;
} }
} }

View File

@@ -61,10 +61,23 @@ export default function FormContent({
<GuestsRoomsPickerForm /> <GuestsRoomsPickerForm />
</div> </div>
</div> </div>
<div className={styles.voucherContainer}> <div className={`${styles.buttonContainer} ${styles.showOnTablet}`}>
<Button
className={styles.button}
form={formId}
intent="primary"
theme="base"
type="submit"
>
<span className={styles.icon}>
<SearchIcon color="white" width={28} height={28} />
</span>
</Button>
</div>
<div className={`${styles.voucherContainer} ${styles.voucherRow}`}>
<Voucher /> <Voucher />
</div> </div>
<div className={styles.buttonContainer}> <div className={`${styles.buttonContainer} ${styles.hideOnTablet}`}>
<Button <Button
className={styles.button} className={styles.button}
form={formId} form={formId}
@@ -86,10 +99,6 @@ export default function FormContent({
</Button> </Button>
</div> </div>
</div> </div>
{/* ToDo: Remove below additional voucher element which is only used in tablet mode better to have submit button twice instead */}
<div className={styles.voucherRow}>
<Voucher />
</div>
</> </>
) )
} }