Skip to content

Commit

Permalink
feat: adjust contextual dropdown vert position
Browse files Browse the repository at this point in the history
  • Loading branch information
petermakowski committed Jan 9, 2024
1 parent 80a8005 commit 3da8d12
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
3 changes: 2 additions & 1 deletion src/components/ContextualMenu/ContextualMenu.stories.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs";

import Button from "../Button";
import ContextualMenu from "./ContextualMenu";

<Meta
Expand Down Expand Up @@ -37,6 +37,7 @@ export const ScrollTemplate = (args) => (
voluptas odit aspernatur alias molestias facere.
</p>
)}
<ContextualMenu {...args} />
</div>
);

Expand Down
44 changes: 43 additions & 1 deletion src/components/ContextualMenu/ContextualMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ export type Props<L> = PropsWithSpread<
HTMLProps<HTMLSpanElement>
>;

const getClosestScrollableParent = (
node: HTMLElement | null
): HTMLElement | null => {
let currentNode = node;
while (currentNode && currentNode !== document.body) {
const { overflowY, overflowX } = window.getComputedStyle(currentNode);
if (
["auto", "scroll", "overlay"].includes(overflowY) &&
["auto", "scroll", "overlay"].includes(overflowX)
) {
return currentNode;
}
currentNode = currentNode.parentElement;
}
return document.body;
};

/**
* Get the node to use for positioning the menu.
* @param wrapper - The component's wrapping element.
Expand Down Expand Up @@ -191,11 +208,35 @@ const ContextualMenu = <L,>({
const wrapper = useRef<HTMLDivElement | null>(null);
const [positionCoords, setPositionCoords] = useState<DOMRect>();
const [adjustedPosition, setAdjustedPosition] = useState(position);
const [verticalPosition, setVerticalPosition] = useState<"top" | "bottom">(
"bottom"
);
const hasToggle = hasToggleIcon || toggleLabel;

const updateVerticalPosition = useCallback(() => {
const parent = getPositionNode(wrapper.current, positionNode);
if (!parent) {
return null;
}
const rect = parent.getBoundingClientRect();
const scrollableParent = getClosestScrollableParent(parent);
if (!scrollableParent) {
return null;
}
const scrollableParentRect = scrollableParent.getBoundingClientRect();
const spaceBelow = scrollableParentRect.height - rect.bottom;
const spaceAbove = rect.top;
const dropdownHeight = rect.height;

setVerticalPosition(
spaceBelow >= dropdownHeight || spaceBelow > spaceAbove ? "bottom" : "top"
);
}, [wrapper, positionNode]);

useEffect(() => {
setAdjustedPosition(position);
}, [position, autoAdjust]);
updateVerticalPosition();
}, [position, autoAdjust, updateVerticalPosition]);

// Update the coordinates of the position node.
const updatePositionCoords = useCallback(() => {
Expand Down Expand Up @@ -338,6 +379,7 @@ const ContextualMenu = <L,>({
<Portal>
<ContextualMenuDropdown<L>
adjustedPosition={adjustedPosition}
verticalPosition={verticalPosition}
autoAdjust={autoAdjust}
handleClose={closePortal}
constrainPanelWidth={constrainPanelWidth}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ export enum Label {
export type MenuLink<L = null> = string | ButtonProps<L> | ButtonProps<L>[];

export type Position = "left" | "center" | "right";
type VerticalPosition = "top" | "bottom";

/**
* The props for the ContextualMenuDropdown component.
* @template L - The type of the link props.
*/
export type Props<L = null> = {
adjustedPosition?: Position;
verticalPosition?: VerticalPosition;
autoAdjust?: boolean;
handleClose?: (evt?: MouseEvent) => void;
constrainPanelWidth?: boolean;
Expand All @@ -55,14 +57,18 @@ export type Props<L = null> = {
*/
const getPositionStyle = (
position: Position,
verticalPosition: VerticalPosition,
positionCoords: Props["positionCoords"],
constrainPanelWidth: Props["constrainPanelWidth"]
): React.CSSProperties => {
if (!positionCoords) {
return null;
}
const { height, left, top, width } = positionCoords;
const topPos = top + height + (window.scrollY || 0);
const topPos =
verticalPosition === "bottom"
? top + height + (window.scrollY || 0)
: top + (window.scrollY || 0);
let leftPos = left;

switch (position) {
Expand Down Expand Up @@ -173,6 +179,7 @@ const ContextualMenuDropdown = <L,>({
links,
position,
positionCoords,
verticalPosition,
positionNode,
scrollOverflow,
setAdjustedPosition,
Expand All @@ -182,16 +189,26 @@ const ContextualMenuDropdown = <L,>({
const dropdown = useRef();

const [positionStyle, setPositionStyle] = useState(
getPositionStyle(adjustedPosition, positionCoords, constrainPanelWidth)
getPositionStyle(
adjustedPosition,
verticalPosition,
positionCoords,
constrainPanelWidth
)
);
const [maxHeight, setMaxHeight] = useState<number>();

// Update the styles to position the menu.
const updatePositionStyle = useCallback(() => {
setPositionStyle(
getPositionStyle(adjustedPosition, positionCoords, constrainPanelWidth)
getPositionStyle(
adjustedPosition,
verticalPosition,
positionCoords,
constrainPanelWidth
)
);
}, [adjustedPosition, positionCoords, constrainPanelWidth]);
}, [adjustedPosition, positionCoords, verticalPosition, constrainPanelWidth]);

// Update the position when the window fitment info changes.
const onUpdateWindowFitment = useCallback(
Expand Down Expand Up @@ -237,6 +254,7 @@ const ContextualMenuDropdown = <L,>({
...(scrollOverflow
? { maxHeight, minHeight: "2rem", overflowX: "auto" }
: {}),
...(verticalPosition === "top" ? { bottom: "0" } : {}),
}}
{...props}
>
Expand Down

0 comments on commit 3da8d12

Please sign in to comment.