Files
kandev/apps/web/components/task/change-workflow-dialog.tsx
2026-09-25 10:30:33 +01:00

181 lines
5.4 KiB
TypeScript

"use client";
import { useId, type RefObject } from "react";
import { IconX } from "@tabler/icons-react";
import { Button } from "@kandev/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@kandev/ui/dialog";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
} from "@kandev/ui/drawer";
import {
ChangeWorkflowActionFooter,
ChangeWorkflowAgentSection,
ChangeWorkflowCurrentTask,
ChangeWorkflowEntryPreview,
ChangeWorkflowSelectors,
ChangeWorkflowSubmitError,
} from "@/components/task/change-workflow-form-sections";
import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint";
import { createFocusReturnHandler } from "@/lib/dialog-focus-return";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { useChangeWorkflow } from "@/hooks/domains/kanban/use-change-workflow";
type ChangeWorkflowDialogProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
taskId: string | null;
workspaceId: string | null;
focusReturnRef?: RefObject<HTMLElement | null>;
onSuccess?: () => void;
};
function ChangeWorkflowForm({
taskId,
workspaceId,
open,
onOpenChange,
onSuccess,
isTouchSurface,
}: {
taskId: string | null;
workspaceId: string | null;
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: () => void;
isTouchSurface: boolean;
}) {
const workflowSelectId = useId();
const stepSelectId = useId();
const state = useChangeWorkflow({ open, taskId, workspaceId, onOpenChange, onSuccess });
const sectionClass = cn("grid gap-3", isTouchSurface && "gap-4");
return (
<form
className="flex min-h-0 flex-1 flex-col"
data-testid="change-workflow-form"
onSubmit={(event) => {
event.preventDefault();
void state.submit();
}}
>
<div
data-testid="change-workflow-scroll"
className={cn(
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 py-4",
isTouchSurface && "px-4 py-3",
)}
>
<div className={sectionClass}>
<ChangeWorkflowCurrentTask state={state} />
<ChangeWorkflowSelectors
state={state}
isTouchSurface={isTouchSurface}
workflowSelectId={workflowSelectId}
stepSelectId={stepSelectId}
/>
<ChangeWorkflowAgentSection state={state} isTouchSurface={isTouchSurface} />
<ChangeWorkflowEntryPreview state={state} isTouchSurface={isTouchSurface} />
<ChangeWorkflowSubmitError state={state} />
</div>
</div>
<ChangeWorkflowActionFooter
state={state}
isTouchSurface={isTouchSurface}
onOpenChange={onOpenChange}
/>
</form>
);
}
export function ChangeWorkflowDialog({
open,
onOpenChange,
taskId,
workspaceId,
focusReturnRef,
onSuccess,
}: ChangeWorkflowDialogProps) {
const { t } = useTranslation();
const { isFinePointer, isMobile } = useResponsiveBreakpoint();
const isTouchSurface = isMobile || !isFinePointer;
const closeFocus = createFocusReturnHandler(focusReturnRef);
const surfaceContent = (
<>
<div className="flex shrink-0 items-start justify-between gap-3 border-b border-border/70 px-5 py-4">
<div className="min-w-0">
{isMobile ? (
<DrawerHeader className="p-0 text-left">
<DrawerTitle className="text-base">{t("task:changeWorkflowTitle")}</DrawerTitle>
<DrawerDescription>{t("task:changeWorkflowDescription")}</DrawerDescription>
</DrawerHeader>
) : (
<DialogHeader className="text-left">
<DialogTitle className="text-base">{t("task:changeWorkflowTitle")}</DialogTitle>
<DialogDescription>{t("task:changeWorkflowDescription")}</DialogDescription>
</DialogHeader>
)}
</div>
<Button
type="button"
variant="ghost"
size="icon"
className={cn("shrink-0", isTouchSurface && "size-11")}
aria-label={t("common:close")}
onClick={() => onOpenChange(false)}
data-testid="change-workflow-close"
>
<IconX className="size-4" aria-hidden="true" />
</Button>
</div>
<ChangeWorkflowForm
taskId={taskId}
workspaceId={workspaceId}
open={open}
onOpenChange={onOpenChange}
onSuccess={onSuccess}
isTouchSurface={isTouchSurface}
/>
</>
);
if (isMobile) {
return (
<Drawer open={open} onOpenChange={onOpenChange}>
<DrawerContent
className="!top-0 !bottom-auto !mt-0 !h-[calc(100dvh-16px-env(safe-area-inset-bottom,0px))] !max-h-[calc(100dvh-16px-env(safe-area-inset-bottom,0px))] min-h-0 overflow-hidden p-0 outline-none"
data-testid="change-workflow-drawer"
onCloseAutoFocus={closeFocus}
>
<div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl bg-background">
{surfaceContent}
</div>
</DrawerContent>
</Drawer>
);
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
className="flex max-h-[min(90dvh,900px)] min-h-0 flex-col gap-0 overflow-hidden p-0 sm:max-w-2xl"
data-testid="change-workflow-dialog"
showCloseButton={false}
onCloseAutoFocus={closeFocus}
>
{surfaceContent}
</DialogContent>
</Dialog>
);
}