mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 11:58:53 +00:00
♿️(frontend) focus side panel container on open
Focus aside on side panel open so screen readers announce the aria-label
This commit is contained in:
@@ -29,6 +29,7 @@ and this project adheres to
|
|||||||
|
|
||||||
- 🩹(backend) identify externally provisioned users to PostHog
|
- 🩹(backend) identify externally provisioned users to PostHog
|
||||||
- 🐛(backend) fix info panel crash for unregistered rooms
|
- 🐛(backend) fix info panel crash for unregistered rooms
|
||||||
|
- ♿️(frontend) focus side panel container on open #1452
|
||||||
|
|
||||||
## [1.23.0] - 2026-07-08
|
## [1.23.0] - 2026-07-08
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { Button, Div } from '@/primitives'
|
|||||||
import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'
|
import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ParticipantsList } from './controls/Participants/ParticipantsList'
|
import { ParticipantsList } from './controls/Participants/ParticipantsList'
|
||||||
import { useSidePanel } from '../hooks/useSidePanel'
|
import { PanelId, useSidePanel } from '../hooks/useSidePanel'
|
||||||
import { ReactNode } from 'react'
|
import React, { ReactNode, useCallback, useRef } from 'react'
|
||||||
import { Chat } from '../prefabs/Chat'
|
import { Chat } from '../prefabs/Chat'
|
||||||
import { Effects } from './effects/Effects'
|
import { Effects } from './effects/Effects'
|
||||||
import { Admin } from './Admin'
|
import { Admin } from './Admin'
|
||||||
@@ -15,6 +15,7 @@ import { Tools } from './Tools'
|
|||||||
import { Info } from './Info'
|
import { Info } from './Info'
|
||||||
import { HStack } from '@/styled-system/jsx'
|
import { HStack } from '@/styled-system/jsx'
|
||||||
import { useReactionsToolbar } from '@/features/reactions/hooks/useReactionsToolbar'
|
import { useReactionsToolbar } from '@/features/reactions/hooks/useReactionsToolbar'
|
||||||
|
import { useRestoreFocus } from '@/hooks/useRestoreFocus'
|
||||||
|
|
||||||
type StyledSidePanelProps = {
|
type StyledSidePanelProps = {
|
||||||
title: string
|
title: string
|
||||||
@@ -29,103 +30,115 @@ type StyledSidePanelProps = {
|
|||||||
isReactionToolbarOpen?: boolean
|
isReactionToolbarOpen?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledSidePanel = ({
|
const StyledSidePanel = React.forwardRef<HTMLElement, StyledSidePanelProps>(
|
||||||
title,
|
(
|
||||||
ariaLabel,
|
{
|
||||||
children,
|
title,
|
||||||
onClose,
|
ariaLabel,
|
||||||
isClosed,
|
children,
|
||||||
isReactionToolbarOpen,
|
onClose,
|
||||||
closeButtonTooltip,
|
isClosed,
|
||||||
isSubmenu = false,
|
isReactionToolbarOpen,
|
||||||
onBack,
|
closeButtonTooltip,
|
||||||
backButtonLabel,
|
isSubmenu = false,
|
||||||
}: StyledSidePanelProps) => (
|
onBack,
|
||||||
<aside
|
backButtonLabel,
|
||||||
className={css({
|
},
|
||||||
borderWidth: '1px',
|
ref
|
||||||
borderStyle: 'solid',
|
) => (
|
||||||
borderColor: 'box.border',
|
<aside
|
||||||
backgroundColor: 'box.bg',
|
ref={ref}
|
||||||
color: 'box.text',
|
tabIndex={-1}
|
||||||
borderRadius: 8,
|
className={css({
|
||||||
flex: 1,
|
borderWidth: '1px',
|
||||||
position: 'absolute',
|
borderStyle: 'solid',
|
||||||
overflow: 'hidden',
|
borderColor: 'box.border',
|
||||||
display: 'flex',
|
backgroundColor: 'box.bg',
|
||||||
flexDirection: 'column',
|
color: 'box.text',
|
||||||
margin: 'var(--sizes-room-side-panel-margin)',
|
borderRadius: 8,
|
||||||
marginLeft: 0,
|
flex: 1,
|
||||||
marginBottom: 0,
|
position: 'absolute',
|
||||||
padding: 0,
|
overflow: 'hidden',
|
||||||
gap: 0,
|
display: 'flex',
|
||||||
right: 0,
|
flexDirection: 'column',
|
||||||
top: 0,
|
margin: 'var(--sizes-room-side-panel-margin)',
|
||||||
width: 'var(--sizes-room-side-panel)',
|
marginLeft: 0,
|
||||||
transition: '.5s cubic-bezier(.4,0,.2,1) 5ms',
|
marginBottom: 0,
|
||||||
})}
|
padding: 0,
|
||||||
style={{
|
gap: 0,
|
||||||
transform: isClosed
|
right: 0,
|
||||||
? 'translateX(calc(var(--sizes-room-side-panel) + var(--sizes-room-side-panel-margin)))'
|
top: 0,
|
||||||
: 'none',
|
width: 'var(--sizes-room-side-panel)',
|
||||||
bottom: isReactionToolbarOpen
|
transition: '.5s cubic-bezier(.4,0,.2,1) 5ms',
|
||||||
? 'calc( var(--sizes-room-control-bar) + var(--sizes-room-reaction-toolbar-height) + calc(var(--lk-grid-gap) / 2))'
|
'&:focus': {
|
||||||
: 'var(--sizes-room-control-bar)',
|
outline: 'none',
|
||||||
}}
|
},
|
||||||
aria-hidden={isClosed}
|
})}
|
||||||
aria-label={ariaLabel}
|
style={{
|
||||||
>
|
transform: isClosed
|
||||||
<HStack alignItems="center">
|
? 'translateX(calc(var(--sizes-room-side-panel) + var(--sizes-room-side-panel-margin)))'
|
||||||
{isSubmenu && (
|
: 'none',
|
||||||
<Button
|
bottom: isReactionToolbarOpen
|
||||||
variant="secondaryText"
|
? 'calc( var(--sizes-room-control-bar) + var(--sizes-room-reaction-toolbar-height) + calc(var(--lk-grid-gap) / 2))'
|
||||||
size="sm"
|
: 'var(--sizes-room-control-bar)',
|
||||||
square
|
}}
|
||||||
className={css({ marginRight: '0.5rem', marginLeft: '1rem' })}
|
aria-hidden={isClosed}
|
||||||
aria-label={backButtonLabel}
|
aria-label={ariaLabel}
|
||||||
onPress={onBack}
|
>
|
||||||
|
<HStack alignItems="center">
|
||||||
|
{isSubmenu && (
|
||||||
|
<Button
|
||||||
|
variant="secondaryText"
|
||||||
|
size="sm"
|
||||||
|
square
|
||||||
|
className={css({ marginRight: '0.5rem', marginLeft: '1rem' })}
|
||||||
|
aria-label={backButtonLabel}
|
||||||
|
onPress={onBack}
|
||||||
|
>
|
||||||
|
<RiArrowLeftLine size={20} aria-hidden="true" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Heading
|
||||||
|
slot="title"
|
||||||
|
level={1}
|
||||||
|
className={text({ variant: 'h2' })}
|
||||||
|
style={{
|
||||||
|
paddingLeft: isSubmenu ? 0 : '1.5rem',
|
||||||
|
paddingTop: '1rem',
|
||||||
|
display: isClosed ? 'none' : 'flex',
|
||||||
|
justifyContent: 'start',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RiArrowLeftLine size={20} aria-hidden="true" />
|
{title}
|
||||||
</Button>
|
</Heading>
|
||||||
)}
|
</HStack>
|
||||||
<Heading
|
<Div
|
||||||
slot="title"
|
position="absolute"
|
||||||
level={1}
|
top="5"
|
||||||
className={text({ variant: 'h2' })}
|
right="5"
|
||||||
style={{
|
style={{
|
||||||
paddingLeft: isSubmenu ? 0 : '1.5rem',
|
display: isClosed ? 'none' : undefined,
|
||||||
paddingTop: '1rem',
|
|
||||||
display: isClosed ? 'none' : 'flex',
|
|
||||||
justifyContent: 'start',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
<Button
|
||||||
</Heading>
|
invisible
|
||||||
</HStack>
|
variant="tertiaryText"
|
||||||
<Div
|
size="xs"
|
||||||
position="absolute"
|
onPress={onClose}
|
||||||
top="5"
|
aria-label={closeButtonTooltip}
|
||||||
right="5"
|
tooltip={closeButtonTooltip}
|
||||||
style={{
|
>
|
||||||
display: isClosed ? 'none' : undefined,
|
<RiCloseLine />
|
||||||
}}
|
</Button>
|
||||||
>
|
</Div>
|
||||||
<Button
|
{children}
|
||||||
invisible
|
</aside>
|
||||||
variant="tertiaryText"
|
)
|
||||||
size="xs"
|
|
||||||
onPress={onClose}
|
|
||||||
aria-label={closeButtonTooltip}
|
|
||||||
tooltip={closeButtonTooltip}
|
|
||||||
>
|
|
||||||
<RiCloseLine />
|
|
||||||
</Button>
|
|
||||||
</Div>
|
|
||||||
{children}
|
|
||||||
</aside>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
StyledSidePanel.displayName = 'StyledSidePanel'
|
||||||
|
|
||||||
type PanelProps = {
|
type PanelProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@@ -162,8 +175,24 @@ export const SidePanel = () => {
|
|||||||
|
|
||||||
const { isOpen: isReactionToolbarOpen } = useReactionsToolbar()
|
const { isOpen: isReactionToolbarOpen } = useReactionsToolbar()
|
||||||
|
|
||||||
|
const asideRef = useRef<HTMLElement>(null)
|
||||||
|
|
||||||
|
const panelManagesFocus = activePanelId === PanelId.CHAT
|
||||||
|
|
||||||
|
const focusAside = useCallback(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
asideRef.current?.focus({ preventScroll: true })
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useRestoreFocus(isSidePanelOpen && !panelManagesFocus, {
|
||||||
|
onOpened: focusAside,
|
||||||
|
preventScroll: true,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSidePanel
|
<StyledSidePanel
|
||||||
|
ref={asideRef}
|
||||||
title={title}
|
title={title}
|
||||||
ariaLabel={t('ariaLabel', { title })}
|
ariaLabel={t('ariaLabel', { title })}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user