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