[WEB-4684] chore: dialog component enhancements (#7606)

* chore: z-index tokens added

* chore: dialog component code refactor

* chore: dialog component improvements

* fix: lint error

* fix: lint error

* fix: format error
This commit is contained in:
Anmol Singh Bhatia 2025-08-20 22:17:26 +05:30 committed by GitHub
parent b8a88fe89c
commit 03479cf6b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 121 additions and 81 deletions

View File

@ -1,17 +0,0 @@
export enum EDialogPosition {
TOP = "flex items-center justify-center text-center mx-4 my-10 md:my-20",
CENTER = "flex items-end sm:items-center justify-center p-4 min-h-full",
}
export enum EDialogWidth {
SM = "sm:max-w-sm",
MD = "sm:max-w-md",
LG = "sm:max-w-lg",
XL = "sm:max-w-xl",
XXL = "sm:max-w-2xl",
XXXL = "sm:max-w-3xl",
XXXXL = "sm:max-w-4xl",
VXL = "sm:max-w-5xl",
VIXL = "sm:max-w-6xl",
VIIXL = "sm:max-w-7xl",
}

View File

@ -3,75 +3,116 @@
import * as React from "react";
import { Dialog as BaseDialog } from "@base-ui-components/react";
import { cn } from "@plane/utils";
import { EDialogWidth } from "./constants";
function DialogPortal({ ...props }: React.ComponentProps<typeof BaseDialog.Portal>) {
return <BaseDialog.Portal data-slot="dialog-portal" {...props} />;
// enums
export enum EDialogWidth {
SM = "sm:max-w-sm",
MD = "sm:max-w-md",
LG = "sm:max-w-lg",
XL = "sm:max-w-xl",
XXL = "sm:max-w-2xl",
XXXL = "sm:max-w-3xl",
XXXXL = "sm:max-w-4xl",
VXL = "sm:max-w-5xl",
VIXL = "sm:max-w-6xl",
VIIXL = "sm:max-w-7xl",
}
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof BaseDialog.Backdrop>) {
return (
<BaseDialog.Backdrop
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 z-30 bg-custom-backdrop transition-all duration-200 [&[data-ending-style]]:opacity-0 [&[data-starting-style]]:opacity-0",
className
)}
{...props}
/>
);
// Types
export type DialogPosition = "center" | "top";
export interface DialogProps extends React.ComponentProps<typeof BaseDialog.Root> {
children: React.ReactNode;
}
function Dialog({ ...props }: React.ComponentProps<typeof BaseDialog.Root>) {
return <BaseDialog.Root data-slot="dialog" {...props} />;
export interface DialogPanelProps extends React.ComponentProps<typeof BaseDialog.Popup> {
width?: EDialogWidth;
position?: DialogPosition;
children: React.ReactNode;
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof BaseDialog.Trigger>) {
return <BaseDialog.Trigger data-slot="dialog-trigger" {...props} />;
export interface DialogTitleProps extends React.ComponentProps<typeof BaseDialog.Title> {
children: React.ReactNode;
}
function DialogPanel({
className,
width = EDialogWidth.XXL,
children,
...props
}: React.ComponentProps<typeof BaseDialog.Popup> & { width?: EDialogWidth }) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<BaseDialog.Popup
data-slot="dialog-content"
className={cn(
"fixed flex justify-center top-0 left-0 w-full z-30 px-4 sm:py-20 overflow-y-auto overflow-hidden outline-none"
)}
{...props}
>
<div
className={cn(
"rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all w-full",
width,
className
)}
// Constants
const OVERLAY_CLASSNAME = cn("fixed inset-0 z-backdrop bg-custom-backdrop");
const BASE_CLASSNAME = "relative text-left bg-custom-background-100 rounded-lg shadow-md w-full z-modal";
// Utility functions
const getPositionClassNames = React.useCallback(
(position: DialogPosition) =>
cn("isolate fixed z-modal", {
"top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2": position === "center",
"top-8 left-1/2 -translate-x-1/2": position === "top",
}),
[]
);
const DialogPortal = React.memo<React.ComponentProps<typeof BaseDialog.Portal>>(({ children, ...props }) => (
<BaseDialog.Portal data-slot="dialog-portal" {...props}>
{children}
</BaseDialog.Portal>
));
DialogPortal.displayName = "DialogPortal";
const DialogOverlay = React.memo<React.ComponentProps<typeof BaseDialog.Backdrop>>(({ className, ...props }) => (
<BaseDialog.Backdrop data-slot="dialog-overlay" className={cn(OVERLAY_CLASSNAME, className)} {...props} />
));
DialogOverlay.displayName = "DialogOverlay";
const DialogComponent = React.memo<DialogProps>(({ children, ...props }) => (
<BaseDialog.Root data-slot="dialog" {...props}>
{children}
</BaseDialog.Root>
));
DialogComponent.displayName = "Dialog";
const DialogTrigger = React.memo<React.ComponentProps<typeof BaseDialog.Trigger>>(({ children, ...props }) => (
<BaseDialog.Trigger data-slot="dialog-trigger" {...props}>
{children}
</BaseDialog.Trigger>
));
DialogTrigger.displayName = "DialogTrigger";
const DialogPanel = React.forwardRef<React.ElementRef<typeof BaseDialog.Popup>, DialogPanelProps>(
({ className, width = EDialogWidth.XXL, children, position = "center", ...props }, ref) => {
const positionClassNames = React.useMemo(() => getPositionClassNames(position), [position]);
return (
<DialogPortal>
<DialogOverlay />
<BaseDialog.Popup
ref={ref}
data-slot="dialog-content"
className={cn(BASE_CLASSNAME, positionClassNames, width, className)}
role="dialog"
aria-modal="true"
{...props}
>
{children}
</div>
</BaseDialog.Popup>
</DialogPortal>
);
}
</BaseDialog.Popup>
</DialogPortal>
);
}
);
DialogPanel.displayName = "DialogPanel";
function DialogTitle({ className, ...props }: React.ComponentProps<typeof BaseDialog.Title>) {
return (
<BaseDialog.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
);
}
// compound components
Dialog.Trigger = DialogTrigger;
Dialog.Panel = DialogPanel;
Dialog.Title = DialogTitle;
const DialogTitle = React.memo<DialogTitleProps>(({ className, children, ...props }) => (
<BaseDialog.Title data-slot="dialog-title" className={cn("text-lg leading-none font-semibold", className)} {...props}>
{children}
</BaseDialog.Title>
));
export { Dialog, DialogTitle, DialogTrigger, DialogPanel };
DialogTitle.displayName = "DialogTitle";
// Create the compound Dialog component with proper typing
const Dialog = Object.assign(DialogComponent, {
Panel: DialogPanel,
Title: DialogTitle,
}) as typeof DialogComponent & {
Panel: typeof DialogPanel;
Title: typeof DialogTitle;
};
export { Dialog, DialogTitle, DialogPanel };

