"use client";
import { AnimatePresence, type HTMLMotionProps, motion } from "motion/react";
import { Dialog as DialogPrimitive } from "radix-ui";
import type * as React from "react";
import { useTranslation } from "react-i18next";
import { RxCross2 } from "react-icons/rx";
import { useControlledState } from "@/hooks/use-controlled-state";
import { getStrictContext } from "@/lib/get-strict-context";
import { cn } from "@/lib/utils";
import { WindowDragArea } from "../window-drag-area";
type DialogContextType = {
isOpen: boolean;
setIsOpen: DialogProps["onOpenChange"];
subPage: boolean;
container: HTMLElement | null | undefined;
};
const [DialogProvider, useDialog] =
getStrictContext("DialogContext");
type DialogProps = React.ComponentProps & {
/** Render in a portal container as an in-flow sub-page instead of a centered modal. */
subPage?: boolean;
/** Portal container target. Required when subPage=true; ignored otherwise. */
container?: HTMLElement | null;
};
function Dialog({ subPage, container, children, ...props }: DialogProps) {
const [isOpen, setIsOpen] = useControlledState({
value: props?.open,
defaultValue: props?.defaultOpen,
onChange: props?.onOpenChange,
});
return (
{/* In sub-page mode the Dialog isn't a modal — it's an in-flow page.
Forcing `modal={false}` prevents Radix from locking pointer-events
and aria-hiding everything outside the dialog. Children are passed
explicitly (not via spread) so React doesn't have to guess where
the JSX subtree should mount. */}
{children}
);
}
type DialogTriggerProps = React.ComponentProps;
function DialogTrigger(props: DialogTriggerProps) {
return ;
}
type DialogPortalProps = Omit<
React.ComponentProps,
"forceMount"
>;
function DialogPortal(props: DialogPortalProps) {
const { isOpen, container } = useDialog();
return (
{isOpen && (
)}
);
}
type DialogOverlayProps = Omit<
React.ComponentProps,
"forceMount" | "asChild"
> &
HTMLMotionProps<"div">;
function DialogOverlay({
className,
transition = { duration: 0.2, ease: "easeInOut" },
...props
}: DialogOverlayProps) {
return (
{/* Keep the OS title-bar zone draggable while a modal is open — the
overlay otherwise covers the native drag region. `data-window-drag-area`
stops Radix from treating a drag here as an outside-click dismiss. */}
);
}
type DialogContentProps = Omit<
React.ComponentProps,
"forceMount" | "asChild"
> &
HTMLMotionProps<"div"> & {
/**
* Suppress the built-in top-right close X. Use when the dialog renders
* its own header bar with a custom close control to avoid two X buttons
* stacking near the corner.
*/
hideClose?: boolean;
/**
* When false, the user cannot dismiss the dialog — Escape and outside
* clicks are ignored and the close X is hidden. Use for steps the user
* must complete to progress (e.g. required onboarding, a blocking
* download). The dialog can still be closed programmatically via `open`.
*/
dismissible?: boolean;
};
function SubPageContent({
children,
}: {
className?: string;
children?: React.ReactNode;
}) {
const { isOpen } = useDialog();
if (!isOpen) return null;
// Inline styles deliberately override any className the caller passed
// for the modal mode (max-w-*, max-h-*, my-*). tailwind-merge inside the
// shared dialog wrappers turned out to be unreliable when both classnames
// and !important variants competed — inline styles guarantee the layout.
return (
{children}
);
}
function DialogContent({
className,
children,
onOpenAutoFocus,
onCloseAutoFocus,
onEscapeKeyDown,
onPointerDownOutside,
onInteractOutside,
transition,
hideClose,
dismissible = true,
...props
}: DialogContentProps) {
const { t } = useTranslation();
const { subPage } = useDialog();
if (subPage) {
return {children};
}
return (
{
if (!dismissible) event.preventDefault();
onEscapeKeyDown?.(event);
}}
onPointerDownOutside={onPointerDownOutside}
onInteractOutside={(event) => {
if (!dismissible) {
event.preventDefault();
return;
}
const target = event.target as HTMLElement | null;
if (target?.closest('[data-window-drag-area="true"]')) {
event.preventDefault();
}
onInteractOutside?.(event);
}}
>
{children}
{!hideClose && dismissible && (
{t("common.buttons.close")}
)}
);
}
type DialogCloseProps = React.ComponentProps;
function DialogClose(props: DialogCloseProps) {
return ;
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
);
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
);
}
function DialogTitle({
className,
...props
}: React.ComponentProps) {
return (
);
}
function DialogDescription({
className,
...props
}: React.ComponentProps) {
return (
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};