ERP-node/frontend/components/ui/alert-dialog.tsx

154 lines
5.7 KiB
TypeScript
Raw Normal View History

2025-08-21 09:41:46 +09:00
"use client";
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { useModalPortal } from "@/lib/modalPortalRef";
import { useTabId } from "@/contexts/TabIdContext";
import { useTabStore } from "@/stores/tabStore";
2025-08-21 09:41:46 +09:00
// AlertDialog: 비활성 탭이면 자동으로 open={false} 처리
const AlertDialog: React.FC<React.ComponentProps<typeof AlertDialogPrimitive.Root>> = ({
open,
...props
}) => {
const tabId = useTabId();
const activeTabId = useTabStore((s) => s[s.mode].activeTabId);
const isTabActive = !tabId || tabId === activeTabId;
const effectiveOpen = open != null ? open && isTabActive : undefined;
return <AlertDialogPrimitive.Root {...props} open={effectiveOpen} />;
};
AlertDialog.displayName = "AlertDialog";
2025-08-21 09:41:46 +09:00
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-1050 bg-black/80",
2025-08-21 09:41:46 +09:00
className,
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
interface ScopedAlertDialogContentProps
extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> {
/** 포탈 대상 컨테이너. 명시적으로 전달하면 해당 값 사용, 미전달 시 탭 시스템 자동 감지 */
container?: HTMLElement | null;
/** 탭 비활성 시 포탈 내용 숨김 */
hidden?: boolean;
}
2025-08-21 09:41:46 +09:00
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
ScopedAlertDialogContentProps
>(({ className, container: explicitContainer, hidden: hiddenProp, style, ...props }, ref) => {
const autoContainer = useModalPortal();
const container = explicitContainer !== undefined ? explicitContainer : autoContainer;
const scoped = !!container;
const adjustedStyle = scoped && style
? { ...style, maxHeight: undefined, maxWidth: undefined }
: style;
return (
<AlertDialogPortal container={container ?? undefined}>
<div
className={scoped ? "absolute inset-0 z-1050 flex items-center justify-center overflow-hidden p-4" : undefined}
style={hiddenProp ? { display: "none" } : undefined}
>
{scoped ? (
<div className="absolute inset-0 bg-black/80" />
) : (
<AlertDialogPrimitive.Overlay className="fixed inset-0 z-1050 bg-black/80" />
)}
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
scoped
? "bg-background relative z-1 grid w-full max-w-lg max-h-full gap-4 border p-6 shadow-lg sm:rounded-lg"
: "bg-background fixed top-[50%] left-[50%] z-1100 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg",
className,
scoped && "max-h-full",
)}
style={adjustedStyle}
{...props}
/>
</div>
</AlertDialogPortal>
);
});
2025-08-21 09:41:46 +09:00
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
);
AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description ref={ref} className={cn("text-muted-foreground text-sm", className)} {...props} />
));
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};