From 6678dd8a90e01db91d8e33349d73ce150adfa569 Mon Sep 17 00:00:00 2001 From: Javaughn Pryce Date: Fri, 27 Feb 2026 22:08:07 -0500 Subject: [PATCH 1/5] create question mark icon --- .../src/components/icon/QuestionMarkIcon.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 builder-frontend/src/components/icon/QuestionMarkIcon.tsx diff --git a/builder-frontend/src/components/icon/QuestionMarkIcon.tsx b/builder-frontend/src/components/icon/QuestionMarkIcon.tsx new file mode 100644 index 00000000..4160540c --- /dev/null +++ b/builder-frontend/src/components/icon/QuestionMarkIcon.tsx @@ -0,0 +1,18 @@ +export default function QuestionMarkIcon(props: { class?: string }) { + return ( + + + + ); +} From 6e321267d9e41488abd1a307767e648ee7319313 Mon Sep 17 00:00:00 2001 From: Javaughn Pryce Date: Fri, 27 Feb 2026 22:12:20 -0500 Subject: [PATCH 2/5] create reusable question mark component --- .../src/components/shared/QuestionTooltip.tsx | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 builder-frontend/src/components/shared/QuestionTooltip.tsx diff --git a/builder-frontend/src/components/shared/QuestionTooltip.tsx b/builder-frontend/src/components/shared/QuestionTooltip.tsx new file mode 100644 index 00000000..17e74950 --- /dev/null +++ b/builder-frontend/src/components/shared/QuestionTooltip.tsx @@ -0,0 +1,82 @@ +import { createSignal, JSX } from "solid-js"; +import QuestionMarkIcon from "../icon/QuestionMarkIcon"; + +interface TooltipProps { + text: string; + iconClass?: string; + children?: JSX.Element; +} + +export default function QuestionTooltip(props: TooltipProps) { + const [isVisible, setIsVisible] = createSignal(false); + const [position, setPosition] = createSignal<{ x: number, align: 'center' | 'left' | 'right' }>({ x: 0, align: 'center' }); + + let containerRef: HTMLDivElement | undefined; + + const handleMouseEnter = () => { + if (containerRef) { + const rect = containerRef.getBoundingClientRect(); + 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) { + // Overflow on the left: align left edge of tooltip with container + setPosition({ x: 0, align: 'left' }); + } else if (centerRight > viewportWidth - padding) { + // Overflow on the right: align right edge of tooltip with container + setPosition({ x: 0, align: 'right' }); + } else { + // Safe to center perfectly + setPosition({ x: 0, align: 'center' }); + } + } + setIsVisible(true); + }; + + const handleMouseLeave = () => setIsVisible(false); + + return ( +
+ {props.children ? ( + props.children + ) : ( + + )} + + {isVisible() && ( +
+ {props.text} + {/* Decorative arrow pointing up */} +
+
+ )} +
+ ); +} From 50daadf80dff08c9183d679501e4776001de90ec Mon Sep 17 00:00:00 2001 From: Javaughn Pryce Date: Fri, 27 Feb 2026 22:24:39 -0500 Subject: [PATCH 3/5] remove iconClass prop --- builder-frontend/src/components/shared/QuestionTooltip.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builder-frontend/src/components/shared/QuestionTooltip.tsx b/builder-frontend/src/components/shared/QuestionTooltip.tsx index 17e74950..78eb652a 100644 --- a/builder-frontend/src/components/shared/QuestionTooltip.tsx +++ b/builder-frontend/src/components/shared/QuestionTooltip.tsx @@ -3,7 +3,6 @@ import QuestionMarkIcon from "../icon/QuestionMarkIcon"; interface TooltipProps { text: string; - iconClass?: string; children?: JSX.Element; } @@ -55,7 +54,7 @@ export default function QuestionTooltip(props: TooltipProps) { {props.children ? ( props.children ) : ( - + )} {isVisible() && ( From aa851726d8016ffd4631ef31d04f33fa400b58bc Mon Sep 17 00:00:00 2001 From: Javaughn Pryce Date: Sat, 28 Feb 2026 17:37:25 -0500 Subject: [PATCH 4/5] use SolidJS builtin Show component to conditionally render QuestionTooltip --- .../src/components/shared/QuestionTooltip.tsx | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/builder-frontend/src/components/shared/QuestionTooltip.tsx b/builder-frontend/src/components/shared/QuestionTooltip.tsx index 78eb652a..b783131f 100644 --- a/builder-frontend/src/components/shared/QuestionTooltip.tsx +++ b/builder-frontend/src/components/shared/QuestionTooltip.tsx @@ -1,4 +1,4 @@ -import { createSignal, JSX } from "solid-js"; +import { createSignal, JSX, Show } from "solid-js"; import QuestionMarkIcon from "../icon/QuestionMarkIcon"; interface TooltipProps { @@ -9,18 +9,18 @@ interface TooltipProps { export default function QuestionTooltip(props: TooltipProps) { const [isVisible, setIsVisible] = createSignal(false); const [position, setPosition] = createSignal<{ x: number, align: 'center' | 'left' | 'right' }>({ x: 0, align: 'center' }); - + let containerRef: HTMLDivElement | undefined; const handleMouseEnter = () => { if (containerRef) { const rect = containerRef.getBoundingClientRect(); 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 @@ -51,14 +51,12 @@ export default function QuestionTooltip(props: TooltipProps) { tabIndex={0} aria-label="More information" > - {props.children ? ( - props.children - ) : ( - - )} + }> + {props.children} + - {isVisible() && ( -
+
{props.text} {/* Decorative arrow pointing up */} -
- )} +
); -} +} \ No newline at end of file From 92e9b9b0f1c95f58208463105b3fdc522f669a5a Mon Sep 17 00:00:00 2001 From: Javaughn Pryce Date: Sat, 28 Feb 2026 18:04:09 -0500 Subject: [PATCH 5/5] refactor for readability --- .../src/components/shared/QuestionTooltip.tsx | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/builder-frontend/src/components/shared/QuestionTooltip.tsx b/builder-frontend/src/components/shared/QuestionTooltip.tsx index b783131f..27306ab0 100644 --- a/builder-frontend/src/components/shared/QuestionTooltip.tsx +++ b/builder-frontend/src/components/shared/QuestionTooltip.tsx @@ -1,6 +1,12 @@ 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; @@ -8,32 +14,37 @@ interface TooltipProps { export default function QuestionTooltip(props: TooltipProps) { const [isVisible, setIsVisible] = createSignal(false); - const [position, setPosition] = createSignal<{ x: number, align: 'center' | 'left' | 'right' }>({ x: 0, align: 'center' }); + const [position, setPosition] = createSignal<{ + x: number; + align: TooltipAlignment; + }>({ x: 0, align: TooltipAlignment.Center }); let containerRef: HTMLDivElement | undefined; - const handleMouseEnter = () => { - if (containerRef) { - const rect = containerRef.getBoundingClientRect(); - const tooltipWidth = 256; // w-64 is 16rem = 256px + 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; + // 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 + const viewportWidth = window.innerWidth; + const padding = 16; // 16px safety padding from screen edges - if (centerLeft < padding) { - // Overflow on the left: align left edge of tooltip with container - setPosition({ x: 0, align: 'left' }); - } else if (centerRight > viewportWidth - padding) { - // Overflow on the right: align right edge of tooltip with container - setPosition({ x: 0, align: 'right' }); - } else { - // Safe to center perfectly - setPosition({ x: 0, align: 'center' }); - } + 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(); + const align = determineAlignment(rect); + setPosition({ x: 0, align }); } setIsVisible(true); }; @@ -51,25 +62,34 @@ export default function QuestionTooltip(props: TooltipProps) { tabIndex={0} aria-label="More information" > - }> + + } + > {props.children}
{props.text} {/* Decorative arrow pointing up */}