View File

@ -1 +1 @@
export * from "./root";
export * from "./root";

View File

@ -97,9 +97,11 @@ const PopoverPortal = React.memo<React.ComponentProps<typeof BasePopover.Portal>
return <BasePopover.Portal data-slot="popover-portal" {...props} />;
});
const PopoverPositioner = React.memo<React.ComponentProps<typeof BasePopover.Positioner>>(function PopoverPositioner(props) {
return <BasePopover.Positioner data-slot="popover-positioner" {...props} />;
});
const PopoverPositioner = React.memo<React.ComponentProps<typeof BasePopover.Positioner>>(
function PopoverPositioner(props) {
return <BasePopover.Positioner data-slot="popover-positioner" {...props} />;
}
);
// compound components
const Popover = Object.assign(
@ -119,4 +121,4 @@ PopoverPortal.displayName = "PopoverPortal";
PopoverTrigger.displayName = "PopoverTrigger";
PopoverPositioner.displayName = "PopoverPositioner";
export { Popover};
export { Popover };

View File

@ -442,6 +442,20 @@ module.exports = {
fontFamily: {
custom: ["Inter", "sans-serif"],
},
zIndex: {
base: 0 /* default content */,
header: 10 /* sticky headers, navbars */,
sidebar: 20 /* sidebars, drawers */,
dropdown: 30 /* dropdowns, select menus */,
popover: 40 /* popovers, hovercards */,
tooltip: 50 /* tooltips, hints */,
sticky: 60 /* sticky UI */,
backdrop: 90 /* backdrop / overlay */,
modal: 100 /* dialogs, modals */,
toast: 110 /* toast, alerts */,
loader: 120 /* blocking loader/spinner */,
max: 9999 /* emergency override (rare use) */,
},
},
},
plugins: [