Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions builder-frontend/src/components/icon/QuestionMarkIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default function QuestionMarkIcon(props: { class?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/Url"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
class={props.class || "size-6"}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
/>
</svg>
);
}
99 changes: 99 additions & 0 deletions builder-frontend/src/components/shared/QuestionTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { createSignal, JSX, Show } from "solid-js";
import QuestionMarkIcon from "../icon/QuestionMarkIcon";

export enum TooltipAlignment {
Center = "center",
Left = "left",
Right = "right",
}

interface TooltipProps {
text: string;
children?: JSX.Element;
}

export default function QuestionTooltip(props: TooltipProps) {
const [isVisible, setIsVisible] = createSignal(false);
const [position, setPosition] = createSignal<{
x: number;
align: TooltipAlignment;
}>({ x: 0, align: TooltipAlignment.Center });

let containerRef: HTMLDivElement | undefined;

const determineAlignment = (rect: DOMRect): TooltipAlignment => {
const tooltipWidth = 256; // w-64 is 16rem = 256px

// Calculate potential left and right bounds if centered
const centerLeft = rect.left + rect.width / 2 - tooltipWidth / 2;
const centerRight = centerLeft + tooltipWidth;

const viewportWidth = window.innerWidth;
const padding = 16; // 16px safety padding from screen edges

if (centerLeft < padding) {
return TooltipAlignment.Left;
} else if (centerRight > viewportWidth - padding) {
return TooltipAlignment.Right;
} else {
return TooltipAlignment.Center;
}
};

const handleMouseEnter = () => {
if (containerRef) {
const rect = containerRef.getBoundingClientRect();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readability, I have a few small nitpicks here:

  • Creating a typescript type called something like TooltipAlignment set to 'center' | 'left' | 'right'.
  • Having the handleMouseEnter(...) function call another function for determining alignment that returns a TooltipAlignment, and then setting setPosition(...); based on the return value.

This would make it easier at-a-glance to know what handleMouseEnter(...) does. This is purely stylistic so feel free to ignore.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. It would make the code more readable and maintainable.

const align = determineAlignment(rect);
setPosition({ x: 0, align });
}
setIsVisible(true);
};

const handleMouseLeave = () => setIsVisible(false);

return (
<div
ref={containerRef}
class="relative inline-flex items-center justify-center cursor-help"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onFocus={handleMouseEnter}
onBlur={handleMouseLeave}
tabIndex={0}
aria-label="More information"
>
<Show
when={props.children}
fallback={
<QuestionMarkIcon class="size-5 text-gray-500 hover:text-gray-700 transition-colors" />
}
>
{props.children}
</Show>

<Show when={isVisible()}>
<div
class={`absolute z-50 w-64 p-3 mt-2 text-sm text-gray-800 bg-white border border-gray-200 rounded-lg shadow-lg top-full pointer-events-none fade-in ${
position().align === TooltipAlignment.Center
? "left-1/2 -translate-x-1/2"
: position().align === TooltipAlignment.Left
? "left-0"
: "right-0"
}`}
>
{props.text}
{/* Decorative arrow pointing up */}
<div
class={`absolute w-3 h-3 bg-white border-t border-l border-gray-200 rotate-45 -top-[7px] ${
position().align === TooltipAlignment.Center
? "left-1/2 -translate-x-1/2"
: position().align === TooltipAlignment.Left
? "left-3"
: "right-3"
}`}
/>
</div>
</Show>
</div>
);
}
Loading