73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
import { Button, type ButtonProps } from '@pikku/mantine/core'
|
|
import { m } from '@/i18n/messages'
|
|
import { useLocale } from '@/i18n/config'
|
|
import { type I18nString, type I18nNode } from '@pikku/react'
|
|
|
|
/**
|
|
* A button that requires two clicks to fire, guarding against accidental
|
|
* state transitions. The first click "arms" it — the label swaps to a
|
|
* confirm prompt and the colour goes red; the second click commits and
|
|
* disarms. If left untouched it auto-reverts after `resetMs`.
|
|
*
|
|
* Disarming happens synchronously on the confirming click (not on mutation
|
|
* success), so a failed mutation leaves a normal, retryable button — the
|
|
* caller's `loading` prop drives the in-flight visual.
|
|
*/
|
|
type ConfirmButtonProps = ButtonProps & {
|
|
onConfirm: () => void
|
|
/** Label shown in the armed state. Defaults to the localized "Confirm?". */
|
|
confirmLabel?: I18nString
|
|
/** Colour while armed. */
|
|
confirmColor?: string
|
|
/** Auto-revert delay in ms. */
|
|
resetMs?: number
|
|
children?: I18nNode
|
|
'data-testid'?: string
|
|
}
|
|
|
|
export function ConfirmButton({
|
|
onConfirm,
|
|
confirmLabel,
|
|
confirmColor = 'red',
|
|
resetMs = 3000,
|
|
color,
|
|
variant,
|
|
children,
|
|
'data-testid': testId,
|
|
...rest
|
|
}: ConfirmButtonProps) {
|
|
useLocale()
|
|
const [armed, setArmed] = useState(false)
|
|
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
|
|
useEffect(() => () => clearTimeout(timer.current), [])
|
|
|
|
const handleClick = () => {
|
|
if (armed) {
|
|
clearTimeout(timer.current)
|
|
setArmed(false)
|
|
onConfirm()
|
|
return
|
|
}
|
|
setArmed(true)
|
|
timer.current = setTimeout(() => setArmed(false), resetMs)
|
|
}
|
|
|
|
return (
|
|
<Button
|
|
{...rest}
|
|
key={armed ? 'armed' : 'idle'}
|
|
data-testid={armed && testId ? `${testId}-confirm` : testId}
|
|
data-confirm-state={armed ? 'armed' : 'idle'}
|
|
color={armed ? confirmColor : color}
|
|
variant={armed ? 'filled' : variant}
|
|
onClick={handleClick}
|
|
>
|
|
{armed
|
|
? confirmLabel ?? m.common_pages_admin_booking_status_actions_confirm()
|
|
: children}
|
|
</Button>
|
|
)
|
|
}
|