Skip to content

Commit

Permalink
feat: display edit box and tooltip below the card when cropped (#842)
Browse files Browse the repository at this point in the history
  • Loading branch information
srvEq authored Dec 12, 2024
1 parent 5f4d668 commit 759d01c
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 16 deletions.
4 changes: 4 additions & 0 deletions components/canvas/ChoiceNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { WaitingButton } from "./WaitingButton";
import { useIsEditingNode } from "./hooks/useIsEditingNode";
import { useNodeAdd } from "./hooks/useNodeAdd";
import { isChoiceChild } from "./utils/nodeRelationsHelper";
import { useNodeRef } from "./hooks/useNodeRef";

export const ChoiceNode = ({
data,
Expand Down Expand Up @@ -48,6 +49,7 @@ export const ChoiceNode = ({
const connectionNodeId = useStore((state) => state.connectionNodeId);
const lastChild = children[children?.length - 1];
const isEditingNode = useIsEditingNode(selected);
const ref = useNodeRef();

useEffect(() => {
setHovering(false);
Expand Down Expand Up @@ -150,6 +152,7 @@ export const ChoiceNode = ({
!disabled && !dragging && setHovering(true);
}}
onMouseLeave={() => setHovering(false)}
ref={ref}
>
<NodeCard
onClick={handleClickNode}
Expand Down Expand Up @@ -183,6 +186,7 @@ export const ChoiceNode = ({
includeRole={false}
includeDuration={false}
includeEstimate={false}
nodeRef={ref}
/>
{renderNodeButtons()}
</div>
Expand Down
4 changes: 4 additions & 0 deletions components/canvas/GenericNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { NodeTooltip } from "./NodeTooltip";
import { QIPRContainer } from "./QIPRContainer";
import { SourceHandle } from "./SourceHandle";
import { getNodeHelperText } from "./utils/getNodeHelperText";
import { useNodeRef } from "./hooks/useNodeRef";

export const GenericNode = ({
data,
Expand Down Expand Up @@ -45,6 +46,7 @@ export const GenericNode = ({

const handleQIPRContainerOnClick = useQIPRContainerOnClick(data);
const shouldDisplayQIPR = useShouldDisplayQIPR(tasks, hovering, selected);
const ref = useNodeRef();

useEffect(() => {
setHovering(false);
Expand Down Expand Up @@ -78,6 +80,7 @@ export const GenericNode = ({
<div
onMouseEnter={() => !disabled && !dragging && setHovering(true)}
onMouseLeave={() => setHovering(false)}
ref={ref}
>
<NodeCard
onClick={handleClickNode}
Expand Down Expand Up @@ -120,6 +123,7 @@ export const GenericNode = ({
includeRole={false}
includeDuration={false}
includeEstimate={false}
nodeRef={ref}
/>
{renderNodeButtons()}
</div>
Expand Down
5 changes: 4 additions & 1 deletion components/canvas/LinkedProcessNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
isChoiceChild,
isMainActivityColumn,
} from "./utils/nodeRelationsHelper";
import { useNodeRef } from "./hooks/useNodeRef";

export const LinkedProcessNode = ({
data: {
Expand All @@ -51,6 +52,7 @@ export const LinkedProcessNode = ({
const [hoveringShape, setHoveringShape] = useState(false);
const connectionNodeId = useStore((state) => state.connectionNodeId);
const { addNode, isNodeButtonDisabled } = useNodeAdd();
const ref = useNodeRef();

const name = getProjectName(linkedProjectData || undefined);
const userAccesses = linkedProjectData?.userAccesses;
Expand Down Expand Up @@ -146,6 +148,7 @@ export const LinkedProcessNode = ({
<div
onMouseEnter={() => !disabled && !dragging && setHovering(true)}
onMouseLeave={() => setHovering(false)}
ref={ref}
>
<NodeCard
onClick={handleClickNode}
Expand Down Expand Up @@ -177,7 +180,7 @@ export const LinkedProcessNode = ({
</NodeCard>
<TargetHandle hidden={!mergeOption} />
<SourceHandle />
<NodeTooltipContainer isVisible={hoveringShape}>
<NodeTooltipContainer isVisible={hoveringShape} nodeRef={ref}>
{name && <NodeTooltipSection header={"Title"} text={name} />}
{formattedUpdated && (
<NodeTooltipSection header={"Last Updated"} text={formattedUpdated} />
Expand Down
4 changes: 4 additions & 0 deletions components/canvas/MainActivityNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { QIPRContainer } from "./QIPRContainer";
import { SourceHandle } from "./SourceHandle";
import { SubActivityButton } from "./SubActivityButton";
import { WaitingButton } from "./WaitingButton";
import { useNodeRef } from "./hooks/useNodeRef";

export const MainActivityNode = ({
data,
Expand Down Expand Up @@ -54,6 +55,7 @@ export const MainActivityNode = ({

const handleQIPRContainerOnClick = useQIPRContainerOnClick(data);
const shouldDisplayQIPR = useShouldDisplayQIPR(tasks, hovering, selected);
const ref = useNodeRef();

const formattedDurationSum =
totalDurations && formatMinMaxTotalDuration(totalDurations);
Expand Down Expand Up @@ -113,6 +115,7 @@ export const MainActivityNode = ({
<div
onMouseEnter={() => !disabled && !dragging && setHovering(true)}
onMouseLeave={() => setHovering(false)}
ref={ref}
>
<NodeCard
onClick={handleClickNode}
Expand Down Expand Up @@ -158,6 +161,7 @@ export const MainActivityNode = ({
includeDuration={false}
includeEstimate
estimate={formattedDurationSum}
nodeRef={ref}
/>
{renderNodeButtons()}
</div>
Expand Down
91 changes: 76 additions & 15 deletions components/canvas/NodeTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,91 @@
import { NodeDataCommon } from "@/types/NodeData";
import { ReactNode } from "react";
import React, {
ReactNode,
RefObject,
useLayoutEffect,
useRef,
useState,
} from "react";
import { NodeToolbar, NodeToolbarProps, Position } from "reactflow";
import { EditableNodeTooltipSection } from "./EditableNodeTooltipSection";
import styles from "./NodeTooltip.module.scss";

type NodeTooltipContainerProps = {
children: ReactNode;
isVisible?: boolean;
position?: Position;
style?: NodeToolbarProps["style"];
isEditing?: boolean;
nodeRef?: RefObject<HTMLDivElement>;
};

export const NodeTooltipContainer = ({
children,
isVisible,
position,
style,
isEditing,
nodeRef,
}: NodeTooltipContainerProps) => {
const toolTipRef = useRef<HTMLDivElement>(null);
const [tooltipPosition, setTooltipPosition] = useState<Position | undefined>(
undefined
);
const [offset, setoffset] = useState(10);
const calculateTooltipPosition = (toolTipHeight: number) => {
const appHeaderSpace = 70;
if (!toolTipRef?.current) return;
const viewPortBottom = toolTipRef.current.getBoundingClientRect().bottom;
const nodeHeight = nodeRef?.current?.getBoundingClientRect().height;
const availableSpace = viewPortBottom - (nodeHeight ?? 0) - appHeaderSpace;
if (toolTipHeight > availableSpace) {
setTooltipPosition(Position.Bottom);
setoffset(30);
} else {
setTooltipPosition(Position.Top);
setoffset(10);
}
};

useLayoutEffect(() => {
const tooltipNode = document.querySelector(".react-flow__node-toolbar");
if (!tooltipNode) return;
const calculatePositionIfNeeded = () => {
const hasChildren =
(tooltipNode.querySelector("div")?.childElementCount ?? 0) > 0;
const toolTipHeight = tooltipNode.getBoundingClientRect().height;
if (hasChildren && toolTipHeight) {
calculateTooltipPosition(toolTipHeight);
return true;
}
return false;
};
// Check initially if tooltipNode DOM has updated data to calculate position
if (calculatePositionIfNeeded()) return;
const observer = new MutationObserver(() => {
if (calculatePositionIfNeeded()) observer.disconnect(); //disconnect observer when position is calculated
});

observer.observe(tooltipNode, {
attributes: true,
childList: true,
subtree: true,
});

return () => observer.disconnect();
}, [isVisible, toolTipRef, isEditing]);

return (
<NodeToolbar
position={position}
isVisible={isVisible}
className={styles.container}
onMouseDownCapture={(e) => e.stopPropagation()}
style={style}
>
{children}
</NodeToolbar>
<div ref={toolTipRef}>
<NodeToolbar
position={tooltipPosition}
isVisible={isVisible}
className={styles.container}
onMouseDownCapture={(e) => e.stopPropagation()}
style={style}
offset={offset}
>
{children}
</NodeToolbar>
</div>
);
};

Expand All @@ -42,10 +101,11 @@ type Field<IncludeKey extends string, Key extends string> =
[k in Key]: string | undefined;
});

type NodeTooltipProps = Pick<NodeTooltipContainerProps, "position"> & {
type NodeTooltipProps = {
nodeData: NodeDataCommon;
isHovering?: boolean;
isEditing?: boolean;
nodeRef: RefObject<HTMLDivElement>;
} & Field<"includeDescription", "description"> &
Field<"includeDuration", "duration"> &
Field<"includeEstimate", "estimate"> &
Expand All @@ -62,8 +122,8 @@ export const NodeTooltip = ({
estimate,
isHovering,
isEditing,
position,
nodeData,
nodeRef,
}: NodeTooltipProps) => {
const editingStyle = { minWidth: "300px" };
const tooltipStyle = isEditing ? editingStyle : undefined;
Expand All @@ -75,8 +135,9 @@ export const NodeTooltip = ({
return (
<NodeTooltipContainer
isVisible={isHovering || isEditing}
position={position}
style={tooltipStyle}
isEditing={isEditing}
nodeRef={nodeRef}
>
{shouldDisplayDescription && (
<EditableNodeTooltipSection
Expand Down
4 changes: 4 additions & 0 deletions components/canvas/SubActivityNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useQIPRContainerOnClick } from "./hooks/useQIPRContainerOnClick";
import { useShouldDisplayQIPR } from "./hooks/useShouldDisplayQIPR";
import { FormatNodeText } from "./utils/FormatNodeText";
import { isChoiceChild } from "./utils/nodeRelationsHelper";
import { useNodeRef } from "./hooks/useNodeRef";

export const SubActivityNode = ({
data,
Expand Down Expand Up @@ -60,6 +61,7 @@ export const SubActivityNode = ({

const handleQIPRContainerOnClick = useQIPRContainerOnClick(data);
const shouldDisplayQIPR = useShouldDisplayQIPR(tasks, hovering, selected);
const ref = useNodeRef();

useEffect(() => {
setHovering(false);
Expand Down Expand Up @@ -148,6 +150,7 @@ export const SubActivityNode = ({
<div
onMouseEnter={() => !disabled && !dragging && setHovering(true)}
onMouseLeave={() => setHovering(false)}
ref={ref}
>
<NodeCard
onClick={handleClickNode}
Expand Down Expand Up @@ -198,6 +201,7 @@ export const SubActivityNode = ({
includeDuration
duration={formatDuration(duration, unit)}
includeEstimate={false}
nodeRef={ref}
/>
{renderNodeButtons()}
</div>
Expand Down
4 changes: 4 additions & 0 deletions components/canvas/WaitingNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useNodeAdd } from "./hooks/useNodeAdd";
import { useQIPRContainerOnClick } from "./hooks/useQIPRContainerOnClick";
import { useShouldDisplayQIPR } from "./hooks/useShouldDisplayQIPR";
import { isChoiceChild } from "./utils/nodeRelationsHelper";
import { useNodeRef } from "./hooks/useNodeRef";

export const WaitingNode = ({
data,
Expand Down Expand Up @@ -61,6 +62,7 @@ export const WaitingNode = ({

const handleQIPRContainerOnClick = useQIPRContainerOnClick(data);
const shouldDisplayQIPR = useShouldDisplayQIPR(tasks, hovering, selected);
const ref = useNodeRef();

useEffect(() => {
setHovering(false);
Expand Down Expand Up @@ -149,6 +151,7 @@ export const WaitingNode = ({
<div
onMouseEnter={() => !disabled && !dragging && setHovering(true)}
onMouseLeave={() => setHovering(false)}
ref={ref}
>
<NodeCard
onClick={handleClickNode}
Expand Down Expand Up @@ -194,6 +197,7 @@ export const WaitingNode = ({
includeDuration
duration={formatDuration(duration, unit)}
includeEstimate={false}
nodeRef={ref}
/>
{renderNodeButtons()}
</div>
Expand Down
3 changes: 3 additions & 0 deletions components/canvas/hooks/useNodeRef.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { useRef } from "react";

export const useNodeRef = () => useRef<HTMLDivElement>(null);

0 comments on commit 759d01c

Please sign in to comment.