From c7a3875441d7d8f4a0f2dccc6b99f4a94dc56c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rick?= Date: Wed, 14 Jan 2026 12:38:05 -0300 Subject: [PATCH 1/4] feat(Overlay): add keepPositionFixed prop and update classNames logic --- .../src/components/Overlay/Overlay.tsx | 6 +++-- .../src/components/Overlay/Overlay.types.ts | 2 ++ .../Overlay/tests/Overlay.stories.tsx | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/reshaped/src/components/Overlay/Overlay.tsx b/packages/reshaped/src/components/Overlay/Overlay.tsx index 740fb324..0395bee6 100644 --- a/packages/reshaped/src/components/Overlay/Overlay.tsx +++ b/packages/reshaped/src/components/Overlay/Overlay.tsx @@ -1,6 +1,7 @@ "use client"; import { TrapFocus } from "@reshaped/utilities"; +import { classNames } from "@reshaped/utilities"; import { type FocusableElement } from "@reshaped/utilities/internal"; import React from "react"; @@ -12,7 +13,6 @@ import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; import useScrollLock from "hooks/useScrollLock"; import useToggle from "hooks/useToggle"; import { onNextFrame } from "utilities/animation"; -import { classNames } from "@reshaped/utilities"; import s from "./Overlay.module.css"; @@ -31,6 +31,7 @@ const Overlay: React.FC = (props) => { onAfterOpen, disableCloseOnClick, containerRef, + keepPositionFixed, className, attributes, } = props; @@ -57,6 +58,7 @@ const Overlay: React.FC = (props) => { const { active: rendered, activate: render, deactivate: remove } = useToggle(active || false); const { active: visible, activate: show, deactivate: hide } = useToggle(active || false); const isDismissible = useIsDismissible({ active, contentRef, hasTrigger: false }); + const shouldBeContained = containerRef && !keepPositionFixed; const rootClassNames = classNames( s.root, @@ -64,7 +66,7 @@ const Overlay: React.FC = (props) => { isTransparent && s["--click-through"], blurred && s["--blurred"], animated && s["--animated"], - containerRef && s["--contained"], + shouldBeContained && s["--contained"], overflow === "auto" && s["--overflow-auto"], className ); diff --git a/packages/reshaped/src/components/Overlay/Overlay.types.ts b/packages/reshaped/src/components/Overlay/Overlay.types.ts index 823a4e43..523d9bdb 100644 --- a/packages/reshaped/src/components/Overlay/Overlay.types.ts +++ b/packages/reshaped/src/components/Overlay/Overlay.types.ts @@ -26,6 +26,8 @@ export type Props = { disableCloseOnClick?: boolean; /** Element to render the component in */ containerRef?: React.RefObject; + /** Keep the fixed position of the component even when it is rendered inside a container */ + keepPositionFixed?: boolean; /** Additional classname for the root element */ className?: G.ClassName; /** Additional attributes for the root element */ diff --git a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx index b48e146a..ec867621 100644 --- a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx +++ b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx @@ -264,6 +264,29 @@ export const containerRef: StoryObj = { }, }; +export const containerRefWithKeepFixedPosition: StoryObj = { + name: "containerRef with keepFixedPosition", + render: () => { + const containerRef = React.useRef(null); + + return ( + <> +
+ + Content + + + ); + }, + play: ({ canvasElement }) => { + const canvas = within(canvasElement.ownerDocument.body); + const container = canvas.getByTestId("test-id"); + const overlay = canvas.getByText("Content"); + + expect(container).toContainElement(overlay); + }, +}; + export const className: StoryObj = { name: "className, attributes", render: () => ( From a7fa6cccf74ba6fd95fbbc5a51abba9be3b992e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rick?= Date: Wed, 14 Jan 2026 12:52:45 -0300 Subject: [PATCH 2/4] feat(Modal): integrate keepPositionFixed prop and update related logic --- .../reshaped/src/components/Modal/Modal.tsx | 5 +++- .../src/components/Modal/Modal.types.ts | 5 +++- .../components/Modal/tests/Modal.stories.tsx | 23 +++++++++++++++++++ .../Overlay/tests/Overlay.stories.tsx | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/reshaped/src/components/Modal/Modal.tsx b/packages/reshaped/src/components/Modal/Modal.tsx index c1ed664a..b96448a0 100644 --- a/packages/reshaped/src/components/Modal/Modal.tsx +++ b/packages/reshaped/src/components/Modal/Modal.tsx @@ -80,6 +80,7 @@ const Modal: React.FC = (props) => { disableSwipeGesture, disableCloseOnOutsideClick, containerRef, + keepPositionFixed, overlayClassName, className, attributes, @@ -99,6 +100,7 @@ const Modal: React.FC = (props) => { const [dragDistance, setDragDistance] = React.useState(0); const [hideProgress, setHideProgress] = React.useState(0); const mixinStyles = resolveMixin({ padding }); + const shouldBeContained = containerRef && !keepPositionFixed; const value = React.useMemo( () => ({ @@ -247,6 +249,7 @@ const Modal: React.FC = (props) => { blurred={blurredOverlay} overflow={clientPosition === "center" ? "auto" : "hidden"} className={overlayClassName} + keepPositionFixed={keepPositionFixed} containerRef={containerRef} attributes={{ onTouchStart: handleDragStart, @@ -259,7 +262,7 @@ const Modal: React.FC = (props) => { active && s["--active"], dragging && s["--dragging"], overflow && s[`--overflow-${overflow}`], - containerRef && s["--contained"], + shouldBeContained && s["--contained"], responsiveClassNames(s, "--position", position), mixinStyles.classNames ); diff --git a/packages/reshaped/src/components/Modal/Modal.types.ts b/packages/reshaped/src/components/Modal/Modal.types.ts index c90ba397..3ea10f58 100644 --- a/packages/reshaped/src/components/Modal/Modal.types.ts +++ b/packages/reshaped/src/components/Modal/Modal.types.ts @@ -53,4 +53,7 @@ export type Props = { overlayClassName?: G.ClassName; /** Additional attributes for the root element */ attributes?: G.Attributes<"div"> & { ref?: React.RefObject }; -} & Pick; +} & Pick< + OverlayProps, + "onOpen" | "onAfterOpen" | "onAfterClose" | "active" | "containerRef" | "keepPositionFixed" +>; diff --git a/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx b/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx index 813e7588..13de6f8b 100644 --- a/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx +++ b/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx @@ -212,8 +212,10 @@ export const containerRef = { render: () => { const containerRef = React.useRef(null); const containerRef2 = React.useRef(null); + const containerRef3 = React.useRef(null); const toggle = useToggle(); const toggle2 = useToggle(); + const toggle3 = useToggle(); return ( @@ -260,6 +262,27 @@ export const containerRef = { + + + + + + + + ); }, diff --git a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx index ec867621..c5111583 100644 --- a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx +++ b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx @@ -265,7 +265,7 @@ export const containerRef: StoryObj = { }; export const containerRefWithKeepFixedPosition: StoryObj = { - name: "containerRef with keepFixedPosition", + name: "containerRef with keepPositionFixed", render: () => { const containerRef = React.useRef(null); From d0906cc95d6ba04f8807a9c72d33ca46749f3187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rick?= Date: Thu, 15 Jan 2026 10:43:36 -0300 Subject: [PATCH 3/4] refactor(Modal, Overlay): rename keepPositionFixed to contained and update related logic --- packages/reshaped/src/components/Modal/Modal.tsx | 6 +++--- packages/reshaped/src/components/Modal/Modal.types.ts | 2 +- .../reshaped/src/components/Modal/tests/Modal.stories.tsx | 2 +- packages/reshaped/src/components/Overlay/Overlay.tsx | 4 ++-- packages/reshaped/src/components/Overlay/Overlay.types.ts | 4 ++-- .../src/components/Overlay/tests/Overlay.stories.tsx | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/reshaped/src/components/Modal/Modal.tsx b/packages/reshaped/src/components/Modal/Modal.tsx index b96448a0..eb9b9b75 100644 --- a/packages/reshaped/src/components/Modal/Modal.tsx +++ b/packages/reshaped/src/components/Modal/Modal.tsx @@ -80,7 +80,7 @@ const Modal: React.FC = (props) => { disableSwipeGesture, disableCloseOnOutsideClick, containerRef, - keepPositionFixed, + contained, overlayClassName, className, attributes, @@ -100,7 +100,7 @@ const Modal: React.FC = (props) => { const [dragDistance, setDragDistance] = React.useState(0); const [hideProgress, setHideProgress] = React.useState(0); const mixinStyles = resolveMixin({ padding }); - const shouldBeContained = containerRef && !keepPositionFixed; + const shouldBeContained = containerRef && contained !== false; const value = React.useMemo( () => ({ @@ -249,7 +249,7 @@ const Modal: React.FC = (props) => { blurred={blurredOverlay} overflow={clientPosition === "center" ? "auto" : "hidden"} className={overlayClassName} - keepPositionFixed={keepPositionFixed} + contained={contained} containerRef={containerRef} attributes={{ onTouchStart: handleDragStart, diff --git a/packages/reshaped/src/components/Modal/Modal.types.ts b/packages/reshaped/src/components/Modal/Modal.types.ts index 3ea10f58..bce6089c 100644 --- a/packages/reshaped/src/components/Modal/Modal.types.ts +++ b/packages/reshaped/src/components/Modal/Modal.types.ts @@ -55,5 +55,5 @@ export type Props = { attributes?: G.Attributes<"div"> & { ref?: React.RefObject }; } & Pick< OverlayProps, - "onOpen" | "onAfterOpen" | "onAfterClose" | "active" | "containerRef" | "keepPositionFixed" + "onOpen" | "onAfterOpen" | "onAfterClose" | "active" | "containerRef" | "contained" >; diff --git a/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx b/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx index 13de6f8b..dd04e973 100644 --- a/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx +++ b/packages/reshaped/src/components/Modal/tests/Modal.stories.tsx @@ -273,7 +273,7 @@ export const containerRef = { > = (props) => { onAfterOpen, disableCloseOnClick, containerRef, - keepPositionFixed, + contained, className, attributes, } = props; @@ -58,7 +58,7 @@ const Overlay: React.FC = (props) => { const { active: rendered, activate: render, deactivate: remove } = useToggle(active || false); const { active: visible, activate: show, deactivate: hide } = useToggle(active || false); const isDismissible = useIsDismissible({ active, contentRef, hasTrigger: false }); - const shouldBeContained = containerRef && !keepPositionFixed; + const shouldBeContained = containerRef && contained !== false; const rootClassNames = classNames( s.root, diff --git a/packages/reshaped/src/components/Overlay/Overlay.types.ts b/packages/reshaped/src/components/Overlay/Overlay.types.ts index 523d9bdb..abc4b995 100644 --- a/packages/reshaped/src/components/Overlay/Overlay.types.ts +++ b/packages/reshaped/src/components/Overlay/Overlay.types.ts @@ -26,8 +26,8 @@ export type Props = { disableCloseOnClick?: boolean; /** Element to render the component in */ containerRef?: React.RefObject; - /** Keep the fixed position of the component even when it is rendered inside a container */ - keepPositionFixed?: boolean; + /** Contain the component within the container element. Defaults to true when containerRef is provided */ + contained?: boolean; /** Additional classname for the root element */ className?: G.ClassName; /** Additional attributes for the root element */ diff --git a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx index c5111583..f75440c5 100644 --- a/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx +++ b/packages/reshaped/src/components/Overlay/tests/Overlay.stories.tsx @@ -264,15 +264,15 @@ export const containerRef: StoryObj = { }, }; -export const containerRefWithKeepFixedPosition: StoryObj = { - name: "containerRef with keepPositionFixed", +export const containerRefNotContained: StoryObj = { + name: "containerRef with contained={false}", render: () => { const containerRef = React.useRef(null); return ( <>
- + Content From 9e7d8789ff416fa28be7dc44e3d280578b3d4e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rick?= Date: Thu, 15 Jan 2026 10:50:17 -0300 Subject: [PATCH 4/4] docs: adding changeset --- .changeset/shaky-eagles-rescue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaky-eagles-rescue.md diff --git a/.changeset/shaky-eagles-rescue.md b/.changeset/shaky-eagles-rescue.md new file mode 100644 index 00000000..ccc614d2 --- /dev/null +++ b/.changeset/shaky-eagles-rescue.md @@ -0,0 +1,5 @@ +--- +"reshaped": minor +--- + +Modal/Overlay: Adding the contained prop to control whether the component should be contained within the container element.