fix: ForkModal accessibility and UX (escape, click-outside, labels, maxLength)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-25 00:14:44 -05:00
parent b6e3efd30a
commit ffcddab475

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { GitBranch, X } from 'lucide-react'
import { treesApi } from '@/api/trees'
import { toast } from '@/lib/toast'
@@ -18,6 +18,14 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', handleKeyDown)
return () => document.removeEventListener('keydown', handleKeyDown)
}, [onClose])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!name.trim()) return
@@ -40,8 +48,14 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
<div className="w-full max-w-md rounded-xl border border-border bg-card shadow-xl">
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
onClick={onClose}
>
<div
className="w-full max-w-md rounded-xl border border-border bg-card shadow-xl"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-5 py-4">
<div className="flex items-center gap-2">
@@ -50,6 +64,7 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
</div>
<button
onClick={onClose}
aria-label="Close"
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<X className="h-4 w-4" />
@@ -59,15 +74,17 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
{/* Body */}
<form onSubmit={handleSubmit} className="space-y-4 px-5 py-4">
<div>
<label className="mb-1.5 block text-xs font-medium text-muted-foreground">
<label htmlFor="fork-name" className="mb-1.5 block text-xs font-medium text-muted-foreground">
Name <span className="text-red-400">*</span>
</label>
<input
id="fork-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
autoFocus
maxLength={255}
className={cn(
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
@@ -76,11 +93,12 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
</div>
<div>
<label className="mb-1.5 block text-xs font-medium text-muted-foreground">
<label htmlFor="fork-reason" className="mb-1.5 block text-xs font-medium text-muted-foreground">
Reason for Forking{' '}
<span className="text-muted-foreground/60">(optional)</span>
</label>
<textarea
id="fork-reason"
value={forkReason}
onChange={(e) => setForkReason(e.target.value)}
rows={3}
@@ -108,7 +126,7 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
<button
type="submit"
disabled={isSubmitting || !name.trim()}
className="bg-gradient-brand flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-40"
className="bg-gradient-brand flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed"
>
{isSubmitting ? 'Forking…' : 'Fork Flow'}
</button>