Share one modal shell and inline confirm across the app
Modal owns the overlay, header, close button and footer for the balance, planned-day and QR modals, and closes on Escape (held off while the QR fetch runs). InlineConfirm replaces the hand-built delete and remove-member confirmations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export interface InlineConfirmProps {
|
||||
prompt: ReactNode
|
||||
confirmLabel: ReactNode
|
||||
cancelLabel: ReactNode
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
isBusy?: boolean
|
||||
confirmTestId?: string
|
||||
cancelTestId?: string
|
||||
/** Class for the outer wrapper — the two callers use different elements
|
||||
* for this (`.modal__delete-confirm`, `.settings-panel__remove-confirm`),
|
||||
* so the wrapper's look stays theirs rather than being unified here. */
|
||||
className?: string
|
||||
/** Class for the span wrapping `prompt` — only `PlannedDayModal` needs
|
||||
* one (`.modal__delete-confirm-text`, which pushes the buttons right via
|
||||
* `margin-right: auto`); `Settings`' row has no such class. */
|
||||
promptClassName?: string
|
||||
/** `Settings`' remove-member row shows the danger confirm before cancel;
|
||||
* `PlannedDayModal`'s delete row shows cancel first. Defaults to cancel
|
||||
* first. */
|
||||
confirmFirst?: boolean
|
||||
/** Appends `btn--sm` to both buttons — `Settings`' compact row uses it,
|
||||
* `PlannedDayModal`'s footer-sized row doesn't. */
|
||||
small?: boolean
|
||||
/** `data-testid` on the outer wrapper — `PlannedDayModal`'s row has one
|
||||
* (`delete-confirm`); `Settings`' row doesn't. */
|
||||
testId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The shared "are you sure" row — a prompt plus a danger confirm and a
|
||||
* secondary cancel, both disabled while busy. Replaces the hand-copied
|
||||
* versions in `PlannedDayModal` (delete confirm) and `Settings` (remove-
|
||||
* member confirm), which differ only in wrapper class, prompt wrapping,
|
||||
* button order and button size — all exposed as props here so neither
|
||||
* caller's DOM has to change.
|
||||
*/
|
||||
export function InlineConfirm({
|
||||
prompt,
|
||||
confirmLabel,
|
||||
cancelLabel,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
isBusy = false,
|
||||
confirmTestId,
|
||||
cancelTestId,
|
||||
className,
|
||||
promptClassName,
|
||||
confirmFirst = false,
|
||||
small = false,
|
||||
testId,
|
||||
}: InlineConfirmProps): ReactNode {
|
||||
const sizeClass = small ? ' btn--sm' : ''
|
||||
|
||||
const confirmButton = (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn--danger${sizeClass}`}
|
||||
onClick={onConfirm}
|
||||
disabled={isBusy}
|
||||
data-testid={confirmTestId}
|
||||
>
|
||||
{confirmLabel}
|
||||
</button>
|
||||
)
|
||||
|
||||
const cancelButton = (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn--secondary${sizeClass}`}
|
||||
onClick={onCancel}
|
||||
disabled={isBusy}
|
||||
data-testid={cancelTestId}
|
||||
>
|
||||
{cancelLabel}
|
||||
</button>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={className} data-testid={testId}>
|
||||
<span className={promptClassName}>{prompt}</span>
|
||||
{confirmFirst ? (
|
||||
<>
|
||||
{confirmButton}
|
||||
{cancelButton}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{cancelButton}
|
||||
{confirmButton}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user