Merged in feat/sw-3218-move-sidepeek-to-design-system (pull request #2598)

feat(SW-3218): Move SidePeek to design-system

* Remove SidePeekProvider dependency on Next

* Remove dependency on i18n in sidepeek

* Inline types

* Move SidePeek to design-system

* Fix align-items value


Approved-by: Bianca Widstam
This commit is contained in:
Anton Gunnarsson
2025-08-06 08:35:34 +00:00
parent 75ffd5d10b
commit 7fb082f712
21 changed files with 152 additions and 65 deletions

View File

@@ -5,9 +5,9 @@ import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import JsonToHtml from "@/components/JsonToHtml"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { trackOpenSidePeekOnDestinationPagesEvent } from "@/utils/tracking/destinationPage"
import type { DestinationCityPageData } from "@scandic-hotels/trpc/types/destinationCityPage"
@@ -56,6 +56,9 @@ export default function DestinationPageSidepeek({
isOpen={sidePeekIsOpen}
openInRoot
handleClose={() => setSidePeekIsOpen(false)}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<JsonToHtml
nodes={content.json.children}

View File

@@ -1,7 +1,7 @@
import { Divider } from "@scandic-hotels/design-system/Divider"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { Typography } from "@scandic-hotels/design-system/Typography"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import ContactInformation from "./ContactInformation"
@@ -27,6 +27,9 @@ export default async function AboutTheHotelSidePeek({
title={intl.formatMessage({
defaultMessage: "About the hotel",
})}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<section className={styles.wrapper}>
<ContactInformation

View File

@@ -1,8 +1,8 @@
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Preamble from "@scandic-hotels/design-system/Preamble"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import Link from "@/components/TempDesignSystem/Link"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import styles from "./activities.module.css"
@@ -22,6 +22,9 @@ export default async function ActivitiesSidePeek({
title={intl.formatMessage({
defaultMessage: "Activities",
})}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<Preamble>{preamble}</Preamble>
<div className={styles.buttonContainer}>

View File

@@ -1,10 +1,11 @@
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import AccessibilityAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/Accessibility"
import BreakfastAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/Breakfast"
import CheckInCheckOutAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/CheckInCheckOut"
import ParkingAccordionItem from "@/components/SidePeeks/AmenitiesSidepeekContent/Accordions/Parking"
import AdditionalAmenities from "@/components/SidePeeks/AmenitiesSidepeekContent/AdditionalAmenities"
import Accordion from "@/components/TempDesignSystem/Accordion"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
@@ -32,6 +33,9 @@ export default async function AmenitiesSidePeek({
title={intl.formatMessage({
defaultMessage: "Amenities",
})}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<Accordion>
<ParkingAccordionItem

View File

@@ -1,7 +1,7 @@
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { Typography } from "@scandic-hotels/design-system/Typography"
import ButtonLink from "@/components/ButtonLink"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
@@ -26,7 +26,13 @@ export default async function MeetingsAndConferencesSidePeek({
const meetingPageHref = await appendSlugToPathname(meetingPageUrl)
return (
<SidePeek contentKey={SidepeekSlugs.meetings} title={heading}>
<SidePeek
contentKey={SidepeekSlugs.meetings}
title={heading}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<div className={styles.wrapper}>
<Typography variant="Title/Subtitle/lg">
<h3>

View File

@@ -1,4 +1,6 @@
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { getIntl } from "@/i18n"
import RestaurantBarItem from "./RestaurantBarItem"
@@ -7,12 +9,20 @@ import styles from "./restaurantBar.module.css"
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage"
import type { RestaurantBarSidePeekProps } from "@/types/components/hotelPage/sidepeek/restaurantBar"
export default function RestaurantBarSidePeek({
export default async function RestaurantBarSidePeek({
restaurants,
heading,
}: RestaurantBarSidePeekProps) {
const intl = await getIntl()
return (
<SidePeek contentKey={SidepeekSlugs.restaurant} title={heading}>
<SidePeek
contentKey={SidepeekSlugs.restaurant}
title={heading}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<div className={styles.content}>
{restaurants.map((restaurant) => (
<div key={restaurant.id} className={styles.item}>

View File

@@ -3,10 +3,10 @@ import Link from "next/link"
import { selectRateWithParams } from "@scandic-hotels/common/constants/routes/hotelReservation"
import { dt } from "@scandic-hotels/common/dt"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { Typography } from "@scandic-hotels/design-system/Typography"
import ImageGallery from "@/components/ImageGallery"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
@@ -43,7 +43,13 @@ export default async function RoomSidePeek({
const selectRateURL = selectRateWithParams(lang, hotelId, fromdate, todate)
return (
<SidePeek contentKey={`room-${getRoomNameAsParam(name)}`} title={name}>
<SidePeek
contentKey={`room-${getRoomNameAsParam(name)}`}
title={name}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<div className={styles.content}>
<div className={styles.innerContent}>
<Typography variant="Body/Paragraph/mdRegular">

View File

@@ -1,7 +1,8 @@
import { notFound } from "next/navigation"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import Image from "@/components/Image"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
@@ -45,6 +46,9 @@ export default async function TripAdvisorSidePeek({
title={intl.formatMessage({
defaultMessage: "Ratings & reviews",
})}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<section className={styles.container}>
{hotelHasAwards ? (

View File

@@ -1,7 +1,8 @@
import { cx } from "class-variance-authority"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import ButtonLink from "@/components/ButtonLink"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
@@ -24,7 +25,13 @@ export default async function WellnessAndExerciseSidePeek({
)
return (
<SidePeek contentKey={SidepeekSlugs.wellness} title={heading}>
<SidePeek
contentKey={SidepeekSlugs.wellness}
title={heading}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<div
className={cx(styles.wrapper, {
[styles.hasSpaPage]: spaPage,

View File

@@ -1,5 +1,8 @@
"use client"
import SidePeekProvider from "@/components/SidePeeks/SidePeekProvider"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import SidePeekProvider from "@scandic-hotels/design-system/SidePeek/SidePeekProvider"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
interface SidePeeksProps extends React.PropsWithChildren {
@@ -7,9 +10,27 @@ interface SidePeeksProps extends React.PropsWithChildren {
}
export default function SidePeeks({ hotelId, children }: SidePeeksProps) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
function handleOpen(sidePeek: string) {
trackOpenSidePeekEvent(sidePeek, hotelId)
}
return <SidePeekProvider onOpen={handleOpen}>{children}</SidePeekProvider>
function handleClose() {
const nextSearchParams = new URLSearchParams(searchParams.toString())
nextSearchParams.delete("s")
router.push(`${pathname}?${nextSearchParams}`, { scroll: false })
}
return (
<SidePeekProvider
onOpen={handleOpen}
onClose={handleClose}
searchParams={searchParams}
>
{children}
</SidePeekProvider>
)
}

View File

@@ -2,13 +2,13 @@
import { useIntl } from "react-intl"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { Typography } from "@scandic-hotels/design-system/Typography"
import ButtonLink from "@/components/ButtonLink"
import Contact from "@/components/HotelReservation/Contact"
import AdditionalAmenities from "@/components/SidePeeks/AmenitiesSidepeekContent/AdditionalAmenities"
import Accordion from "@/components/TempDesignSystem/Accordion"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import AccessibilityAccordionItem from "../AmenitiesSidepeekContent/Accordions/Accessibility"
import BreakfastAccordionItem from "../AmenitiesSidepeekContent/Accordions/Breakfast"
@@ -34,6 +34,9 @@ export default function HotelSidePeek({
title={hotel.name}
isOpen={activeSidePeek === SidePeekEnum.hotelDetails}
handleClose={close}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<div className={styles.content}>
<Typography variant="Title/Subtitle/lg">

View File

@@ -1,4 +1,6 @@
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { useIntl } from "react-intl"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { RoomSidePeekContent } from "./RoomSidePeekContent"
@@ -10,11 +12,16 @@ export default function RoomSidePeek({
activeSidePeek,
close,
}: RoomSidePeekProps) {
const intl = useIntl()
return (
<SidePeek
title={room.name}
isOpen={activeSidePeek === SidePeekEnum.roomDetails}
handleClose={close}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<RoomSidePeekContent room={room} />
</SidePeek>

View File

@@ -1,14 +1,14 @@
"use client"
import { useState } from "react"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import JsonToHtml from "@/components/JsonToHtml"
import SidePeek from "../../SidePeek"
import styles from "./sidepeek.module.css"
import type { AlertSidepeekProps } from "./sidepeek"
@@ -17,6 +17,7 @@ export default function AlertSidepeek({
ctaText,
sidePeekContent,
}: AlertSidepeekProps) {
const intl = useIntl()
const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false)
const { heading, content } = sidePeekContent
@@ -38,6 +39,9 @@ export default function AlertSidepeek({
title={heading}
isOpen={sidePeekIsOpen}
handleClose={() => setSidePeekIsOpen(false)}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<JsonToHtml
nodes={content.json.children}

View File

@@ -1,7 +0,0 @@
export interface SidePeekProps {
contentKey?: string
title: string
isOpen?: boolean
openInRoot?: boolean
handleClose?: (isOpen: boolean) => void
}

View File

@@ -1,15 +1,15 @@
"use client"
import { useState } from "react"
import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import ButtonLink from "@/components/ButtonLink"
import JsonToHtml from "@/components/JsonToHtml"
import SidePeek from "../../SidePeek"
import styles from "./sidepeek.module.css"
import type { TeaserCardSidepeekProps } from "@/types/components/teaserCard"
@@ -18,6 +18,7 @@ export default function TeaserCardSidepeek({
button,
sidePeekContent,
}: TeaserCardSidepeekProps) {
const intl = useIntl()
const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false)
const { heading, content, primary_button, secondary_button } = sidePeekContent
@@ -38,6 +39,9 @@ export default function TeaserCardSidepeek({
isOpen={sidePeekIsOpen}
handleClose={() => setSidePeekIsOpen(false)}
openInRoot
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
{content ? (
<JsonToHtml

View File

@@ -1,28 +1,22 @@
"use client"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { createContext, useEffect, useState } from "react"
'use client'
import { useEffect, useState } from 'react'
import { SidePeekContext } from './index'
interface SidepeekProviderProps extends React.PropsWithChildren {
onOpen?: (sidePeek: string) => void
onClose?: () => void
searchParams: URLSearchParams
}
interface ISidePeekContext {
handleClose: (isOpen: boolean) => void
activeSidePeek: string | null
}
export const SidePeekContext = createContext<ISidePeekContext | null>(null)
export default function SidePeekProvider({
children,
searchParams,
onOpen,
onClose,
}: SidepeekProviderProps) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const [activeSidePeek, setActiveSidePeek] = useState<string | null>(null)
useEffect(() => {
const sidePeekParam = searchParams.get("s")
const sidePeekParam = searchParams.get('s')
if (sidePeekParam !== activeSidePeek) {
setActiveSidePeek(sidePeekParam)
}
@@ -36,9 +30,7 @@ export default function SidePeekProvider({
function handleClose(isOpen: boolean) {
if (!isOpen) {
const nextSearchParams = new URLSearchParams(searchParams.toString())
nextSearchParams.delete("s")
router.push(`${pathname}?${nextSearchParams}`, { scroll: false })
onClose?.()
setActiveSidePeek(null)
}
}

View File

@@ -0,0 +1,9 @@
'use client'
import { createContext } from 'react'
interface ISidePeekContext {
handleClose: (isOpen: boolean) => void
activeSidePeek: string | null
}
export const SidePeekContext = createContext<ISidePeekContext | null>(null)

View File

@@ -1,4 +1,6 @@
import type { SidePeekProps } from "./sidePeek"
interface SidePeekSEOProps {
title: string
}
// Sidepeeks generally have important content that should be indexed by search engines.
// The content is hidden behind a modal, but it is still important for SEO.
@@ -6,7 +8,7 @@ import type { SidePeekProps } from "./sidePeek"
export default function SidePeekSEO({
title,
children,
}: React.PropsWithChildren<Pick<SidePeekProps, "title">>) {
}: React.PropsWithChildren<SidePeekSEOProps>) {
return (
<div className="sr-only">
<h2>{title}</h2>

View File

@@ -1,20 +1,26 @@
"use client"
'use client'
import { useContext, useRef } from "react"
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
import { useIntl } from "react-intl"
import { useContext, useRef } from 'react'
import { Dialog, Modal, ModalOverlay } from 'react-aria-components'
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { MaterialIcon } from '../Icons/MaterialIcon'
import { OldDSButton as Button } from '../OldDSButton'
import { Typography } from '../Typography'
import { SidePeekContext } from "@/components/SidePeeks/SidePeekProvider"
import { SidePeekContext } from './SidePeekContext'
import SidePeekSEO from "./SidePeekSEO"
import SidePeekSEO from './SidePeekSEO'
import styles from "./sidePeek.module.css"
import styles from './sidePeek.module.css'
import type { SidePeekProps } from "./sidePeek"
interface SidePeekProps {
contentKey?: string
title: string
isOpen?: boolean
openInRoot?: boolean
handleClose?: (isOpen: boolean) => void
closeLabel: string
}
export default function SidePeek({
children,
@@ -23,14 +29,14 @@ export default function SidePeek({
handleClose,
isOpen,
openInRoot = false,
closeLabel,
}: React.PropsWithChildren<SidePeekProps>) {
const intl = useIntl()
const rootDiv = useRef<HTMLDivElement>(null)
const context = useContext(SidePeekContext)
function onClose() {
const closeHandler = handleClose || context?.handleClose
closeHandler && closeHandler(false)
closeHandler?.(false)
}
return (
@@ -55,9 +61,7 @@ export default function SidePeek({
</Typography>
) : null}
<Button
aria-label={intl.formatMessage({
defaultMessage: "Close",
})}
aria-label={closeLabel}
className={styles.closeButton}
intent="text"
onPress={onClose}

View File

@@ -58,7 +58,7 @@
display: flex;
justify-content: flex-end;
border-bottom: 1px solid var(--Base-Border-Subtle);
align-items: start;
align-items: flex-start;
padding: var(--Spacing-x4);
}

View File

@@ -27,6 +27,8 @@
"./OldDSButton": "./dist/components/OldDSButton/index.js",
"./Select": "./dist/components/Select/index.js",
"./SkeletonShimmer": "./dist/components/SkeletonShimmer/index.js",
"./SidePeek": "./dist/components/SidePeek/index.js",
"./SidePeek/SidePeekProvider": "./dist/components/SidePeek/SidePeekContext/SidePeekProvider.js",
"./Subtitle": "./dist/components/Subtitle/index.js",
"./Switch": "./dist/components/Switch/index.js",
"./Title": "./dist/components/Title/index.js",