142 lines
4.5 KiB
TypeScript
142 lines
4.5 KiB
TypeScript
/**
|
|
* useConfirm — application-level confirm dialog replacing native browser confirm().
|
|
*
|
|
* Usage:
|
|
* const { confirm, ConfirmDialog } = useConfirm();
|
|
* const ok = await confirm('Wirklich löschen?', { confirmLabel: 'Löschen', variant: 'danger' });
|
|
* // Render <ConfirmDialog /> once in the component tree.
|
|
*/
|
|
|
|
import React, { useState, useCallback, useRef, useMemo } from 'react';
|
|
import { useLanguage } from '../providers/language/LanguageContext';
|
|
|
|
export interface ConfirmOptions {
|
|
title?: string;
|
|
confirmLabel?: string;
|
|
cancelLabel?: string;
|
|
variant?: 'primary' | 'danger';
|
|
}
|
|
|
|
interface ConfirmState {
|
|
message: string;
|
|
options: Required<ConfirmOptions>;
|
|
resolve: (value: boolean) => void;
|
|
}
|
|
|
|
export function useConfirm() {
|
|
const { t } = useLanguage();
|
|
const [state, setState] = useState<ConfirmState | null>(null);
|
|
const resolveRef = useRef<((v: boolean) => void) | null>(null);
|
|
|
|
const _defaults: Required<ConfirmOptions> = useMemo(() => ({
|
|
title: t('Bestätigung'),
|
|
confirmLabel: t('Bestätigen'),
|
|
cancelLabel: t('Abbrechen'),
|
|
variant: 'primary' as const,
|
|
}), [t]);
|
|
|
|
const confirm = useCallback((message: string, options?: ConfirmOptions): Promise<boolean> => {
|
|
return new Promise<boolean>((resolve) => {
|
|
resolveRef.current = resolve;
|
|
setState({
|
|
message,
|
|
options: { ..._defaults, ...options },
|
|
resolve,
|
|
});
|
|
});
|
|
}, [_defaults]);
|
|
|
|
const _handleConfirm = useCallback(() => {
|
|
resolveRef.current?.(true);
|
|
resolveRef.current = null;
|
|
setState(null);
|
|
}, []);
|
|
|
|
const _handleCancel = useCallback(() => {
|
|
resolveRef.current?.(false);
|
|
resolveRef.current = null;
|
|
setState(null);
|
|
}, []);
|
|
|
|
const ConfirmDialog: React.FC = useCallback(() => {
|
|
if (!state) return null;
|
|
|
|
const { message, options } = state;
|
|
const isDanger = options.variant === 'danger';
|
|
|
|
return (
|
|
<div
|
|
// Backdrop intentionally has NO onClick handler: this confirm dialog
|
|
// must only close via the explicit Cancel/Confirm buttons or Escape.
|
|
// Accidental outside-clicks should NOT dismiss a decision the user
|
|
// hasn't made yet. (UX policy for all modal dialogs in PORTA.)
|
|
style={{
|
|
position: 'fixed', inset: 0, zIndex: 10000,
|
|
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(2px)',
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
}}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Escape') _handleCancel();
|
|
}}
|
|
tabIndex={-1}
|
|
>
|
|
<div
|
|
onClick={(e) => e.stopPropagation()}
|
|
style={{
|
|
background: 'var(--surface-color, #1a1a2e)',
|
|
border: '1px solid var(--color-border, #333)',
|
|
borderRadius: '12px',
|
|
padding: '1.5rem',
|
|
minWidth: 340, maxWidth: 480,
|
|
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
|
|
display: 'flex', flexDirection: 'column', gap: '1.25rem',
|
|
}}
|
|
>
|
|
<h3 style={{
|
|
margin: 0, fontSize: '1.05rem', fontWeight: 600,
|
|
color: 'var(--text-primary, #e0e0e0)',
|
|
}}>
|
|
{options.title}
|
|
</h3>
|
|
|
|
<p style={{
|
|
margin: 0, fontSize: '0.9rem', lineHeight: 1.5,
|
|
color: 'var(--text-secondary, #999)',
|
|
}}>
|
|
{message}
|
|
</p>
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.75rem' }}>
|
|
<button
|
|
onClick={_handleCancel}
|
|
style={{
|
|
padding: '8px 18px', borderRadius: '6px', fontSize: '0.875rem', fontWeight: 500,
|
|
border: '1px solid var(--color-border, #444)',
|
|
background: 'transparent',
|
|
color: 'var(--text-primary, #e8e8e8)',
|
|
cursor: 'pointer',
|
|
}}
|
|
>
|
|
{options.cancelLabel}
|
|
</button>
|
|
<button
|
|
onClick={_handleConfirm}
|
|
autoFocus
|
|
style={{
|
|
padding: '8px 18px', borderRadius: '6px', fontSize: '0.875rem', fontWeight: 600,
|
|
border: 'none',
|
|
background: isDanger ? '#ef4444' : 'var(--primary-color, #F25843)',
|
|
color: '#fff',
|
|
cursor: 'pointer',
|
|
}}
|
|
>
|
|
{options.confirmLabel}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}, [state, _handleConfirm, _handleCancel]);
|
|
|
|
return { confirm, ConfirmDialog };
|
|
}
|