[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 * as React from "react";
import { Dialog as BaseDialog } from "@base-ui-components/react"; import { Dialog as BaseDialog } from "@base-ui-components/react";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
import { EDialogWidth } from "./constants";
function DialogPortal({ ...props }: React.ComponentProps<typeof BaseDialog.Portal>) { // enums
return <BaseDialog.Portal data-slot="dialog-portal" {...props} />;
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>) { // Types
return ( export type DialogPosition = "center" | "top";
<BaseDialog.Backdrop
data-slot="dialog-overlay" export interface DialogProps extends React.ComponentProps<typeof BaseDialog.Root> {
className={cn( children: React.ReactNode;
"fixed inset-0 z-30 bg-custom-backdrop transition-all duration-200 [&[data-ending-style]]:opacity-0 [&[data-starting-style]]:opacity-0",
className
)}
{...props}
/>
);
} }
function Dialog({ ...props }: React.ComponentProps<typeof BaseDialog.Root>) { export interface DialogPanelProps extends React.ComponentProps<typeof BaseDialog.Popup> {
return <BaseDialog.Root data-slot="dialog" {...props} />; width?: EDialogWidth;
position?: DialogPosition;
children: React.ReactNode;
} }
function DialogTrigger({ ...props }: React.ComponentProps<typeof BaseDialog.Trigger>) { export interface DialogTitleProps extends React.ComponentProps<typeof BaseDialog.Title> {
return <BaseDialog.Trigger data-slot="dialog-trigger" {...props} />; children: React.ReactNode;
} }
function DialogPanel({ // Constants
className, const OVERLAY_CLASSNAME = cn("fixed inset-0 z-backdrop bg-custom-backdrop");
width = EDialogWidth.XXL, const BASE_CLASSNAME = "relative text-left bg-custom-background-100 rounded-lg shadow-md w-full z-modal";
children,
...props // Utility functions
}: React.ComponentProps<typeof BaseDialog.Popup> & { width?: EDialogWidth }) { const getPositionClassNames = React.useCallback(
return ( (position: DialogPosition) =>
<DialogPortal data-slot="dialog-portal"> cn("isolate fixed z-modal", {
<DialogOverlay /> "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2": position === "center",
<BaseDialog.Popup "top-8 left-1/2 -translate-x-1/2": position === "top",
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} const DialogPortal = React.memo<React.ComponentProps<typeof BaseDialog.Portal>>(({ children, ...props }) => (
> <BaseDialog.Portal data-slot="dialog-portal" {...props}>
<div {children}
className={cn( </BaseDialog.Portal>
"rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all w-full", ));
width, DialogPortal.displayName = "DialogPortal";
className
)} 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} {children}
</div> </BaseDialog.Popup>
</BaseDialog.Popup> </DialogPortal>
</DialogPortal> );
); }
} );
DialogPanel.displayName = "DialogPanel";
function DialogTitle({ className, ...props }: React.ComponentProps<typeof BaseDialog.Title>) { const DialogTitle = React.memo<DialogTitleProps>(({ className, children, ...props }) => (
return ( <BaseDialog.Title data-slot="dialog-title" className={cn("text-lg leading-none font-semibold", className)} {...props}>
<BaseDialog.Title {children}
data-slot="dialog-title" </BaseDialog.Title>
className={cn("text-lg leading-none font-semibold", className)} ));
{...props}
/>
);
}
// compound components
Dialog.Trigger = DialogTrigger;
Dialog.Panel = DialogPanel;
Dialog.Title = DialogTitle;
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

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

View File

@ -442,6 +442,20 @@ module.exports = {
fontFamily: { fontFamily: {
custom: ["Inter", "sans-serif"], 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: [ plugins: [