feat(uat-batch-4): a11y form primitives + click-to-preview + EOI empty-state + lint guards

- FieldError primitive (role=alert, aria-live) — used by Wave 3
  form-error UX work.
- FieldLabel primitive (Label + Info-tooltip slot) — foundational for
  the platform-wide admin-settings tooltip audit.
- ESLint guard against em-dash in user-facing JSX text inside
  src/components + src/app (warning, not error; 111 existing instances
  flagged for follow-up sweep).
- FileGrid card body becomes click-to-preview button (was hidden under
  a kebab); aria-label per row; kebab keeps Download/Rename/Delete.
- DocumentList: title cell on rows with signedFileId opens
  FilePreviewDialog; kebab gains Download action (was missing
  per UAT). Single FilePreviewDialog instance lifted to the parent.
- DocumentList type extended with signedFileId.
- EOI empty state: third ghost button "Mark signed without file"
  wired to existing MarkExternallySignedDialog (parity with
  reservation tab).
- Watcher empty-state padding fix on document-detail.

tsc clean. 1419/1419 vitest. lint clean on touched files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 17:20:13 +02:00
parent 6a4f4ea1dd
commit 52342ee45d
7 changed files with 212 additions and 17 deletions

View File

@@ -2,7 +2,6 @@
import {
Download,
Eye,
FileText,
Film,
Image,
@@ -22,7 +21,6 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Skeleton } from '@/components/ui/skeleton';
import { PREVIEWABLE_MIMES } from '@/lib/constants/file-validation';
export interface FileRow {
id: string;
@@ -102,9 +100,14 @@ export function FileGrid({
{files.map((file) => (
<div
key={file.id}
className="group relative rounded-lg border bg-card p-3 hover:border-primary/50 hover:shadow-sm transition-all"
className="group relative rounded-lg border bg-card hover:border-primary/50 hover:shadow-sm transition-all"
>
<div className="flex flex-col items-center gap-2">
<button
type="button"
onClick={() => onPreview(file)}
aria-label={`Preview ${file.filename}`}
className="flex w-full flex-col items-center gap-2 rounded-lg p-3 text-left focus:outline-none focus:ring-2 focus:ring-ring"
>
<FileIcon mimeType={file.mimeType} />
<p className="w-full truncate text-center text-xs font-medium" title={file.filename}>
{file.filename}
@@ -113,7 +116,7 @@ export function FileGrid({
<span>{formatBytes(file.sizeBytes)}</span>
<span>{format(new Date(file.createdAt), 'MMM d, yyyy')}</span>
</div>
</div>
</button>
<div className="absolute right-1 top-1 opacity-0 group-hover:opacity-100 transition-opacity">
<DropdownMenu>
@@ -127,12 +130,6 @@ export function FileGrid({
<Download className="mr-2 h-3.5 w-3.5" />
Download
</DropdownMenuItem>
{file.mimeType && PREVIEWABLE_MIMES.has(file.mimeType) && (
<DropdownMenuItem onClick={() => onPreview(file)}>
<Eye className="mr-2 h-3.5 w-3.5" />
Preview
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={() => onRename(file)}>
<Pencil className="mr-2 h-3.5 w-3.5" />
Rename