feat: add ConfirmDialog composite component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
97
src/design-system/composites/ConfirmDialog/ConfirmDialog.tsx
Normal file
97
src/design-system/composites/ConfirmDialog/ConfirmDialog.tsx
Normal file
@@ -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<HTMLInputElement>(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 (
|
||||
<Modal open={open} onClose={onClose} size="sm" className={className}>
|
||||
<div className={styles.content}>
|
||||
<h2 className={styles.title}>{title}</h2>
|
||||
<p className={styles.message}>{message}</p>
|
||||
|
||||
<div className={styles.inputGroup}>
|
||||
<label className={styles.label} htmlFor="confirm-input">
|
||||
{`Type "${confirmText}" to confirm`}
|
||||
</label>
|
||||
<input
|
||||
ref={inputRef}
|
||||
id="confirm-input"
|
||||
className={styles.input}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.buttonRow}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
disabled={loading}
|
||||
type="button"
|
||||
>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button
|
||||
variant={confirmButtonVariant}
|
||||
onClick={onConfirm}
|
||||
loading={loading}
|
||||
disabled={!matches || loading}
|
||||
type="button"
|
||||
>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user