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

@@ -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,16 +0,0 @@
import type { SidePeekProps } from "./sidePeek"
// 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.
// This component is used to provide SEO information for the sidepeek content.
export default function SidePeekSEO({
title,
children,
}: React.PropsWithChildren<Pick<SidePeekProps, "title">>) {
return (
<div className="sr-only">
<h2>{title}</h2>
{children}
</div>
)
}

View File

@@ -1,81 +0,0 @@
"use client"
import { useContext, useRef } from "react"
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
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 { Typography } from "@scandic-hotels/design-system/Typography"
import { SidePeekContext } from "@/components/SidePeeks/SidePeekProvider"
import SidePeekSEO from "./SidePeekSEO"
import styles from "./sidePeek.module.css"
import type { SidePeekProps } from "./sidePeek"
export default function SidePeek({
children,
title,
contentKey,
handleClose,
isOpen,
openInRoot = false,
}: 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)
}
return (
<>
<div ref={openInRoot ? null : rootDiv}>
<ModalOverlay
UNSTABLE_portalContainer={rootDiv.current || undefined}
className={styles.overlay}
isOpen={
isOpen || (!!contentKey && contentKey === context?.activeSidePeek)
}
onOpenChange={onClose}
isDismissable
>
<Modal className={styles.modal}>
<Dialog className={styles.dialog} aria-label={title}>
<aside className={styles.sidePeek}>
<header className={styles.header}>
{title ? (
<Typography variant="Title/md">
<h2 className={styles.heading}>{title}</h2>
</Typography>
) : null}
<Button
aria-label={intl.formatMessage({
defaultMessage: "Close",
})}
className={styles.closeButton}
intent="text"
onPress={onClose}
>
<MaterialIcon
icon="close"
color="Icon/Interactive/Default"
/>
</Button>
</header>
<div className={styles.sidePeekContent}>{children}</div>
</aside>
</Dialog>
</Modal>
</ModalOverlay>
</div>
<SidePeekSEO title={title}>{children}</SidePeekSEO>
</>
)
}

View File

@@ -1,91 +0,0 @@
.modal {
--sidepeek-desktop-width: 560px;
}
@keyframes slide-in {
from {
right: calc(-1 * var(--sidepeek-desktop-width));
}
to {
right: 0;
}
}
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: var(--sidepeek-z-index);
background-color: var(--UI-Opacity-Almost-Black-30);
}
.modal {
position: fixed;
top: 0;
right: auto;
bottom: 0;
width: 100%;
height: 100vh;
background-color: var(--Background-Primary);
z-index: var(--sidepeek-z-index);
outline: none;
}
.modal[data-entering] {
animation: slide-in 250ms;
}
.modal[data-exiting] {
animation: slide-in 250ms reverse;
}
.dialog {
height: 100%;
outline: none;
}
.sidePeek {
position: relative;
display: grid;
grid-template-rows: min-content auto;
height: 100dvh;
}
.header {
display: flex;
justify-content: flex-end;
border-bottom: 1px solid var(--Base-Border-Subtle);
align-items: start;
padding: var(--Spacing-x4);
}
.header:has(> h2) {
justify-content: space-between;
}
.closeButton {
padding: 0;
}
.heading {
color: var(--Text-Heading);
text-wrap: balance;
hyphens: auto;
}
.sidePeekContent {
padding: var(--Spacing-x4);
overflow-y: auto;
}
@media screen and (min-width: 1367px) {
.modal {
top: 0;
right: 0px;
width: var(--sidepeek-desktop-width);
height: 100vh;
}
}

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