155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
import {
|
|
Button,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@repo/shadcn-ui/components";
|
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
import {
|
|
ArrowLeftIcon,
|
|
CopyIcon,
|
|
EyeIcon,
|
|
MoreHorizontalIcon,
|
|
RotateCcwIcon,
|
|
Trash2Icon,
|
|
} from "lucide-react";
|
|
import { useFormContext } from "react-hook-form";
|
|
import { CancelFormButton, CancelFormButtonProps } from "./cancel-form-button";
|
|
import { SubmitButtonProps, SubmitFormButton } from "./submit-form-button";
|
|
|
|
type Align = "start" | "center" | "end" | "between";
|
|
|
|
type GroupSubmitButtonProps = Omit<SubmitButtonProps, "isLoading" | "preventDoubleSubmit">;
|
|
|
|
export type FormCommitButtonGroupProps = {
|
|
className?: string;
|
|
align?: Align; // default "end"
|
|
gap?: string; // default "gap-2"
|
|
reverseOrderOnMobile?: boolean; // default true (Cancel debajo en móvil)
|
|
|
|
isLoading?: boolean;
|
|
disabled?: boolean;
|
|
preventDoubleSubmit?: boolean; // Evita múltiples submits mientras loading
|
|
|
|
cancel?: CancelFormButtonProps & { show?: boolean };
|
|
submit?: GroupSubmitButtonProps; // props directas a SubmitButton
|
|
|
|
onReset?: () => void;
|
|
onDelete?: () => void;
|
|
onPreview?: () => void;
|
|
onDuplicate?: () => void;
|
|
onBack?: () => void;
|
|
};
|
|
|
|
const alignToJustify: Record<Align, string> = {
|
|
start: "justify-start",
|
|
center: "justify-center",
|
|
end: "justify-end",
|
|
between: "justify-between",
|
|
};
|
|
|
|
export const FormCommitButtonGroup = ({
|
|
className,
|
|
align = "end",
|
|
gap = "gap-2",
|
|
reverseOrderOnMobile = true,
|
|
|
|
isLoading,
|
|
disabled = false,
|
|
preventDoubleSubmit = true,
|
|
|
|
cancel,
|
|
submit,
|
|
|
|
onReset,
|
|
onDelete,
|
|
onPreview,
|
|
onDuplicate,
|
|
onBack,
|
|
}: FormCommitButtonGroupProps) => {
|
|
const showCancel = cancel?.show ?? true;
|
|
const hasSecondaryActions = onReset || onPreview || onDuplicate || onBack || onDelete;
|
|
|
|
// ⛳️ RHF opcional: auto-detectar isSubmitting si no se pasó isLoading
|
|
let rhfIsSubmitting = false;
|
|
try {
|
|
const ctx = useFormContext();
|
|
rhfIsSubmitting = !!ctx?.formState?.isSubmitting;
|
|
} catch {
|
|
// No hay provider de RHF; ignorar
|
|
}
|
|
const busy = isLoading ?? rhfIsSubmitting;
|
|
const computedDisabled = !!(disabled || (preventDoubleSubmit && busy));
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex",
|
|
reverseOrderOnMobile ? "flex-col-reverse sm:flex-row" : "flex-row",
|
|
alignToJustify[align],
|
|
gap,
|
|
className
|
|
)}
|
|
>
|
|
{submit && <SubmitFormButton {...submit} />}
|
|
{showCancel && <CancelFormButton {...cancel} />}
|
|
|
|
{/* Menú de acciones adicionales */}
|
|
{hasSecondaryActions && (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant='ghost' size='sm' disabled={computedDisabled} className='px-2'>
|
|
<MoreHorizontalIcon className='h-4 w-4' />
|
|
<span className='sr-only'>Más acciones</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align='end' className='w-48'>
|
|
{onReset && (
|
|
<DropdownMenuItem
|
|
onClick={onReset}
|
|
disabled={computedDisabled}
|
|
className='text-muted-foreground'
|
|
>
|
|
<RotateCcwIcon className='mr-2 h-4 w-4' />
|
|
Deshacer cambios
|
|
</DropdownMenuItem>
|
|
)}
|
|
{onPreview && (
|
|
<DropdownMenuItem onClick={onPreview} className='text-muted-foreground'>
|
|
<EyeIcon className='mr-2 h-4 w-4' />
|
|
Vista previa
|
|
</DropdownMenuItem>
|
|
)}
|
|
{onDuplicate && (
|
|
<DropdownMenuItem onClick={onDuplicate} className='text-muted-foreground'>
|
|
<CopyIcon className='mr-2 h-4 w-4' />
|
|
Duplicar
|
|
</DropdownMenuItem>
|
|
)}
|
|
{onBack && (
|
|
<DropdownMenuItem onClick={onBack} className='text-muted-foreground'>
|
|
<ArrowLeftIcon className='mr-2 h-4 w-4' />
|
|
Volver
|
|
</DropdownMenuItem>
|
|
)}
|
|
{onDelete && (
|
|
<>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
onClick={onDelete}
|
|
className='text-destructive focus:text-destructive'
|
|
>
|
|
<Trash2Icon className='mr-2 h-4 w-4' />
|
|
Eliminar
|
|
</DropdownMenuItem>
|
|
</>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|