feat(SW-612): Add popover component

This commit is contained in:
Arvid Norlin
2024-11-06 16:14:57 +01:00
committed by Joakim Jäderberg
parent 275fdd8a78
commit c0751968b5
10 changed files with 158 additions and 3 deletions

View File

@@ -104,6 +104,7 @@ export default async function SummaryPage({
euroPrice: prices.euro,
adults,
children,
rateDetails: availability.rateDetails,
cancellationText: availability.cancellationText,
packages,
}}
@@ -122,6 +123,7 @@ export default async function SummaryPage({
euroPrice: prices.euro,
adults,
children,
rateDetails: availability.rateDetails,
cancellationText: availability.cancellationText,
packages,
}}

View File

@@ -11,6 +11,7 @@ import { ArrowRightIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import Popover from "@/components/TempDesignSystem/Popover"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
@@ -178,9 +179,23 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
<Caption color="uiTextMediumContrast">
{room.cancellationText}
</Caption>
<Link color="burgundy" href="#" variant="underscored" size="small">
{intl.formatMessage({ id: "Rate details" })}
</Link>
<Popover
placement={"bottom left"}
triggerContent={
<Caption color="burgundy" type="underline">
{intl.formatMessage({ id: "Rate details" })}
</Caption>
}
>
<aside className={styles.rateDetailsPopover}>
<header>
<Caption type={"bold"}>{room.cancellationText}</Caption>
</header>
{room.rateDetails?.map((detail, idx) => (
<Caption key={`rateDetails-${idx}`}>{detail}</Caption>
))}
</aside>
</Popover>
</div>
{room.packages
? room.packages.map((roomPackage) => (

View File

@@ -41,6 +41,13 @@
gap: var(--Spacing-x-one-and-half);
}
.rateDetailsPopover {
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
max-width: 360px;
}
.entry {
display: flex;
gap: var(--Spacing-x-half);
@@ -50,6 +57,7 @@
.entry > :last-child {
justify-items: flex-end;
}
.total {
display: flex;
flex-direction: column;

View File

@@ -0,0 +1,22 @@
.arrow {
transform-origin: center;
transform: translateY(-2px);
}
[data-placement="left"] .arrow,
[data-placement="left top"] .arrow,
[data-placement="left bottom"] .arrow {
transform: rotate(270deg) translateY(-6px);
}
[data-placement="right"] .arrow,
[data-placement="right top"] .arrow,
[data-placement="right bottom"] .arrow {
transform: rotate(90deg) translateY(-6px);
}
[data-placement="bottom"] .arrow,
[data-placement="bottom left"] .arrow,
[data-placement="bottom right"] .arrow {
transform: rotate(180deg) translateY(-2px);
}

View File

@@ -0,0 +1,19 @@
import styles from "./arrow.module.css"
export function Arrow() {
return (
<div className={styles.arrow}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="27"
height="13"
fill="none"
>
<path
fill="#fff"
d="M13.093 12.193.9 0h25.8L14.508 12.193a1 1 0 0 1-1.415 0Z"
/>
</svg>
</div>
)
}

View File

@@ -0,0 +1,50 @@
import { useRef } from "react"
import {
Button,
Dialog,
DialogTrigger,
OverlayArrow,
Popover as RAPopover,
} from "react-aria-components"
import { CloseLargeIcon } from "@/components/Icons"
import { Arrow } from "./Arrow"
import { PopoverProps } from "./popover"
import styles from "./popover.module.css"
export default function Popover({
triggerContent,
children,
...props
}: PopoverProps) {
let triggerRef = useRef(null)
return (
<DialogTrigger>
<Button className={styles.trigger}>{triggerContent}</Button>
<RAPopover
{...props}
offset={16}
crossOffset={-24}
className={styles.root}
>
<OverlayArrow>
<Arrow />
</OverlayArrow>
<Dialog>
{({ close }) => (
<>
<Button className={styles.closeButton} onPress={close}>
<CloseLargeIcon height={20} width={20} />
</Button>
{children}
</>
)}
</Dialog>
</RAPopover>
</DialogTrigger>
)
}

View File

@@ -0,0 +1,27 @@
.root {
background-color: var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Corner-radius-Medium);
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
padding: var(--Spacing-x2);
max-width: calc(360px + var(--Spacing-x2) * 2);
}
.root section:focus-visible {
outline: none;
}
.trigger {
background: none;
border: none;
padding: 0;
cursor: pointer;
}
.closeButton {
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
cursor: pointer;
padding: 0;
}

View File

@@ -0,0 +1,6 @@
import type { PopoverProps as RAPopoverProps } from "react-aria-components"
export interface PopoverProps extends Omit<RAPopoverProps, "children"> {
triggerContent: React.ReactNode
children: React.ReactNode
}

View File

@@ -725,6 +725,10 @@ export const hotelQueryRouter = router({
return null
}
const rateDetails = validateAvailabilityData.data.rateDefinitions.find(
(rateDef) => rateDef.rateCode === rateCode
)?.generalTerms
const rateTypes = selectedRoom.products.find(
(rate) =>
rate.productType.public.rateCode === rateCode ||
@@ -782,6 +786,7 @@ export const hotelQueryRouter = router({
return {
selectedRoom,
rateDetails,
mustBeGuaranteed,
cancellationText,
memberRate: rates?.member,

View File

@@ -28,6 +28,7 @@ export type RoomsData = {
euroPrice: Price | undefined
adults: number
children?: Child[]
rateDetails?: string[]
cancellationText: string
packages: Packages | null
}