Merge branch 'develop' into feature/sw-561-design-fixes

This commit is contained in:
Linus Flood
2024-10-08 13:23:07 +02:00
47 changed files with 1053 additions and 399 deletions

View File

@@ -22,6 +22,7 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
export default function BookingWidgetClient({
locations,
type,
}: BookingWidgetClientProps) {
const [isOpen, setIsOpen] = useState(false)
@@ -99,8 +100,9 @@ export default function BookingWidgetClient({
>
<CloseLarge />
</button>
<Form locations={locations} />
<Form locations={locations} type={type} />
</section>
<div className={styles.backdrop} onClick={closeMobileSearch} />
<MobileToggleButton openMobileSearch={openMobileSearch} />
</FormProvider>
)

View File

@@ -6,6 +6,10 @@
display: grid;
gap: var(--Spacing-x-one-and-half);
padding: var(--Spacing-x2);
position: sticky;
top: 0;
z-index: 1;
background-color: var(--Base-Surface-Primary-light-Normal);
}
.complete {
@@ -13,7 +17,7 @@
}
.partial {
grid-template-columns: min(1fr, 150px) min-content min(1fr, 150px) 1fr;
grid-template-columns: minmax(auto, 150px) min-content minmax(auto, 150px) auto;
}
.icon {

View File

@@ -1,16 +1,17 @@
@media screen and (max-width: 1366px) {
@media screen and (max-width: 767px) {
.container {
background-color: var(--UI-Input-Controls-Surface-Normal);
bottom: -100%;
display: grid;
gap: var(--Spacing-x3);
grid-template-rows: 36px 1fr;
height: 100dvh;
height: calc(100dvh - 20px);
padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x7);
position: fixed;
transition: bottom 300ms ease;
width: 100%;
z-index: 10000;
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
}
.container[data-open="true"] {
@@ -23,13 +24,26 @@
cursor: pointer;
justify-self: flex-end;
}
.container[data-open="true"] + .backdrop {
background-color: rgba(0, 0, 0, 0.4);
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 1000;
}
}
@media screen and (min-width: 1367px) {
@media screen and (min-width: 768px) {
.container {
border-bottom: 1px solid var(--Base-Border-Subtle);
border-top: 1px solid var(--Base-Border-Subtle);
display: block;
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
position: sticky;
top: 0;
z-index: 10000;
background-color: var(--Base-Surface-Primary-light-Normal);
}
.close {

View File

@@ -2,16 +2,18 @@ import { getLocations } from "@/lib/trpc/memoizedRequests"
import BookingWidgetClient from "./Client"
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
export function preload() {
void getLocations()
}
export default async function BookingWidget() {
export default async function BookingWidget({ type }: BookingWidgetProps) {
const locations = await getLocations()
if (!locations || "error" in locations) {
return null
}
return <BookingWidgetClient locations={locations.data} />
return <BookingWidgetClient locations={locations.data} type={type} />
}

View File

@@ -1,4 +1,5 @@
"use client"
import { DayPicker } from "react-day-picker"
import { useIntl } from "react-intl"

View File

@@ -41,7 +41,8 @@
}
.container[data-isopen="true"] .hideWrapper {
top: 0;
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
top: 20px;
}
}

View File

@@ -0,0 +1,18 @@
import React, { forwardRef, InputHTMLAttributes } from "react"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./input.module.css"
const Input = forwardRef<
HTMLInputElement,
InputHTMLAttributes<HTMLInputElement>
>(function InputComponent(props, ref) {
return (
<Body asChild>
<input {...props} ref={ref} className={styles.input} />
</Body>
)
})
export default Input

View File

@@ -0,0 +1,22 @@
.input {
background-color: transparent;
border: none;
height: 24px;
outline: none;
position: relative;
width: 100%;
z-index: 2;
}
.input::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
background-image: url("/_static/icons/close.svg");
height: 20px;
width: 20px;
}
.input:disabled,
.input:disabled::placeholder {
color: var(--Base-Text-Disabled);
}

View File

@@ -13,6 +13,7 @@ import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Input from "../Input"
import { init, localStorageKey, reducer, sessionStorageKey } from "./reducer"
import SearchList from "./SearchList"
@@ -142,29 +143,26 @@ export default function Search({ locations }: SearchProps) {
</Caption>
</label>
<div {...getRootProps({}, { suppressRefError: true })}>
<Body asChild>
<input
{...getInputProps({
className: styles.input,
id: name,
onFocus(evt) {
handleOnFocus(evt)
openMenu()
<Input
{...getInputProps({
id: name,
onFocus(evt) {
handleOnFocus(evt)
openMenu()
},
placeholder: intl.formatMessage({
id: "Destinations & hotels",
}),
...register(name, {
onBlur: function () {
handleOnBlur()
closeMenu()
},
placeholder: intl.formatMessage({
id: "Destinations & hotels",
}),
...register(name, {
onBlur: function () {
handleOnBlur()
closeMenu()
},
onChange: handleOnChange,
}),
type: "search",
})}
/>
</Body>
onChange: handleOnChange,
}),
type: "search",
})}
/>
</div>
<SearchList
getItemProps={getItemProps}

View File

@@ -7,42 +7,24 @@
}
.container:hover,
.container:has(.input:active, .input:focus, .input:focus-within) {
.container:has(input:active, input:focus, input:focus-within) {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
.container:has(.input:active, .input:focus, .input:focus-within) {
.container:has(input:active, input:focus, input:focus-within) {
border-color: 1px solid var(--UI-Input-Controls-Border-Focus);
}
.label:has(
~ .inputContainer .input:active,
~ .inputContainer .input:focus,
~ .inputContainer .input:focus-within
~ .inputContainer input:active,
~ .inputContainer input:focus,
~ .inputContainer input:focus-within
)
p {
color: var(--UI-Text-Active);
}
.input {
background-color: transparent;
border: none;
height: 24px;
outline: none;
position: relative;
width: 100%;
z-index: 2;
}
.input::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
background-image: url("/_static/icons/close.svg");
height: 20px;
width: 20px;
}
.container:hover:has(.input:not(:active, :focus, :focus-within))
.input::-webkit-search-cancel-button {
.container:hover:has(input:not(:active, :focus, :focus-within))
input::-webkit-search-cancel-button {
display: none;
}

View File

@@ -0,0 +1,71 @@
"use client"
import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
import Input from "../Input"
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}>
<label>
<Caption color="disabled" textTransform="bold">
{vouchers}
</Caption>
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
</label>
<Input type="text" placeholder={addVouchers} disabled />
</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,79 @@
.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%;
display: block;
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
border-radius: var(--Corner-radius-Small);
}
.optionsContainer {
display: flex;
flex-direction: column;
}
.checkbox {
width: 24px;
height: 24px;
}
.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,16 +1,29 @@
.options {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
.infoIcon {
stroke: var(--Base-Text-Disabled);
}
.option {
.vouchersHeader {
display: flex;
gap: var(--Spacing-x-one-and-half);
}
@media screen and (max-width: 1366px) {
.input {
.checkbox {
width: 24px;
height: 24px;
}
.icon,
.voucherRow {
display: none;
}
@media screen and (max-width: 767px) {
.voucherContainer {
padding: var(--Spacing-x2) 0 var(--Spacing-x4);
}
}
@media screen and (max-width: 1367px) {
.inputContainer {
display: grid;
gap: var(--Spacing-x2);
}
@@ -29,52 +42,85 @@
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
}
.options {
gap: var(--Spacing-x2);
margin-top: var(--Spacing-x2);
}
.option {
gap: var(--Spacing-x2);
.button {
align-self: flex-end;
justify-content: center;
width: 100%;
}
}
@media screen and (min-width: 1367px) {
@media screen and (min-width: 768px) {
.input {
display: flex;
align-items: center;
}
.inputContainer {
display: flex;
flex: 2;
gap: var(--Spacing-x2);
}
.voucherContainer {
flex: 1;
}
.rooms,
.vouchers,
.when,
.where {
border-right: 1px solid var(--Base-Surface-Subtle-Normal);
width: 100%;
}
.input input[type="text"] {
.inputContainer input[type="text"] {
border: none;
height: 24px;
}
.rooms,
.when {
max-width: 240px;
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
border-radius: var(--Corner-radius-Small);
}
.vouchers {
max-width: 200px;
padding: var(--Spacing-x1) 0;
.when:hover,
.rooms:hover,
.rooms:has(.input:active, .input:focus, .input:focus-within) {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
.where {
max-width: 280px;
position: relative;
}
.options {
max-width: 158px;
.button {
justify-content: center;
width: 118px;
}
}
@media screen and (min-width: 768px) and (max-width: 1366px) {
.inputContainer {
padding: var(--Spacing-x2) var(--Spacing-x2);
}
.buttonContainer {
padding-right: var(--Spacing-x2);
}
.input .buttonContainer .button {
padding: var(--Spacing-x1);
width: 48px;
height: 48px;
}
.buttonText {
display: none;
}
.icon {
display: flex;
}
.voucherRow {
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

@@ -5,9 +5,14 @@ import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import DatePicker from "@/components/DatePicker"
import { SearchIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Input from "./Input"
import Search from "./Search"
import Voucher from "./Voucher"
import styles from "./formContent.module.css"
@@ -15,53 +20,69 @@ import type { BookingWidgetFormContentProps } from "@/types/components/form/book
export default function FormContent({
locations,
formId,
formState,
}: BookingWidgetFormContentProps) {
const intl = useIntl()
const selectedDate = useWatch({ name: "date" })
const rooms = intl.formatMessage({ id: "Guests & Rooms" })
const vouchers = intl.formatMessage({ id: "Code / Voucher" })
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")
return (
<div className={styles.input}>
<div className={styles.where}>
<Search locations={locations} />
<>
<div className={styles.input}>
<div className={styles.inputContainer}>
<div className={styles.where}>
<Search locations={locations} />
</div>
<div className={styles.when}>
<Caption color="red" textTransform="bold">
{intl.formatMessage(
{ id: "booking.nights" },
{ totalNights: nights }
)}
</Caption>
<DatePicker />
</div>
<div className={styles.rooms}>
<label>
<Caption color="red" textTransform="bold">
{rooms}
</Caption>
</label>
<Input type="text" placeholder={rooms} />
</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>
<div className={styles.when}>
<Caption color="red" textTransform="bold">
{intl.formatMessage(
{ id: "booking.nights" },
{ totalNights: nights }
)}
</Caption>
<DatePicker />
<div className={styles.voucherRow}>
<Voucher />
</div>
<div className={styles.rooms}>
<Caption color="red" textTransform="bold">
{rooms}
</Caption>
<input type="text" placeholder={rooms} />
</div>
<div className={styles.vouchers}>
<Caption color="uiTextMediumContrast" textTransform="bold">
{vouchers}
</Caption>
<input type="text" placeholder={vouchers} />
</div>
<div className={styles.options}>
<label className={styles.option}>
<input type="checkbox" />
<Caption color="textMediumContrast">{bonus}</Caption>
</label>
<label className={styles.option}>
<input type="checkbox" />
<Caption color="textMediumContrast">{reward}</Caption>
</label>
</div>
</div>
</>
)
}

View File

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

View File

@@ -1,12 +1,9 @@
"use client"
import { useRouter } from "next/navigation"
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 { bookingWidgetVariants } from "./variants"
import styles from "./form.module.css"
@@ -15,10 +12,13 @@ import type { BookingWidgetFormProps } from "@/types/components/form/bookingwidg
const formId = "booking-widget"
export default function Form({ locations }: BookingWidgetFormProps) {
const intl = useIntl()
export default function Form({ locations, type }: BookingWidgetFormProps) {
const router = useRouter()
const classNames = bookingWidgetVariants({
type,
})
const { formState, handleSubmit, register } =
useFormContext<BookingWidgetSchema>()
@@ -31,28 +31,19 @@ export default function Form({ locations }: BookingWidgetFormProps) {
}
return (
<section className={styles.section}>
<section className={classNames}>
<form
onSubmit={handleSubmit(onSubmit)}
className={styles.form}
id={formId}
>
<input {...register("location")} type="hidden" />
<FormContent locations={locations} />
<FormContent
locations={locations}
formId={formId}
formState={formState}
/>
</form>
<Button
className={styles.button}
disabled={!formState.isValid}
form={formId}
intent="primary"
size="small"
theme="base"
type="submit"
>
<Caption color="white" textTransform="bold">
{intl.formatMessage({ id: "Search" })}
</Caption>
</Button>
</section>
)
}

View File

@@ -0,0 +1,15 @@
import { cva } from "class-variance-authority"
import styles from "./form.module.css"
export const bookingWidgetVariants = cva(styles.section, {
variants: {
type: {
default: styles.default,
full: styles.full,
},
},
defaultVariants: {
type: "full",
},
})

View File

@@ -1,8 +1,10 @@
"use client"
import { useIntl } from "react-intl"
import Divider from "@/components/TempDesignSystem/Divider"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import HotelDetailSidePeek from "./HotelDetailSidePeek"
@@ -10,10 +12,10 @@ import styles from "./hotelSelectionHeader.module.css"
import { HotelSelectionHeaderProps } from "@/types/components/hotelReservation/selectRate/hotelSelectionHeader"
export default async function HotelSelectionHeader({
export default function HotelSelectionHeader({
hotel,
}: HotelSelectionHeaderProps) {
const intl = await getIntl()
const intl = useIntl()
return (
<header className={styles.hotelSelectionHeader}>

View File

@@ -1,48 +1,91 @@
import { CheckCircleIcon, ChevronDownIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
"use client"
import { useEffect, useRef } from "react"
import { useIntl } from "react-intl"
import { CheckIcon, ChevronDownIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { getIntl } from "@/i18n"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./sectionAccordion.module.css"
import { SectionAccordionProps } from "@/types/components/hotelReservation/selectRate/sectionAccordion"
export default async function SectionAccordion({
export default function SectionAccordion({
header,
selection,
isOpen,
isCompleted,
label,
path,
children,
}: React.PropsWithChildren<SectionAccordionProps>) {
const intl = await getIntl()
const intl = useIntl()
const contentRef = useRef<HTMLDivElement>(null)
const circleRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const content = contentRef.current
const circle = circleRef.current
if (content) {
if (isOpen) {
content.style.maxHeight = `${content.scrollHeight}px`
} else {
content.style.maxHeight = "0"
}
}
if (circle) {
if (isOpen) {
circle.style.backgroundColor = `var(--UI-Text-Placeholder);`
} else {
circle.style.backgroundColor = `var(--Base-Surface-Subtle-Hover);`
}
}
}, [isOpen])
return (
<div className={styles.wrapper}>
<div className={styles.top}>
<div>
<CheckCircleIcon color={selection ? "green" : "pale"} />
</div>
<div className={styles.header}>
<Caption color={"burgundy"} asChild>
<h2>{header}</h2>
</Caption>
{(Array.isArray(selection) ? selection : [selection]).map((s) => (
<Body key={s} className={styles.selection} color={"burgundy"}>
{s}
</Body>
))}
</div>
{selection && (
<Button intent="secondary" size="small" asChild>
<Link href={path}>{intl.formatMessage({ id: "Modify" })}</Link>
</Button>
)}
<div>
<ChevronDownIcon />
<section className={styles.wrapper} data-open={isOpen}>
<div className={styles.iconWrapper}>
<div
className={styles.circle}
data-checked={isCompleted}
ref={circleRef}
>
{isCompleted ? (
<CheckIcon color="white" height="16" width="16" />
) : null}
</div>
</div>
{children}
</div>
<div className={styles.main}>
<header className={styles.headerContainer}>
<div>
<Footnote
asChild
textTransform="uppercase"
color="uiTextPlaceholder"
>
<h2>{header}</h2>
</Footnote>
<Subtitle
type="two"
className={styles.selection}
color="uiTextHighContrast"
>
{label}
</Subtitle>
</div>
{isCompleted && !isOpen && (
<Link href={path} color="burgundy" variant="icon">
{intl.formatMessage({ id: "Modify" })}{" "}
<ChevronDownIcon color="burgundy" />
</Link>
)}
</header>
<div className={styles.content} ref={contentRef}>
{children}
</div>
</div>
</section>
)
}

View File

@@ -1,21 +1,73 @@
.wrapper {
border-bottom: 1px solid var(--Base-Border-Normal);
position: relative;
display: flex;
flex-direction: row;
gap: var(--Spacing-x3);
padding-top: var(--Spacing-x3);
}
.top {
.wrapper:not(:last-child)::after {
position: absolute;
left: 12px;
bottom: 0;
top: var(--Spacing-x5);
height: 100%;
content: "";
border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
}
.main {
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
width: 100%;
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
padding-bottom: var(--Spacing-x3);
padding-top: var(--Spacing-x3);
}
.headerContainer {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--Spacing-x2);
}
.header {
flex-grow: 1;
}
.selection {
font-weight: 450;
font-size: var(--typography-Title-4-fontSize);
}
.iconWrapper {
position: relative;
top: var(--Spacing-x1);
z-index: 10;
}
.circle {
width: 24px;
height: 24px;
border-radius: 100px;
transition: background-color 0.4s;
border: 2px solid var(--Base-Border-Inverted);
display: flex;
justify-content: center;
align-items: center;
}
.circle[data-checked="true"] {
background-color: var(--UI-Input-Controls-Fill-Selected);
}
.wrapper[data-open="true"] .circle[data-checked="false"] {
background-color: var(--UI-Text-Placeholder);
}
.wrapper[data-open="false"] .circle[data-checked="false"] {
background-color: var(--Base-Surface-Subtle-Hover);
}
.content {
overflow: hidden;
transition: max-height 0.4s ease-out;
max-height: 0;
}

View File

@@ -61,3 +61,8 @@
.uiTextMediumContrast * {
fill: var(--UI-Text-Medium-contrast);
}
.blue,
.blue * {
fill: var(--UI-Input-Controls-Fill-Selected);
}

View File

@@ -17,6 +17,7 @@ const config = {
white: styles.white,
uiTextHighContrast: styles.uiTextHighContrast,
uiTextMediumContrast: styles.uiTextMediumContrast,
blue: styles.blue,
},
},
defaultVariants: {

View File

@@ -1,5 +1,3 @@
import React from "react"
import { ChevronRightIcon } from "@/components/Icons"
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"

View File

@@ -75,10 +75,14 @@ p.caption {
color: var(--UI-Text-High-contrast);
}
.disabled {
color: var(--Base-Text-Disabled);
}
.center {
text-align: center;
}
.left {
text-align: left;
}
}

View File

@@ -15,6 +15,7 @@ const config = {
uiTextHighContrast: styles.uiTextHighContrast,
uiTextActive: styles.uiTextActive,
uiTextMediumContrast: styles.uiTextMediumContrast,
disabled: styles.disabled,
},
textTransform: {
bold: styles.bold,

View File

@@ -58,3 +58,7 @@
.pale {
color: var(--Scandic-Brand-Pale-Peach);
}
.uiTextHighContrast {
color: var(--UI-Text-High-contrast);
}

View File

@@ -8,6 +8,7 @@ const config = {
black: styles.black,
burgundy: styles.burgundy,
pale: styles.pale,
uiTextHighContrast: styles.uiTextHighContrast,
},
textAlign: {
center: styles.center,

View File

@@ -0,0 +1,32 @@
import { PropsWithChildren } from "react"
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,
}: PropsWithChildren<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,
},
},
})