Files
tessera-ctl/apps/web/src/components/layout/sidebar-footer.tsx
T
schalli 27a6e2952c fix(quick-260921-bi2): a11y - Layout-Navigation und Einstellungsformulare
useButtonType: Hamburger-, Profil-, Kategorie-, Ein-/Ausklapp- und Logout-
Buttons (kein Formular in diesen Dateien) erhalten type="button".

noSvgWithoutTitle: Icons neben sichtbarem Text werden dekorativ
(aria-hidden="true"). Zwei bislang unbenannte interaktive Elemente erhalten
zusaetzlich ein aria-label, weil ihr Text im eingeklappten Sidebar-Zustand
verschwindet bzw. ganz fehlte: die Dashboard-/Marketplace-Links und der
"Einstellungen"-Button in der Seitenleiste (t('dashboard')/t('marketplace')/
t('settings'), alle bereits vorhandene Schluessel) sowie der mobile
Schliessen-Button (tCommon('close')).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 09:21:43 +02:00

78 lines
3.1 KiB
TypeScript

'use client';
import { useTranslations } from 'next-intl';
import { useSidebarStore } from '@/lib/stores/sidebar-store';
import { useAuthStore } from '@/lib/stores/auth-store';
import { LocaleSwitcher } from '@/components/locale-switcher';
export function SidebarFooter() {
const t = useTranslations('sidebar');
const tHeader = useTranslations('header');
const isCollapsed = useSidebarStore((s) => s.isCollapsed);
const user = useAuthStore((s) => s.user);
const userInitial = user
? (user.displayName ?? user.username).charAt(0).toUpperCase()
: '?';
const userName = user?.displayName ?? user?.username ?? t('userPlaceholder');
const roleLabel = user ? tHeader(`role.${user.role}`) : '';
return (
<div className="border-t border-sidebar-border p-3 flex flex-col gap-2">
{/* Locale switcher */}
{!isCollapsed && (
<div className="px-2 py-1">
<LocaleSwitcher />
</div>
)}
{/* Settings */}
<button
type="button"
aria-label={t('settings')}
className="flex items-center gap-3 rounded-md px-2 py-1.5 text-sm text-sidebar-foreground hover:bg-muted transition-colors"
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="shrink-0"
>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
{!isCollapsed && (
<span className="truncate">{t('settings')}</span>
)}
</button>
{/* User info - wired to auth store */}
<div className="flex items-center gap-3 px-2 py-1.5">
<div className="h-7 w-7 shrink-0 rounded-full bg-primary flex items-center justify-center text-xs font-medium text-primary-foreground">
{userInitial}
</div>
{!isCollapsed && (
<div className="flex flex-col overflow-hidden">
<span className="text-sm text-sidebar-foreground truncate">
{userName}
</span>
{user && (
<span className="text-xs text-muted-foreground truncate">
{roleLabel}
</span>
)}
</div>
)}
</div>
</div>
);
}