ℹ️ feat: Dismissible Tooltips in Modals (#10851)

This commit is contained in:
Dustin Healy 2025-12-09 17:13:21 -08:00 committed by Danny Avila
parent d08f7c2c8a
commit 2ed2b87c30
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956

View file

@ -66,28 +66,71 @@ type DialogContentProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
DialogContentProps DialogContentProps
>(({ className, overlayClassName, showCloseButton = true, children, ...props }, ref) => ( >(
<DialogPortal> (
<DialogOverlay className={overlayClassName} /> {
<DialogPrimitive.Content className,
ref={ref} overlayClassName,
className={cn( showCloseButton = true,
'max-w-11/12 fixed left-[50%] top-[50%] z-50 grid max-h-[90vh] w-full translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-2xl bg-background p-6 text-text-primary shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]', children,
className, onEscapeKeyDown: propsOnEscapeKeyDown,
)} ...props
{...props} },
> ref,
{children} ) => {
{showCloseButton && ( /* Handle Escape key to prevent closing dialog if a tooltip is open
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-ring-primary ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> (this is a workaround in order to achieve WCAG compliance which requires
<X className="h-6 w-6" aria-hidden="true" /> that our tooltips be dismissable with Escape key) */
{/* eslint-disable-next-line i18next/no-literal-string */} const handleEscapeKeyDown = React.useCallback(
<span className="sr-only">Close</span> (event: KeyboardEvent) => {
</DialogPrimitive.Close> const tooltips = document.querySelectorAll('.tooltip');
)}
</DialogPrimitive.Content> for (const tooltip of Array.from(tooltips)) {
</DialogPortal> const computedStyle = window.getComputedStyle(tooltip);
)); const opacity = parseFloat(computedStyle.opacity);
if (
tooltip.parentElement &&
computedStyle.display !== 'none' &&
computedStyle.visibility !== 'hidden' &&
opacity > 0
) {
event.preventDefault();
return;
}
}
// Call the original handler if it exists
propsOnEscapeKeyDown?.(event);
},
[propsOnEscapeKeyDown],
);
return (
<DialogPortal>
<DialogOverlay className={overlayClassName} />
<DialogPrimitive.Content
ref={ref}
onEscapeKeyDown={handleEscapeKeyDown}
className={cn(
'max-w-11/12 fixed left-[50%] top-[50%] z-50 grid max-h-[90vh] w-full translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-2xl bg-background p-6 text-text-primary shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]',
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-ring-primary ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-6 w-6" aria-hidden="true" />
{/* eslint-disable-next-line i18next/no-literal-string */}
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
},
);
DialogContent.displayName = DialogPrimitive.Content.displayName; DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (