feat(SW-1884): Always render sidepeek contents, not just during SSR

Approved-by: Michael Zetterberg
This commit is contained in:
Erik Tiekstra
2025-04-03 09:36:22 +00:00
parent b70d933c73
commit 8c2047e847
6 changed files with 73 additions and 59 deletions

View File

@@ -69,6 +69,19 @@ ul {
margin-block-end: 0;
}
/* From Tailwind */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
@media screen and (min-width: 768px) {
:root {
--max-width-spacing: calc(var(--Layout-Tablet-Margin-Margin-min) * 2);

View File

@@ -74,10 +74,6 @@
padding: 0;
}
.visuallyHidden {
display: none;
}
@media screen and (max-width: 767px) {
.overlay {
height: var(--visual-viewport-height);

View File

@@ -194,7 +194,7 @@ export default function DestinationFilterAndSort({
</DialogTrigger>
{/* This section is added to the DOM for SEO purposes. The filters are linkable and should be indexable */}
<nav className={styles.visuallyHidden}>
<nav className="sr-only">
<ul>
{facilityFilters.map((filter) => (
<li key={`filter-${filter.slug}`}>

View File

@@ -0,0 +1,16 @@
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,6 +1,5 @@
"use client"
import { useIsSSR } from "@react-aria/ssr"
import { useContext, useRef } from "react"
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
import { useIntl } from "react-intl"
@@ -11,12 +10,13 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import { SidePeekContext } from "@/components/SidePeeks/SidePeekProvider"
import Button from "../Button"
import SidePeekSEO from "./SidePeekSEO"
import styles from "./sidePeek.module.css"
import type { SidePeekProps } from "./sidePeek"
function SidePeek({
export default function SidePeek({
children,
title,
contentKey,
@@ -24,7 +24,6 @@ function SidePeek({
isOpen,
openInRoot = false,
}: React.PropsWithChildren<SidePeekProps>) {
const isSSR = useIsSSR()
const intl = useIntl()
const rootDiv = useRef<HTMLDivElement>(null)
@@ -34,51 +33,47 @@ function SidePeek({
closeHandler && closeHandler(false)
}
if (isSSR) {
return (
<div className={styles.visuallyHidden}>
<h2>{title}</h2>
{children}
</div>
)
}
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" className={styles.heading}>
<h2>{title}</h2>
</Typography>
) : null}
<Button
aria-label={intl.formatMessage({ id: "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>
<>
<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" className={styles.heading}>
<h2>{title}</h2>
</Typography>
) : null}
<Button
aria-label={intl.formatMessage({ id: "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>
</>
)
}
export default SidePeek

View File

@@ -21,12 +21,6 @@
}
}
.visuallyHidden {
position: absolute;
opacity: 0;
visibility: hidden;
}
.overlay {
position: fixed;
top: 0;