Polish: A11y-, Motion- & Deep-Linking-Sweep (Web Interface Guidelines)
Aus dem web-design-guidelines-Audit über das ganze Frontend: P1 — index.css: prefers-reduced-motion-Block (Animationen/Transitions aus), color-scheme dark/light, zwei `transition: all` → explizite Properties, globaler :focus-visible-Ring. Formular-aria-labels (SystemDrawer-Passwörter inkl. name/autocomplete/spellCheck, Memory, Connect, ModelBrowse, LaneEditor, CustomDialog). Icon-Button-aria-labels + dekorative Icons aria-hidden. P2 — Memory-Lösch-Bestätigung (war sofort), overscroll-behavior:contain auf Modals (CustomDialog/Agent/Cockpit/CommandPalette), ModelBrowse fmtN → Intl.NumberFormat. P3 — Deep-Linking: top-level View im URL-Hash (#models), Reload/Teilen/Cmd-Klick funktionieren; Sidebar-Nav als <a href="#id"> (echte Links, aria-current), hashchange-Sync; index.css-Aktiv-Selektoren button→a nachgezogen. Verifiziert: npm run build (tsc strict) clean; live gegen die Box (Hash-Nav, Reload-Persistenz, aria-current, Aktiv-Styling teal+Akzent, keine Konsolenfehler). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ export function CommandPalette({ onNavigate }: { onNavigate: (v: ViewId) => void
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
label="Befehlspalette"
|
||||
className="fixed inset-0 z-50 flex items-start justify-center bg-black/50 p-4 pt-[12vh]"
|
||||
className="fixed inset-0 z-50 flex items-start justify-center bg-black/50 p-4 pt-[12vh] overscroll-contain"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -15,15 +15,16 @@ export interface CustomDialogProps {
|
||||
export function CustomDialog({ type, title, message, defaultValue, autoValue, autoLabel, onConfirm, onCancel }: CustomDialogProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
return (
|
||||
<div className="fixed inset-0 z-[99] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="fixed inset-0 z-[99] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4 overscroll-contain" role="dialog" aria-modal="true" aria-label={title}>
|
||||
<div className="w-full max-w-sm rounded-2xl border border-border/80 bg-card p-6 shadow-2xl space-y-4 animate-in fade-in zoom-in-95 duration-200">
|
||||
<div className="flex items-center justify-between border-b border-border/20 pb-2">
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-primary">{title}</h3>
|
||||
<button
|
||||
<button
|
||||
onClick={onCancel || (() => onConfirm())}
|
||||
aria-label="Schließen"
|
||||
className="p-1 rounded hover:bg-accent text-muted-foreground hover:text-foreground cursor-pointer"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<X className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">{message}</p>
|
||||
@@ -34,6 +35,7 @@ export function CustomDialog({ type, title, message, defaultValue, autoValue, au
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
defaultValue={defaultValue}
|
||||
aria-label={title}
|
||||
className="flex-1 h-9 px-3 text-xs bg-background/40 border border-border/60 rounded-lg outline-none focus:border-primary transition-colors text-foreground"
|
||||
autoFocus
|
||||
onKeyDown={(e) => {
|
||||
|
||||
@@ -722,15 +722,20 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
|
||||
type={showSudo ? "text" : "password"}
|
||||
value={sudoPasswordInput}
|
||||
onChange={(e) => setSudoPasswordInput(e.target.value)}
|
||||
aria-label="Host Sudo-Passwort"
|
||||
name="sudo-password"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
placeholder="Sudo-Passwort für System-Operationen"
|
||||
className="w-full h-10 pl-3 pr-10 text-xs bg-background/40 border border-border/60 rounded-lg outline-none focus:border-primary transition-colors text-foreground"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowSudo(!showSudo)}
|
||||
aria-label={showSudo ? "Passwort verbergen" : "Passwort anzeigen"}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{showSudo ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
{showSudo ? <EyeOff className="h-4 w-4" aria-hidden="true" /> : <Eye className="h-4 w-4" aria-hidden="true" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[9px] text-muted-foreground leading-normal">
|
||||
@@ -749,15 +754,20 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
|
||||
type={showHf ? "text" : "password"}
|
||||
value={hfTokenInput}
|
||||
onChange={(e) => setHfTokenInput(e.target.value)}
|
||||
placeholder="hf_..."
|
||||
aria-label="HuggingFace API Token"
|
||||
name="hf-token"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
placeholder="hf_…"
|
||||
className="w-full h-10 pl-3 pr-10 text-xs bg-background/40 border border-border/60 rounded-lg outline-none focus:border-primary transition-colors text-foreground"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowHf(!showHf)}
|
||||
aria-label={showHf ? "Token verbergen" : "Token anzeigen"}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{showHf ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
{showHf ? <EyeOff className="h-4 w-4" aria-hidden="true" /> : <Eye className="h-4 w-4" aria-hidden="true" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[9px] text-muted-foreground leading-normal">
|
||||
|
||||
@@ -39,7 +39,8 @@ export function MemoryInputCard() {
|
||||
<textarea
|
||||
value={memContent}
|
||||
onChange={(e) => setMemContent(e.target.value)}
|
||||
placeholder="Fakt / Regel im Pool speichern..."
|
||||
aria-label="Fakt oder Regel im Gedächtnis speichern"
|
||||
placeholder="Fakt / Regel im Pool speichern…"
|
||||
rows={2}
|
||||
className="w-full resize-none rounded-xl border border-border/50 bg-background/30 p-2 text-[10px] outline-none focus:ring-1 focus:ring-primary transition-all leading-normal"
|
||||
/>
|
||||
@@ -47,6 +48,7 @@ export function MemoryInputCard() {
|
||||
<select
|
||||
value={memCat}
|
||||
onChange={(e) => setMemCat(e.target.value)}
|
||||
aria-label="Kategorie"
|
||||
className="h-7 rounded-lg border border-border/50 bg-background/50 px-2 text-[10px] outline-none cursor-pointer"
|
||||
>
|
||||
<option value="stable">🔵 Fakt</option>
|
||||
|
||||
@@ -96,6 +96,7 @@ export function LaneEditor() {
|
||||
value={val as number}
|
||||
min={spec.min}
|
||||
max={spec.max}
|
||||
aria-label={spec.label}
|
||||
onChange={(e) => set(k, Number(e.target.value) as any)}
|
||||
className="h-8 w-full rounded-lg border border-border/40 bg-background/40 px-3 font-mono text-[11px] text-foreground outline-none focus:border-primary/50"
|
||||
/>
|
||||
@@ -103,6 +104,8 @@ export function LaneEditor() {
|
||||
<input
|
||||
type="text"
|
||||
value={val as string}
|
||||
aria-label={spec.label}
|
||||
spellCheck={false}
|
||||
placeholder={k === "coder_lite" ? "(leer = aus)" : ""}
|
||||
onChange={(e) => set(k, e.target.value as any)}
|
||||
className="h-8 w-full rounded-lg border border-border/40 bg-background/40 px-3 font-mono text-[11px] text-foreground outline-none focus:border-primary/50"
|
||||
|
||||
Reference in New Issue
Block a user