fix(BOOK-468): Added inert attribute to SidePeekSEO element to ignore tab navigation and screen readers
Approved-by: Linus Flood
This commit is contained in:
@@ -2,6 +2,8 @@ import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
|||||||
import SidePeek from "@scandic-hotels/design-system/SidePeek"
|
import SidePeek from "@scandic-hotels/design-system/SidePeek"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
|
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ export default async function MeetingsAndConferencesSidePeek({
|
|||||||
heading,
|
heading,
|
||||||
}: MeetingsAndConferencesSidePeekProps) {
|
}: MeetingsAndConferencesSidePeekProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
const shouldInert = env.SEO_INERT
|
||||||
const { seatingText, roomText } = await getConferenceRoomTexts(meetingRooms)
|
const { seatingText, roomText } = await getConferenceRoomTexts(meetingRooms)
|
||||||
const visibleImages = meetingFacilities?.heroImages.slice(0, 2)
|
const visibleImages = meetingFacilities?.heroImages.slice(0, 2)
|
||||||
const meetingPageHref = await appendSlugToPathname(meetingPageUrl)
|
const meetingPageHref = await appendSlugToPathname(meetingPageUrl)
|
||||||
@@ -33,6 +36,7 @@ export default async function MeetingsAndConferencesSidePeek({
|
|||||||
id: "common.close",
|
id: "common.close",
|
||||||
defaultMessage: "Close",
|
defaultMessage: "Close",
|
||||||
})}
|
})}
|
||||||
|
shouldInert={shouldInert}
|
||||||
>
|
>
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<Typography variant="Title/Subtitle/lg">
|
<Typography variant="Title/Subtitle/lg">
|
||||||
|
|||||||
6
apps/scandic-web/env/server.ts
vendored
6
apps/scandic-web/env/server.ts
vendored
@@ -107,6 +107,11 @@ export const env = createEnv({
|
|||||||
// transform to boolean
|
// transform to boolean
|
||||||
.transform((s) => s === "true")
|
.transform((s) => s === "true")
|
||||||
.default("false"),
|
.default("false"),
|
||||||
|
SEO_INERT: z
|
||||||
|
.string()
|
||||||
|
.refine((s) => s === "1" || s === "0")
|
||||||
|
.transform((s) => s === "1")
|
||||||
|
.default("0"),
|
||||||
},
|
},
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
@@ -162,5 +167,6 @@ export const env = createEnv({
|
|||||||
HOTEL_BRANDING: process.env.HOTEL_BRANDING,
|
HOTEL_BRANDING: process.env.HOTEL_BRANDING,
|
||||||
CHATBOT_LIVE_LANGS: process.env.CHATBOT_LIVE_LANGS,
|
CHATBOT_LIVE_LANGS: process.env.CHATBOT_LIVE_LANGS,
|
||||||
NEW_STAYS_ON_MY_PAGES: process.env.NEW_STAYS_ON_MY_PAGES,
|
NEW_STAYS_ON_MY_PAGES: process.env.NEW_STAYS_ON_MY_PAGES,
|
||||||
|
SEO_INERT: process.env.SEO_INERT,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import type { SidePeekSelfControlledProps } 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<SidePeekSelfControlledProps, 'title'>>) {
|
|
||||||
return (
|
|
||||||
<div className="sr-only">
|
|
||||||
<h2>{title}</h2>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import { IconButton } from '../../IconButton'
|
|||||||
import { MaterialIcon } from '../../Icons/MaterialIcon'
|
import { MaterialIcon } from '../../Icons/MaterialIcon'
|
||||||
import { Typography } from '../../Typography'
|
import { Typography } from '../../Typography'
|
||||||
|
|
||||||
import SidePeekSEO from './SidePeekSEO'
|
import SidePeekSEO from '../SidePeekSEO'
|
||||||
|
|
||||||
import styles from './sidePeekSelfControlled.module.css'
|
import styles from './sidePeekSelfControlled.module.css'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
interface SidePeekSEOProps {
|
interface SidePeekSEOProps {
|
||||||
title: string
|
title: string
|
||||||
|
shouldInert?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sidepeeks generally have important content that should be indexed by search engines.
|
// Sidepeeks generally have important content that should be indexed by search engines.
|
||||||
@@ -7,10 +8,15 @@ interface SidePeekSEOProps {
|
|||||||
// This component is used to provide SEO information for the sidepeek content.
|
// This component is used to provide SEO information for the sidepeek content.
|
||||||
export default function SidePeekSEO({
|
export default function SidePeekSEO({
|
||||||
title,
|
title,
|
||||||
|
shouldInert = false,
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<SidePeekSEOProps>) {
|
}: React.PropsWithChildren<SidePeekSEOProps>) {
|
||||||
return (
|
return (
|
||||||
<div className="sr-only">
|
// Both inert and sr-only to ensure that the content is not focusable
|
||||||
|
// or visible to screen readers but still available for SEO.
|
||||||
|
// The other possible options, such as aria-hidden and the hidden attribute,
|
||||||
|
// are less suitable for SEO purposes.
|
||||||
|
<div className="sr-only" inert={shouldInert}>
|
||||||
<h2>{title}</h2>
|
<h2>{title}</h2>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ interface SidePeekProps {
|
|||||||
openInRoot?: boolean
|
openInRoot?: boolean
|
||||||
handleClose?: (isOpen: boolean) => void
|
handleClose?: (isOpen: boolean) => void
|
||||||
closeLabel: string
|
closeLabel: string
|
||||||
|
shouldInert?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SidePeek({
|
export default function SidePeek({
|
||||||
@@ -30,6 +31,7 @@ export default function SidePeek({
|
|||||||
isOpen,
|
isOpen,
|
||||||
openInRoot = false,
|
openInRoot = false,
|
||||||
closeLabel,
|
closeLabel,
|
||||||
|
shouldInert,
|
||||||
}: React.PropsWithChildren<SidePeekProps>) {
|
}: React.PropsWithChildren<SidePeekProps>) {
|
||||||
const rootDiv = useRef<HTMLDivElement>(null)
|
const rootDiv = useRef<HTMLDivElement>(null)
|
||||||
const headerRef = useRef<HTMLElement>(null)
|
const headerRef = useRef<HTMLElement>(null)
|
||||||
@@ -116,7 +118,9 @@ export default function SidePeek({
|
|||||||
</ModalOverlay>
|
</ModalOverlay>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SidePeekSEO title={title}>{children}</SidePeekSEO>
|
<SidePeekSEO title={title} shouldInert={shouldInert}>
|
||||||
|
{children}
|
||||||
|
</SidePeekSEO>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user