feat(SW-1884): Always render sidepeek contents, not just during SSR
Approved-by: Michael Zetterberg
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.visuallyHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.overlay {
|
||||
height: var(--visual-viewport-height);
|
||||
|
||||
@@ -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}`}>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -21,12 +21,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.visuallyHidden {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
Reference in New Issue
Block a user