Skeleton loader for booking widget on desktop
This commit is contained in:
@@ -1,17 +1,11 @@
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
import styles from "./loading.module.css"
|
||||
import BookingWidgetSkeleton from "@/components/BookingWidget/BookingWidgetSkeleton"
|
||||
|
||||
export default function LoadingBookingWidget() {
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
)
|
||||
return <BookingWidgetSkeleton />
|
||||
}
|
||||
|
||||
90
components/BookingWidget/BookingWidgetSkeleton.tsx
Normal file
90
components/BookingWidget/BookingWidgetSkeleton.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { SearchIcon } from "@/components/Icons"
|
||||
|
||||
import { SearchSkeleton } from "../Forms/BookingWidget/FormContent/Search"
|
||||
import { VoucherSkeleton } from "../Forms/BookingWidget/FormContent/Voucher"
|
||||
import { bookingWidgetVariants } from "../Forms/BookingWidget/variants"
|
||||
import SkeletonShimmer from "../SkeletonShimmer"
|
||||
import Button from "../TempDesignSystem/Button"
|
||||
import Caption from "../TempDesignSystem/Text/Caption"
|
||||
|
||||
import formStyles from "../Forms/BookingWidget/form.module.css"
|
||||
import formContentStyles from "../Forms/BookingWidget/FormContent/formContent.module.css"
|
||||
import widgetStyles from "./bookingWidget.module.css"
|
||||
|
||||
export default function BookingWidgetSkeleton() {
|
||||
const intl = useIntl()
|
||||
|
||||
const classNames = bookingWidgetVariants({
|
||||
type: "full",
|
||||
})
|
||||
|
||||
return (
|
||||
<section className={widgetStyles.containerDesktop}>
|
||||
<section className={classNames}>
|
||||
<form className={formStyles.form}>
|
||||
<div className={formContentStyles.input}>
|
||||
<div className={formContentStyles.inputContainer}>
|
||||
<div className={formContentStyles.where}>
|
||||
<SearchSkeleton />
|
||||
</div>
|
||||
<div className={formContentStyles.when}>
|
||||
<Caption color="red" type="bold">
|
||||
{intl.formatMessage(
|
||||
{ id: "booking.nights" },
|
||||
{ totalNights: 0 }
|
||||
)}
|
||||
</Caption>
|
||||
<SkeletonShimmer />
|
||||
</div>
|
||||
<div className={formContentStyles.rooms}>
|
||||
<Caption color="red" type="bold" asChild>
|
||||
<span>{intl.formatMessage({ id: "Guests & Rooms" })}</span>
|
||||
</Caption>
|
||||
<SkeletonShimmer />
|
||||
</div>
|
||||
</div>
|
||||
<div className={formContentStyles.voucherContainer}>
|
||||
<VoucherSkeleton />
|
||||
</div>
|
||||
<div className={formContentStyles.buttonContainer}>
|
||||
<Button
|
||||
className={formContentStyles.button}
|
||||
intent="primary"
|
||||
theme="base"
|
||||
type="submit"
|
||||
disabled
|
||||
>
|
||||
<Caption
|
||||
color="white"
|
||||
type="bold"
|
||||
className={formContentStyles.buttonText}
|
||||
asChild
|
||||
>
|
||||
<span>{intl.formatMessage({ id: "Search" })}</span>
|
||||
</Caption>
|
||||
<span className={formContentStyles.icon}>
|
||||
<SearchIcon color="white" width={28} height={28} />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{/* <section className={styles.containerMobile} data-open={isOpen}>
|
||||
<button
|
||||
className={styles.close}
|
||||
onClick={closeMobileSearch}
|
||||
type="button"
|
||||
>
|
||||
<CloseLargeIcon />
|
||||
</button>
|
||||
<Form locations={locations} type={type} />
|
||||
</section>
|
||||
<div className={styles.backdrop} onClick={closeMobileSearch} />
|
||||
<MobileToggleButton openMobileSearch={openMobileSearch} /> */}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { useFormContext, useWatch } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import Input from "../Input"
|
||||
@@ -203,3 +204,18 @@ export default function Search({ locations }: SearchProps) {
|
||||
</Downshift>
|
||||
)
|
||||
}
|
||||
|
||||
export function SearchSkeleton() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.label}>
|
||||
<Caption type="bold" color="red" asChild>
|
||||
<span>Where to</span>
|
||||
</Caption>
|
||||
</div>
|
||||
<div className={styles.input}>
|
||||
<SkeletonShimmer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
|
||||
@@ -78,3 +79,54 @@ export default function Voucher() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function VoucherSkeleton() {
|
||||
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 form = useForm()
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<div className={styles.optionsContainer}>
|
||||
<div className={styles.vouchers}>
|
||||
<label>
|
||||
<Caption color="disabled" type="bold" asChild>
|
||||
<span>{vouchers}</span>
|
||||
</Caption>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</label>
|
||||
<Input type="text" placeholder={addVouchers} disabled />
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div className={styles.option}>
|
||||
<Checkbox name="useBonus" registerOptions={{ disabled: true }}>
|
||||
<Caption color="disabled" asChild>
|
||||
<span>{bonus}</span>
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className={styles.option}>
|
||||
<Checkbox name="useReward" registerOptions={{ disabled: true }}>
|
||||
<Caption color="disabled" asChild>
|
||||
<span>{reward}</span>
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useIntl } from "react-intl"
|
||||
import useDropdownStore from "@/stores/main-menu"
|
||||
|
||||
import { ChevronDownSmallIcon } from "@/components/Icons"
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import useClickOutside from "@/hooks/useClickOutside"
|
||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||
@@ -73,3 +74,13 @@ export default function MyPagesMenu({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function MyPagesMenuSkeleton() {
|
||||
return (
|
||||
<MainMenuButton>
|
||||
<Avatar />
|
||||
<SkeletonShimmer width="10ch" />
|
||||
<ChevronDownSmallIcon className={`${styles.chevron}`} color="red" />
|
||||
</MainMenuButton>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import LoginButton from "@/components/LoginButton"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import Avatar from "../Avatar"
|
||||
import MyPagesMenu from "../MyPagesMenu"
|
||||
import MyPagesMenu, { MyPagesMenuSkeleton } from "../MyPagesMenu"
|
||||
import MyPagesMobileMenu from "../MyPagesMobileMenu"
|
||||
|
||||
import styles from "./myPagesMenuWrapper.module.css"
|
||||
@@ -62,3 +62,12 @@ export default async function MyPagesMenuWrapper() {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function MyPagesMenuWrapperSkeleton() {
|
||||
return (
|
||||
<div>
|
||||
<MyPagesMenuSkeleton />
|
||||
{/* <MyPagesMobileMenuSkeleton /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import MobileMenuWrapper from "./MobileMenuWrapper"
|
||||
import MyPagesMenuWrapper from "./MyPagesMenuWrapper"
|
||||
import MyPagesMenuWrapper, {
|
||||
MyPagesMenuWrapperSkeleton,
|
||||
} from "./MyPagesMenuWrapper"
|
||||
import NavigationMenu from "./NavigationMenu"
|
||||
|
||||
import styles from "./mainMenu.module.css"
|
||||
@@ -32,7 +34,7 @@ export default async function MainMenu() {
|
||||
<Suspense fallback={"Loading nav"}>
|
||||
<NavigationMenu isMobile={false} />
|
||||
</Suspense>
|
||||
<Suspense fallback={"Loading profile"}>
|
||||
<Suspense fallback={<MyPagesMenuWrapperSkeleton />}>
|
||||
<MyPagesMenuWrapper />
|
||||
</Suspense>
|
||||
<Suspense fallback={"Loading menu"}>
|
||||
|
||||
19
components/SkeletonShimmer/index.tsx
Normal file
19
components/SkeletonShimmer/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import styles from "./skeleton.module.css"
|
||||
|
||||
export default function SkeletonShimmer({
|
||||
height,
|
||||
width,
|
||||
}: {
|
||||
height?: string
|
||||
width?: string
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={styles.shimmer}
|
||||
style={{
|
||||
height: height,
|
||||
width: width,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
29
components/SkeletonShimmer/skeleton.module.css
Normal file
29
components/SkeletonShimmer/skeleton.module.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.shimmer {
|
||||
background-color: hsla(0, 0%, 85%, 0.5);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
min-height: 1em;
|
||||
}
|
||||
.shimmer::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0) 0,
|
||||
rgba(255, 255, 255, 0.2) 20%,
|
||||
rgba(255, 255, 255, 0.5) 60%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
animation: shimmer 3s infinite;
|
||||
content: "";
|
||||
}
|
||||
@keyframes shimmer {
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user