feat(dialogs): gedeelde primitives — useDirtyCloseGuard, useDialogSubmitShortcut, layout-classes
Story 1 van PBI "Alle dialogen conform docs/patterns/dialog.md". - components/shared/use-dirty-close-guard.tsx — hook + paired AlertDialog - components/shared/use-dialog-submit-shortcut.ts — Cmd/Ctrl+Enter handler - components/shared/entity-dialog-layout.ts — MD3-conforme classes voor §4 - TaskDialog refactored om beide hooks + classes te gebruiken (geen gedragsverandering) - 8 nieuwe unit-tests Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b47f62966e
commit
b05c4d241b
6 changed files with 219 additions and 47 deletions
16
components/shared/entity-dialog-layout.ts
Normal file
16
components/shared/entity-dialog-layout.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
export const entityDialogContentClasses = cn(
|
||||
'flex flex-col p-0 gap-0',
|
||||
'max-h-[90vh] w-full max-w-[calc(100%-2rem)]',
|
||||
'sm:max-w-[90vw] sm:max-h-[85vh]',
|
||||
'lg:max-w-[50vw] lg:min-w-[480px]',
|
||||
)
|
||||
|
||||
export const entityDialogHeaderClasses =
|
||||
'flex items-center justify-between px-6 pt-5 pb-4 border-b border-outline-variant shrink-0'
|
||||
|
||||
export const entityDialogBodyClasses = 'flex-1 overflow-y-auto px-6 py-6 space-y-6'
|
||||
|
||||
export const entityDialogFooterClasses =
|
||||
'border-t border-outline-variant px-6 py-4 shrink-0'
|
||||
10
components/shared/use-dialog-submit-shortcut.ts
Normal file
10
components/shared/use-dialog-submit-shortcut.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import type { KeyboardEvent } from 'react'
|
||||
|
||||
export function useDialogSubmitShortcut(submit: () => void) {
|
||||
return (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
submit()
|
||||
}
|
||||
}
|
||||
}
|
||||
66
components/shared/use-dirty-close-guard.tsx
Normal file
66
components/shared/use-dirty-close-guard.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
|
||||
export interface DirtyCloseGuard {
|
||||
confirmOpen: boolean
|
||||
setConfirmOpen: (v: boolean) => void
|
||||
attemptClose: () => void
|
||||
confirmDiscard: () => void
|
||||
}
|
||||
|
||||
export function useDirtyCloseGuard(
|
||||
isDirty: boolean,
|
||||
onClose: () => void,
|
||||
): DirtyCloseGuard {
|
||||
const [confirmOpen, setConfirmOpen] = useState(false)
|
||||
|
||||
function attemptClose() {
|
||||
if (isDirty) setConfirmOpen(true)
|
||||
else onClose()
|
||||
}
|
||||
|
||||
function confirmDiscard() {
|
||||
setConfirmOpen(false)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return { confirmOpen, setConfirmOpen, attemptClose, confirmDiscard }
|
||||
}
|
||||
|
||||
export function DirtyCloseGuardDialog({
|
||||
guard,
|
||||
}: {
|
||||
guard: DirtyCloseGuard
|
||||
}) {
|
||||
return (
|
||||
<AlertDialog open={guard.confirmOpen} onOpenChange={guard.setConfirmOpen}>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Wijzigingen niet opgeslagen</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Wil je de wijzigingen weggooien?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => guard.setConfirmOpen(false)}>
|
||||
Terug
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction variant="destructive" onClick={guard.confirmDiscard}>
|
||||
Weggooien
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue