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; 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) { @media screen and (min-width: 768px) {
:root { :root {
--max-width-spacing: calc(var(--Layout-Tablet-Margin-Margin-min) * 2); --max-width-spacing: calc(var(--Layout-Tablet-Margin-Margin-min) * 2);

View File

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

View File

@@ -194,7 +194,7 @@ export default function DestinationFilterAndSort({
</DialogTrigger> </DialogTrigger>
{/* This section is added to the DOM for SEO purposes. The filters are linkable and should be indexable */} {/* 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> <ul>
{facilityFilters.map((filter) => ( {facilityFilters.map((filter) => (
<li key={`filter-${filter.slug}`}> <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" "use client"
import { useIsSSR } from "@react-aria/ssr"
import { useContext, useRef } from "react" import { useContext, useRef } from "react"
import { Dialog, Modal, ModalOverlay } from "react-aria-components" import { Dialog, Modal, ModalOverlay } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -11,12 +10,13 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import { SidePeekContext } from "@/components/SidePeeks/SidePeekProvider" import { SidePeekContext } from "@/components/SidePeeks/SidePeekProvider"
import Button from "../Button" import Button from "../Button"
import SidePeekSEO from "./SidePeekSEO"
import styles from "./sidePeek.module.css" import styles from "./sidePeek.module.css"
import type { SidePeekProps } from "./sidePeek" import type { SidePeekProps } from "./sidePeek"
function SidePeek({ export default function SidePeek({
children, children,
title, title,
contentKey, contentKey,
@@ -24,7 +24,6 @@ function SidePeek({
isOpen, isOpen,
openInRoot = false, openInRoot = false,
}: React.PropsWithChildren<SidePeekProps>) { }: React.PropsWithChildren<SidePeekProps>) {
const isSSR = useIsSSR()
const intl = useIntl() const intl = useIntl()
const rootDiv = useRef<HTMLDivElement>(null) const rootDiv = useRef<HTMLDivElement>(null)
@@ -34,51 +33,47 @@ function SidePeek({
closeHandler && closeHandler(false) closeHandler && closeHandler(false)
} }
if (isSSR) {
return (
<div className={styles.visuallyHidden}>
<h2>{title}</h2>
{children}
</div>
)
}
return ( return (
<div ref={openInRoot ? null : rootDiv}> <>
<ModalOverlay <div ref={openInRoot ? null : rootDiv}>
UNSTABLE_portalContainer={rootDiv.current || undefined} <ModalOverlay
className={styles.overlay} UNSTABLE_portalContainer={rootDiv.current || undefined}
isOpen={ className={styles.overlay}
isOpen || (!!contentKey && contentKey === context?.activeSidePeek) isOpen={
} isOpen || (!!contentKey && contentKey === context?.activeSidePeek)
onOpenChange={onClose} }
isDismissable onOpenChange={onClose}
> isDismissable
<Modal className={styles.modal}> >
<Dialog className={styles.dialog} aria-label={title}> <Modal className={styles.modal}>
<aside className={styles.sidePeek}> <Dialog className={styles.dialog} aria-label={title}>
<header className={styles.header}> <aside className={styles.sidePeek}>
{title ? ( <header className={styles.header}>
<Typography variant="Title/md" className={styles.heading}> {title ? (
<h2>{title}</h2> <Typography variant="Title/md" className={styles.heading}>
</Typography> <h2>{title}</h2>
) : null} </Typography>
<Button ) : null}
aria-label={intl.formatMessage({ id: "Close" })} <Button
className={styles.closeButton} aria-label={intl.formatMessage({ id: "Close" })}
intent="text" className={styles.closeButton}
onPress={onClose} intent="text"
> onPress={onClose}
<MaterialIcon icon="close" color="Icon/Interactive/Default" /> >
</Button> <MaterialIcon
</header> icon="close"
<div className={styles.sidePeekContent}>{children}</div> color="Icon/Interactive/Default"
</aside> />
</Dialog> </Button>
</Modal> </header>
</ModalOverlay> <div className={styles.sidePeekContent}>{children}</div>
</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 { .overlay {
position: fixed; position: fixed;
top: 0; top: 0;