Files
Anthony Lio 42f1401769 fix(ui-patterns): a11y accessible names for ExpandableVideo (#50226)
## What kind of change does this PR introduce?

bug fix a11y `ExapndableVideo` 

## What is the current behavior?

`ExpandableVideo` blurred thumbnail has `alt="Video guide preview"`
sitting behind an overlay that already reads "Watch video guide" making
screen readers announcing the same thing twice

## What is the new behavior?

- adds an optional `videoTitle` prop that names the video once and feeds
both the button's `aria-label` and the player's `title`.

## Test
1. visit `/docs/guides/functions`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Enhancements**
  - Video previews in guides now display the relevant guide title.
  - Partner introduction videos now include a descriptive title.
- Video controls and embedded players provide more specific
accessibility labels when titles are available.
- Preview images without meaningful alternative text are treated as
decorative to reduce redundant screen-reader output.
- **Bug Fixes**
- Guide titles with Markdown formatting now appear as clean, readable
text in video labels.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-11 23:37:52 +03:00

71 lines
2.1 KiB
TypeScript

'use client'
import GuidesTableOfContents from '~/components/GuidesSidebar'
import { TocAnchorsProvider } from '~/features/docs/GuidesMdx.client'
import { type GuideFrontmatter } from '~/lib/docs'
import { mdToPlainText } from '~/lib/md-to-plain-text'
import { createContext, useContext, type ReactNode } from 'react'
import { cn } from 'ui'
interface GuideContextValue {
meta?: GuideFrontmatter
}
const GuideContext = createContext<GuideContextValue | undefined>(undefined)
export const useGuide = () => {
const context = useContext(GuideContext)
if (!context) {
throw new Error('useGuide must be used within a GuideProvider')
}
return context
}
interface GuideProps {
meta?: GuideFrontmatter
children?: ReactNode
className?: string
}
export function Guide({ meta, children, className }: GuideProps) {
const hideToc = meta?.hideToc || meta?.hide_table_of_contents
return (
<GuideContext.Provider value={{ meta }}>
<TocAnchorsProvider>
<div className={cn('grid grid-cols-12 relative gap-4', className)}>
<div
className={cn(
'relative',
'transition-all ease-out',
'duration-100',
hideToc ? 'col-span-12' : 'col-span-12 md:col-span-8'
)}
>
{children}
</div>
{!hideToc && (
<GuidesTableOfContents
video={meta?.tocVideo}
videoTitle={meta?.title ? mdToPlainText(meta.title) : undefined}
className={cn(
'hidden md:flex',
'md:col-span-3 md:col-start-10',
'self-start',
'sticky',
/**
* --header-height: height of nav
* 3rem: content padding
*/
'top-[calc(var(--header-height)+3rem)]',
// 4rem accounts for 3rem of top padding + 1rem of extra breathing room
'max-h-[calc(100vh-var(--header-height)-4rem)]'
)}
/>
)}
</div>
</TocAnchorsProvider>
</GuideContext.Provider>
)
}