Files
AfiS-Aktensystem/web/components/ProfileMenu.tsx
T
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

102 lines
3.0 KiB
TypeScript

'use client'
import { useEffect, useRef, useState, type ReactNode } from 'react'
import Link from 'next/link'
import {
authText,
notifyText,
overviewText,
profileText,
themeText,
} from '@/lib/i18n/shell'
import { ThemeToggle } from '@/components/ThemeToggle'
/**
* Konto und Einstellungen, oben rechts.
*
* Alles, was den einzelnen Nutzer betrifft und nicht die Akten, sammelt sich
* hinter dem Profilbild: das Thema, die Benachrichtigungen, der eigene Zugang,
* das Abmelden. In der Kopfzeile standen diese Dinge vorher zwischen den
* Aktenbereichen und nahmen dort Platz weg, den die Arbeit braucht.
*
* Das Abmelden kommt als Server Action von aussen herein - dieses Fenster darf
* die Sitzung nicht selbst beenden können.
*/
export function ProfileMenu({
displayName,
username,
avatar,
signOutAction,
}: {
displayName: string
username: string
avatar: ReactNode
signOutAction: () => Promise<void>
}) {
const [offen, setOffen] = useState(false)
const huelle = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!offen) return
function daneben(event: PointerEvent) {
if (!huelle.current?.contains(event.target as Node)) setOffen(false)
}
function fluchttaste(event: KeyboardEvent) {
if (event.key === 'Escape') setOffen(false)
}
document.addEventListener('pointerdown', daneben)
document.addEventListener('keydown', fluchttaste)
return () => {
document.removeEventListener('pointerdown', daneben)
document.removeEventListener('keydown', fluchttaste)
}
}, [offen])
return (
<div ref={huelle} className="relative">
<button
type="button"
onClick={() => setOffen((vorher) => !vorher)}
aria-expanded={offen}
aria-haspopup="menu"
aria-label={profileText.open}
className="btn btn-sm p-1"
>
{avatar}
</button>
{offen ? (
<div
role="menu"
className="absolute top-full right-0 z-40 mt-2 w-64 border border-line bg-panel p-4"
>
<p className="font-condensed text-xs text-text">{displayName}</p>
<p className="font-mono text-2xs text-text-dim">@{username}</p>
<div className="mt-4 flex items-center justify-between gap-3 border-t border-line pt-3">
<span className="font-condensed text-2xs text-text-dim">{themeText.label}</span>
<ThemeToggle />
</div>
<nav className="mt-3 flex flex-col items-start gap-2 border-t border-line pt-3">
<Link href="/einstellungen" className="btn btn-sm" onClick={() => setOffen(false)}>
{notifyText.heading}
</Link>
<Link href="/zugang" className="btn btn-sm" onClick={() => setOffen(false)}>
{overviewText.heading}
</Link>
</nav>
<form action={signOutAction} className="mt-3 border-t border-line pt-3">
<button type="submit" className="btn btn-sm btn-primary">
{authText.signOut}
</button>
</form>
</div>
) : null}
</div>
)
}