a11y: skip link exists in App, ChatInput aria-label, sidebar focus trap, MiniPlayer aria-live
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9f7a42cdb5
commit
e5fd019edf
3 changed files with 32 additions and 10 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { useLocation, Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
|
|
@ -13,6 +13,7 @@ import { useSidebarNavigation } from '@/hooks/useSidebarNavigation';
|
|||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tooltip } from '@/components/ui/tooltip';
|
||||
import { FocusTrap } from '@/components/ui/focus-trap';
|
||||
|
||||
interface SidebarProps {
|
||||
currentView?: string;
|
||||
|
|
@ -90,12 +91,25 @@ const navItemInactiveClasses =
|
|||
|
||||
const navItemActiveClasses = 'bg-primary/10 text-primary sidebar-active-indicator';
|
||||
|
||||
const LG_BREAKPOINT = 1024;
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = ({ currentView }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { sidebarOpen, setSidebarOpen } = useUIStore();
|
||||
const { handleMobileNav, handleLogout } = useSidebarNavigation();
|
||||
const navItems = useMemo(() => buildNavItems(t), [t]);
|
||||
const [isMobile, setIsMobile] = useState(() =>
|
||||
typeof window !== 'undefined' ? window.innerWidth < LG_BREAKPOINT : false,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia(`(max-width: ${LG_BREAKPOINT - 1}px)`);
|
||||
const handler = () => setIsMobile(mq.matches);
|
||||
handler();
|
||||
mq.addEventListener('change', handler);
|
||||
return () => mq.removeEventListener('change', handler);
|
||||
}, []);
|
||||
|
||||
const activeView =
|
||||
currentView ||
|
||||
|
|
@ -113,7 +127,11 @@ export const Sidebar: React.FC<SidebarProps> = ({ currentView }) => {
|
|||
/>
|
||||
)}
|
||||
|
||||
<aside
|
||||
<FocusTrap
|
||||
active={sidebarOpen && isMobile}
|
||||
onEscape={() => setSidebarOpen(false)}
|
||||
>
|
||||
<aside
|
||||
data-testid="app-sidebar"
|
||||
className={cn(
|
||||
'fixed left-sidebar bottom-sidebar top-sidebar rounded-xl flex flex-col transition-shell z-sidebar overflow-hidden',
|
||||
|
|
@ -301,6 +319,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ currentView }) => {
|
|||
</Tooltip>
|
||||
</div>
|
||||
</aside>
|
||||
</FocusTrap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ export const ChatInput: React.FC = () => {
|
|||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
placeholder="Broadcast message..."
|
||||
aria-label="Type a message"
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:border-border/50 focus:ring-1 focus:ring-border/50 transition-all font-mono text-sm"
|
||||
disabled={!currentConversationId || isUploading}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { ChevronUp, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { Tooltip } from '@/components/ui/tooltip';
|
||||
import { usePlayer } from '../hooks/usePlayer';
|
||||
import { TrackInfo } from './TrackInfo';
|
||||
|
|
@ -28,6 +29,7 @@ export function MiniPlayer({
|
|||
className,
|
||||
position = 'bottom',
|
||||
}: MiniPlayerProps) {
|
||||
const { t } = useTranslation();
|
||||
const player = usePlayer();
|
||||
|
||||
if (!isVisible || !player.currentTrack) {
|
||||
|
|
@ -55,12 +57,12 @@ export function MiniPlayer({
|
|||
className,
|
||||
)}
|
||||
role="region"
|
||||
aria-label="Mini lecteur audio"
|
||||
aria-label={t('player.miniPlayerAriaLabel')}
|
||||
>
|
||||
<div className="container mx-auto px-4 py-2">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Track Info - Compact */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex-1 min-w-0" aria-live="polite">
|
||||
<TrackInfo
|
||||
track={player.currentTrack}
|
||||
showCover={true}
|
||||
|
|
@ -112,7 +114,7 @@ export function MiniPlayer({
|
|||
</div>
|
||||
|
||||
{/* Toggle Button */}
|
||||
<Tooltip content="Agrandir le lecteur">
|
||||
<Tooltip content={t('player.expandPlayer')}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggle}
|
||||
|
|
@ -122,16 +124,16 @@ export function MiniPlayer({
|
|||
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
|
||||
'transition-colors duration-[var(--sumi-duration-fast)]',
|
||||
)}
|
||||
aria-label="Agrandir le lecteur"
|
||||
aria-label={t('player.expandPlayer')}
|
||||
>
|
||||
<ChevronUp className="h-5 w-5" aria-hidden="true" />
|
||||
<span className="sr-only">Agrandir le lecteur</span>
|
||||
<span className="sr-only">{t('player.expandPlayer')}</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{/* Close Button (optional) */}
|
||||
{onClose && (
|
||||
<Tooltip content="Fermer le mini lecteur">
|
||||
<Tooltip content={t('player.closeMiniPlayer')}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
|
|
@ -141,10 +143,10 @@ export function MiniPlayer({
|
|||
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
|
||||
'transition-colors duration-[var(--sumi-duration-fast)]',
|
||||
)}
|
||||
aria-label="Fermer le mini lecteur"
|
||||
aria-label={t('player.closeMiniPlayer')}
|
||||
>
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
<span className="sr-only">Fermer le mini lecteur</span>
|
||||
<span className="sr-only">{t('player.closeMiniPlayer')}</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue