Skip to content

Commit d99f23f

Browse files
ENG-1742 Add max height and scroll for node type menu (#1019)
* ENG-1742 Add max height and scroll to node type menu Adds `max-h-[60vh] overflow-y-auto` to the node menu list so it doesn't overflow off-screen when there are many node types. Also scrolls the active item into view when navigating with arrow keys. Co-authored-by: Cursor <cursoragent@cursor.com> * fix max height for NodeMenu * support keyboard-only flow * use Blueprint flip: enabled to position Popover --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent da78ce0 commit d99f23f

1 file changed

Lines changed: 41 additions & 2 deletions

File tree

apps/roam/src/components/DiscourseNodeMenu.tsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Props = {
3535
extensionAPI: OnloadArgs["extensionAPI"];
3636
trigger?: JSX.Element;
3737
isShift?: boolean;
38+
menuMaxHeight?: number;
3839
};
3940

4041
const NodeMenu = ({
@@ -44,6 +45,7 @@ const NodeMenu = ({
4445
extensionAPI,
4546
trigger,
4647
isShift,
48+
menuMaxHeight,
4749
}: { onClose: () => void } & Props) => {
4850
const isInitialTextSelected =
4951
!!textarea && textarea.selectionStart !== textarea.selectionEnd;
@@ -71,6 +73,23 @@ const NodeMenu = ({
7173
const [activeIndex, setActiveIndex] = useState(0);
7274
const [isOpen, setIsOpen] = useState(!trigger);
7375

76+
useEffect(() => {
77+
const container = menuRef.current;
78+
if (!container) return;
79+
const activeItem = container.children[activeIndex] as
80+
| HTMLElement
81+
| undefined;
82+
if (!activeItem) return;
83+
const containerRect = container.getBoundingClientRect();
84+
const itemRect = activeItem.getBoundingClientRect();
85+
if (
86+
itemRect.bottom > containerRect.bottom ||
87+
itemRect.top < containerRect.top
88+
) {
89+
activeItem.scrollIntoView({ block: "nearest", behavior: "auto" });
90+
}
91+
}, [activeIndex]);
92+
7493
const onSelect = useCallback(
7594
(index: number) => {
7695
const menuItem =
@@ -252,14 +271,18 @@ const NodeMenu = ({
252271
className="relative z-50"
253272
position={Position.BOTTOM_LEFT}
254273
modifiers={{
255-
flip: { enabled: false },
274+
flip: { enabled: true },
256275
preventOverflow: { enabled: false },
257276
}}
258277
autoFocus={false}
259278
enforceFocus={false}
260279
onInteraction={trigger ? handlePopoverInteraction : undefined}
261280
content={
262-
<Menu ulRef={menuRef} data-active-index={activeIndex}>
281+
<Menu
282+
ulRef={menuRef}
283+
data-active-index={activeIndex}
284+
style={{ overflowY: "auto", maxHeight: menuMaxHeight }}
285+
>
263286
{discourseNodes.map((item, i) => {
264287
const nodeColor =
265288
formatHexColor(item?.canvasSettings?.color) || "#000";
@@ -302,15 +325,25 @@ const NodeMenu = ({
302325

303326
export const render = (props: Props) => {
304327
if (!props.textarea) return;
328+
if (props.textarea.parentElement?.querySelector("[data-discourse-node-menu]"))
329+
return;
305330
const parent = document.createElement("span");
331+
parent.setAttribute("data-discourse-node-menu", "true");
306332
const coords = getCoordsFromTextarea(props.textarea);
307333
parent.style.position = "absolute";
308334
parent.style.left = `${coords.left}px`;
309335
parent.style.top = `${coords.top}px`;
310336
props.textarea.parentElement?.insertBefore(parent, props.textarea);
337+
const parentTop =
338+
props.textarea.parentElement?.getBoundingClientRect().top ?? 0;
339+
const menuMaxHeight = Math.max(
340+
window.innerHeight - (parentTop + coords.top) - 24,
341+
100,
342+
);
311343
ReactDOM.render(
312344
<NodeMenu
313345
{...props}
346+
menuMaxHeight={menuMaxHeight}
314347
onClose={() => {
315348
ReactDOM.unmountComponentAtNode(parent);
316349
parent.remove();
@@ -355,13 +388,19 @@ export const TextSelectionNodeMenu = ({
355388
/>
356389
);
357390

391+
const menuMaxHeight = Math.max(
392+
window.innerHeight - textarea.getBoundingClientRect().bottom - 8,
393+
100,
394+
);
395+
358396
return (
359397
<NodeMenu
360398
textarea={textarea}
361399
extensionAPI={extensionAPI}
362400
trigger={trigger}
363401
onClose={onClose}
364402
isShift
403+
menuMaxHeight={menuMaxHeight}
365404
/>
366405
);
367406
};

0 commit comments

Comments
 (0)