From f7d30c12574e06d476de2b7421cb4ff5ca60180b Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:06:38 +0100 Subject: [PATCH] feat: add ConfirmDialog composite component Co-Authored-By: Claude Sonnet 4.6 --- .../ConfirmDialog/ConfirmDialog.module.css | 58 +++++++++++ .../ConfirmDialog/ConfirmDialog.tsx | 97 +++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/design-system/composites/ConfirmDialog/ConfirmDialog.module.css create mode 100644 src/design-system/composites/ConfirmDialog/ConfirmDialog.tsx diff --git a/src/design-system/composites/ConfirmDialog/ConfirmDialog.module.css b/src/design-system/composites/ConfirmDialog/ConfirmDialog.module.css new file mode 100644 index 0000000..d32417b --- /dev/null +++ b/src/design-system/composites/ConfirmDialog/ConfirmDialog.module.css @@ -0,0 +1,58 @@ +.content { + display: flex; + flex-direction: column; + gap: 12px; + padding: 4px 0; + font-family: var(--font-body); +} + +.title { + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + margin: 0; + line-height: 1.3; +} + +.message { + font-size: 14px; + color: var(--text-secondary); + margin: 0; + line-height: 1.5; +} + +.inputGroup { + display: flex; + flex-direction: column; + gap: 6px; +} + +.label { + font-size: 12px; + color: var(--text-secondary); +} + +.input { + width: 100%; + padding: 6px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-raised); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 12px; + outline: none; + transition: border-color 0.15s, box-shadow 0.15s; +} + +.input:focus { + border-color: var(--amber); + box-shadow: 0 0 0 3px var(--amber-bg); +} + +.buttonRow { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 4px; +} diff --git a/src/design-system/composites/ConfirmDialog/ConfirmDialog.tsx b/src/design-system/composites/ConfirmDialog/ConfirmDialog.tsx new file mode 100644 index 0000000..af9702a --- /dev/null +++ b/src/design-system/composites/ConfirmDialog/ConfirmDialog.tsx @@ -0,0 +1,97 @@ +import { useState, useEffect, useRef } from 'react' +import { Modal } from '../Modal/Modal' +import { Button } from '../../primitives/Button/Button' +import styles from './ConfirmDialog.module.css' + +export interface ConfirmDialogProps { + open: boolean + onClose: () => void + onConfirm: () => void + title?: string + message: string + confirmText: string + confirmLabel?: string + cancelLabel?: string + variant?: 'danger' | 'warning' | 'info' + loading?: boolean + className?: string +} + +export function ConfirmDialog({ + open, + onClose, + onConfirm, + title = 'Confirm Deletion', + message, + confirmText, + confirmLabel = 'Delete', + cancelLabel = 'Cancel', + variant = 'danger', + loading = false, + className, +}: ConfirmDialogProps) { + const [input, setInput] = useState('') + const inputRef = useRef(null) + const matches = input === confirmText + + useEffect(() => { + if (open) { + setInput('') + const id = setTimeout(() => inputRef.current?.focus(), 0) + return () => clearTimeout(id) + } + }, [open]) + + function handleKeyDown(e: React.KeyboardEvent) { + if (e.key === 'Enter' && matches && !loading) { + e.preventDefault() + onConfirm() + } + } + + const confirmButtonVariant = variant === 'danger' ? 'danger' : 'primary' + + return ( + +
+

{title}

+

{message}

+ +
+ + setInput(e.target.value)} + onKeyDown={handleKeyDown} + autoComplete="off" + /> +
+ +
+ + +
+
+
+ ) +}