Skip to content

Commit

Permalink
feat: Let the context menu move withing the viewport
Browse files Browse the repository at this point in the history
  • Loading branch information
miyanokomiya committed Oct 9, 2024
1 parent 645eeb9 commit ad9652d
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 6 deletions.
11 changes: 10 additions & 1 deletion src/components/AppCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ export const AppCanvas: React.FC = () => {

const wrapperRef = useRef<HTMLDivElement>(null);
const getWrapper = useCallback(() => wrapperRef.current, []);
const getWrapperSize = useCallback(
() => getWrapper()?.getBoundingClientRect() ?? { width: 0, height: 0 },
[getWrapper],
);
const {
setViewport,
zoomView,
Expand Down Expand Up @@ -855,7 +859,12 @@ export const AppCanvas: React.FC = () => {
{textEditor}
{linkMenu}
{contextMenu ? (
<ContextMenu items={contextMenu.items} point={contextMenu.point} onClickItem={onClickContextMenuItem} />
<ContextMenu
items={contextMenu.items}
point={contextMenu.point}
onClickItem={onClickContextMenuItem}
getContainerSize={getWrapperSize}
/>
) : undefined}
</>
);
Expand Down
41 changes: 36 additions & 5 deletions src/components/ContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { IVec2 } from "okageo";
import { add, IVec2 } from "okageo";
import { ContextMenuItem } from "../composables/states/types";
import { useCallback, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { ListButton, ListSpacer } from "./atoms/buttons/ListButton";
import { AppText } from "./molecules/AppText";
import iconDropdown from "../assets/icons/dropdown.svg";
import { Size } from "../models";

interface Props {
items: ContextMenuItem[];
point: IVec2;
onClickItem?: (key: string, meta?: any) => void;
getContainerSize?: () => Size;
}

export const ContextMenu: React.FC<Props> = ({ items, point, onClickItem }) => {
export const ContextMenu: React.FC<Props> = ({ items, point, onClickItem, getContainerSize }) => {
const handleClick = useCallback(
(item: ContextMenuItem) => {
if ("separator" in item) return;
Expand All @@ -20,11 +22,16 @@ export const ContextMenu: React.FC<Props> = ({ items, point, onClickItem }) => {
[onClickItem],
);

const ref = useRef<HTMLDivElement>(null);
const { diff } = usePanelWithinViewport(ref, getContainerSize);
const p = diff ? add(diff, point) : point;

return (
<div
className="fixed border left-0 top-0 bg-white"
ref={ref}
className={"fixed border left-0 top-0 bg-white" + (diff ? "" : " opacity-0")}
style={{
transform: `translate(${point.x}px, ${point.y}px)`,
transform: `translate(${p.x}px, ${p.y}px)`,
}}
>
<div className="flex flex-col">
Expand Down Expand Up @@ -101,3 +108,27 @@ const ContextItem: React.FC<ContextItemProps> = ({ item, dropdownKey, onClickIte
</div>
);
};

const PANEL_OFFSET = 4;

const usePanelWithinViewport = (panelRef: React.RefObject<HTMLElement>, getContainerSize?: () => Size) => {
const [diff, setDiff] = useState<IVec2>();

useEffect(() => {
if (!panelRef.current || !getContainerSize) return;

const viewportSize = getContainerSize();
const rect = panelRef.current.getBoundingClientRect();
let dx = 0;
if (rect.right > viewportSize.width + PANEL_OFFSET) {
dx = -rect.width;
}
let dy = 0;
if (rect.bottom > viewportSize.height + PANEL_OFFSET) {
dy = viewportSize.height - rect.bottom - PANEL_OFFSET;
}
setDiff({ x: dx, y: dy });
}, [panelRef, getContainerSize]);

return { diff };
};

0 comments on commit ad9652d

Please sign in to comment.