Compare commits

...

1 Commits

Author SHA1 Message Date
Jamison Lahman
c1fc691b26 chore(fe): allow Modal.Body to defer scrolling to children 2026-03-13 14:57:09 -07:00
3 changed files with 36 additions and 13 deletions

View File

@@ -61,6 +61,7 @@ interface ModalContextValue {
hasAttemptedClose: boolean;
setHasAttemptedClose: (value: boolean) => void;
height: keyof typeof heightClasses;
deferScrollingToChildren: boolean;
hasDescription: boolean;
setHasDescription: (value: boolean) => void;
}
@@ -84,9 +85,9 @@ const widthClasses = {
const heightClasses = {
fit: "h-fit",
sm: "max-h-[30rem] overflow-y-auto",
lg: "max-h-[calc(100dvh-4rem)] overflow-y-auto",
full: "h-[80dvh] overflow-y-auto",
sm: "max-h-[30rem]",
lg: "max-h-[calc(100dvh-4rem)]",
full: "h-[80dvh]",
};
/**
@@ -120,6 +121,8 @@ export interface ModalContentProps
> {
width?: keyof typeof widthClasses;
height?: keyof typeof heightClasses;
/** Lets nested children like ScrollIndicatorDiv own vertical scrolling. */
deferScrollingToChildren?: boolean;
preventAccidentalClose?: boolean;
skipOverlay?: boolean;
background?: "default" | "gray";
@@ -136,6 +139,7 @@ const ModalContent = React.forwardRef<
children,
width = "md",
height = "fit",
deferScrollingToChildren = false,
preventAccidentalClose = true,
skipOverlay = false,
background = "default",
@@ -290,6 +294,11 @@ const ModalContent = React.forwardRef<
!hasContainerCenter && "left-1/2 top-1/2"
);
const contentOverflowClasses =
deferScrollingToChildren || height === "fit"
? "overflow-hidden"
: "overflow-y-auto";
const dialogEventHandlers = {
onOpenAutoFocus: (e: Event) => {
resetState();
@@ -320,6 +329,7 @@ const ModalContent = React.forwardRef<
hasAttemptedClose,
setHasAttemptedClose,
height,
deferScrollingToChildren,
hasDescription,
setHasDescription,
}}
@@ -344,7 +354,13 @@ const ModalContent = React.forwardRef<
widthClasses[width]
)}
>
<div className={cn(cardClasses, "w-full min-h-0")}>
<div
className={cn(
cardClasses,
contentOverflowClasses,
"w-full min-h-0"
)}
>
{children}
</div>
<div className="w-full flex-shrink-0">{bottomSlot}</div>
@@ -357,7 +373,6 @@ const ModalContent = React.forwardRef<
style={containerStyle}
className={cn(
positionClasses,
"overflow-hidden",
"z-modal",
background === "gray"
? "bg-background-tint-01"
@@ -367,7 +382,8 @@ const ModalContent = React.forwardRef<
"max-w-[calc(100dvw-2rem)] max-h-[calc(100dvh-2rem)]",
animationClasses,
widthClasses[width],
heightClasses[height]
heightClasses[height],
contentOverflowClasses
)}
{...dialogEventHandlers}
>
@@ -510,16 +526,20 @@ interface ModalBodyProps extends WithoutStyles<SectionProps> {
}
const ModalBody = React.forwardRef<HTMLDivElement, ModalBodyProps>(
({ twoTone = true, children, ...props }, ref) => {
const { deferScrollingToChildren } = useModalContext();
return (
<div
ref={ref}
className={cn(
twoTone && "bg-background-tint-01",
"flex-auto min-h-0 overflow-y-auto w-full"
"flex-auto min-h-0 w-full",
deferScrollingToChildren ? "overflow-hidden" : "overflow-y-auto"
)}
>
<Section
height="auto"
height={deferScrollingToChildren ? "full" : "auto"}
justifyContent="start"
padding={1}
gap={1}
alignItems="start"

View File

@@ -119,6 +119,11 @@ export default function ScrollIndicatorDiv({
// DOM after initial render, which changes scrollHeight without firing
// resize or scroll events on the container).
const mutationObserver = new MutationObserver(handleScroll);
mutationObserver.observe(container, {
childList: true,
subtree: true,
characterData: true,
});
return () => {
container.removeEventListener("scroll", handleScroll);

View File

@@ -161,6 +161,7 @@ export default function PreviewModal({
<Modal.Content
width={variant.width}
height={variant.height}
deferScrollingToChildren
preventAccidentalClose={false}
onOpenAutoFocus={(e) => e.preventDefault()}
>
@@ -170,10 +171,7 @@ export default function PreviewModal({
onClose={onClose}
/>
{/* Body — uses flex-1/min-h-0/overflow-hidden (not Modal.Body)
so that child ScrollIndicatorDivs become the actual scroll
container instead of the body stealing it via overflow-y-auto. */}
<div className="flex flex-col flex-1 min-h-0 overflow-hidden w-full bg-background-tint-01">
<Modal.Body padding={0} gap={0}>
{isLoading ? (
<Section>
<SimpleLoader className="h-8 w-8" />
@@ -187,7 +185,7 @@ export default function PreviewModal({
) : (
variant.renderContent(ctx)
)}
</div>
</Modal.Body>
{/* Floating footer */}
{!isLoading && !loadError && (