Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
2c4e0ee
chore: fieldlocation fetch completed
SahilCs15 Jun 29, 2025
26a70f4
chore: rendered the fieldmodifier apps added an event listner to send…
SahilCs15 Jun 30, 2025
c394b1f
chore: added test cases for the field location data
SahilCs15 Jul 1, 2025
bf2cc68
fix: undo unneccsary changes
SahilCs15 Jul 1, 2025
a0ff5b0
fix: resolved the issues
SahilCs15 Jul 3, 2025
000168e
feat: hovertoolbar component and rendering
csAyushDubey Jul 4, 2025
d9a0d09
chore: talismanrc update
csAyushDubey Jul 4, 2025
bc21aae
test: fieldlabelwrapper
csAyushDubey Jul 4, 2025
d2c1c70
Merge pull request #449 from contentstack/Field-modifiers-in-canvas
sairaj-cs Jul 7, 2025
88a81a4
redner the app on dom
SahilCs15 Jul 9, 2025
19f865a
Merge pull request #450 from contentstack/VE-6701-Hover-Toolbar-Support
csAyushDubey Jul 9, 2025
1cae0c3
fix: disable scroll when field modifer is active
SahilCs15 Jul 9, 2025
42c1576
feat: fieldLabelWrapper changes
csAyushDubey Jul 9, 2025
bab4171
fix: tooltip arrow change
csAyushDubey Jul 10, 2025
bfd84b7
Merge pull request #453 from contentstack/VE-6698-reference-map-sync
csAyushDubey Jul 10, 2025
8478bbc
fix: missed rendering case
csAyushDubey Jul 10, 2025
14ba432
fix:removed uncessary changes
SahilCs15 Jul 11, 2025
764fc0b
Merge branch 'render-field-modifier-in-canvas' into disable-scroll-wh…
SahilCs15 Jul 11, 2025
12be298
fix: addresed the requested cahnges
SahilCs15 Jul 14, 2025
51204af
chore: cleanup commit
csAyushDubey Jul 15, 2025
71f24d8
fix: tests
csAyushDubey Jul 15, 2025
8830f3c
chore: increased test timeout for CI
csAyushDubey Jul 15, 2025
eb2d72f
chore: incresed hook timeout for CI
csAyushDubey Jul 15, 2025
abfd640
chore: added retry to vitest
csAyushDubey Jul 15, 2025
da638dd
chore: increased timeout further
csAyushDubey Jul 15, 2025
cd63458
Merge pull request #458 from contentstack/disable-scroll-when-field-m…
SahilCs15 Jul 15, 2025
0d36fbc
Merge pull request #455 from contentstack/VE-6600-Hover-Toolbar-Support
csAyushDubey Jul 16, 2025
aa16fa2
Merge branch 'develop_v3' into VE-5474-Field-Modifier-support-for-can…
SahilCs15 Jul 16, 2025
c8e3a35
fix: fixed conficts
SahilCs15 Jul 16, 2025
435a028
fix: fixed the required changes
SahilCs15 Jul 16, 2025
37dccb8
fix: removed not requied changes
SahilCs15 Jul 16, 2025
366fc87
Merge pull request #460 from contentstack/VE-5474-Field-Modifier-supp…
SahilCs15 Jul 16, 2025
a80d08d
fix: no hovertoolbar when focussed
csAyushDubey Jul 16, 2025
69328d4
Merge pull request #461 from contentstack/VE-6600-Hover-Toolbar-Support
csAyushDubey Jul 16, 2025
cc71759
fix: changed order in tooltip and customcursor hiding
csAyushDubey Jul 18, 2025
565d56d
Merge pull request #464 from contentstack/VE-6600-Hover-Toolbar-Support
csAyushDubey Jul 18, 2025
b43682d
fix: modified the way we were handling fieldlocation data fetch (#462)
SahilCs15 Jul 18, 2025
ac4fdb3
fix: field parent clickability
csAyushDubey Jul 22, 2025
3b897a4
Merge pull request #467 from contentstack/VE-7005-hover-toolbar-click…
csAyushDubey Jul 22, 2025
ede9b92
Revert "fix: modified the way we were handling fieldlocation data fet…
sairajchouhan Jul 24, 2025
85224b1
Revert "fix: removed not requied changes"
sairajchouhan Jul 24, 2025
dc13ad4
Revert "fix: fixed the required changes"
sairajchouhan Jul 24, 2025
d7c56db
Revert "fix: fixed conficts"
sairajchouhan Jul 24, 2025
cb2fd29
Revert "fix: addresed the requested cahnges"
sairajchouhan Jul 24, 2025
242ebcb
Revert "fix:removed uncessary changes"
sairajchouhan Jul 24, 2025
31c0bd8
Revert "fix: disable scroll when field modifer is active"
sairajchouhan Jul 24, 2025
705c572
Revert "redner the app on dom"
sairajchouhan Jul 24, 2025
79b8225
Revert "fix: resolved the issues"
sairajchouhan Jul 24, 2025
f70131a
Revert "fix: undo unneccsary changes"
sairajchouhan Jul 24, 2025
7a92f1c
Revert "chore: added test cases for the field location data"
sairajchouhan Jul 24, 2025
e48a69d
Revert "chore: rendered the fieldmodifier apps added an event listner…
sairajchouhan Jul 24, 2025
9a364a1
Revert "chore: fieldlocation fetch completed"
sairajchouhan Jul 24, 2025
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
2 changes: 2 additions & 0 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ fileignoreconfig:
checksum: 3badd6a142456b6a361569e6fc546349a38ac6b366bef7fd5255d1e93220444e
- filename: src/visualBuilder/components/Collab/ThreadPopup/__test__/CommentTextArea.test.tsx
checksum: d0ef271ee5381d9feab06bda6e7e89bd0a882fee87495627bd811c1f0a5459c7
- filename: package-lock.json
checksum: fd06363871d0ee16ebfb5d9d0cc479e0922a615bb76584b80bb6933ee6c3e237
version: "1.0"
23 changes: 23 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"url": "https://github.com/contentstack/live-preview-sdk.git"
},
"dependencies": {
"@floating-ui/dom": "^1.7.2",
"@preact/compat": "17.1.2",
"@preact/signals": "1.2.2",
"classnames": "^2.5.1",
Expand Down
2 changes: 1 addition & 1 deletion src/__test__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function sleep(waitTimeInMs = 100): Promise<void> {
export const waitForHoverOutline = async () => {
await waitFor(() => {
const hoverOutline = document.querySelector(
"[data-testid='visual-builder__hover-outline']"
"[data-testid='visual-builder__hover-outline'][style]"
);
expect(hoverOutline).not.toBeNull();
});
Expand Down
184 changes: 184 additions & 0 deletions src/visualBuilder/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { h, cloneElement } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks';
import {
computePosition,
flip,
shift,
offset,
arrow
} from '@floating-ui/dom';
import { visualBuilderStyles } from '../visualBuilder.style';
import classNames from 'classnames';
import { ContentTypeIcon } from './icons';
import { FieldTypeIconsMap } from '../generators/generateCustomCursor';
interface TooltipProps {
children: JSX.Element;
content: JSX.Element;
placement?: 'top-start' | 'bottom-start' | 'left-start' | 'right-start';
}

/**
* A lightweight, reusable tooltip component for Preact powered by Floating UI.
*
* @param {object} props - The component props.
* @param {preact.ComponentChildren} props.children - The single child element that triggers the tooltip.
* @param {string | preact.VNode} props.content - The content to display inside the tooltip.
* @param {'top'|'bottom'|'left'|'right'} [props.placement='top'] - The desired placement of the tooltip.
*/
const Tooltip = ({ children, content, placement = 'top-start' }: TooltipProps) => {
const [isVisible, setIsVisible] = useState(false);
// Create refs for the trigger and the floating tooltip elements
const triggerRef = useRef(null);
const tooltipRef = useRef(null);
const arrowRef = useRef(null);

const showTooltip = () => setIsVisible(true);
const hideTooltip = () => setIsVisible(false);

// This effect calculates the tooltip's position whenever it becomes visible
// or if its content or placement changes.
useEffect(() => {
if (!isVisible || !triggerRef.current || !tooltipRef.current) {
return;
}

const trigger = triggerRef.current as HTMLElement;
const tooltip = tooltipRef.current as HTMLElement;

computePosition(trigger, tooltip, {
placement,
// Middleware runs in order to modify the position
middleware: [
offset(8), // Add 8px of space between the trigger and tooltip
flip(), // Flip to the opposite side if it overflows
shift({ padding: 5 }), // Shift to keep it in view
...(arrowRef.current ? [arrow({ element: arrowRef.current as HTMLElement })] : []), // Handle arrow positioning
],
}).then(({ x, y, placement, middlewareData }) => {
// Apply the calculated coordinates to the tooltip element
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});

// Position the arrow element
if (middlewareData.arrow && arrowRef.current) {
const { x: arrowX, y: arrowY } = middlewareData.arrow;
const side = placement.split('-')[0];
const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right',
}[side] as string;

const arrowElement = arrowRef.current as HTMLElement;

// Reset all positioning properties
Object.assign(arrowElement.style, {
left: '',
top: '',
right: '',
bottom: '',
});

// For placements like top-start, bottom-start, etc., we want the arrow
// to be centered on the tooltip rather than pointing at the trigger center
if (placement.includes('-start') || placement.includes('-end')) {
const tooltipRect = tooltip.getBoundingClientRect();

if (side === 'top' || side === 'bottom') {
// For top/bottom placements, center the arrow horizontally
arrowElement.style.left = `${14}px`; // 4px = half arrow width
if (arrowY != null) {
arrowElement.style.top = `${arrowY}px`;
}
} else {
// For left/right placements, center the arrow vertically
arrowElement.style.top = `${tooltipRect.height / 2 - 4}px`; // 4px = half arrow height
if (arrowX != null) {
arrowElement.style.left = `${arrowX}px`;
}
}
} else {
// For regular placements (top, bottom, left, right), use floating-ui's positioning
if (arrowX != null) {
arrowElement.style.left = `${arrowX}px`;
}
if (arrowY != null) {
arrowElement.style.top = `${arrowY}px`;
}
}

// Position arrow to overlap the tooltip's border
(arrowElement.style as any)[staticSide] = '-4px';
}
});

}, [isVisible, placement, content]);

// We need to clone the child element to attach our ref and event listeners.
// This ensures we don't wrap the child in an extra <div>.
const triggerWithListeners = cloneElement(children, {
ref: triggerRef,
onMouseEnter: showTooltip,
onMouseLeave: hideTooltip,
onFocus: showTooltip,
onBlur: hideTooltip,
'aria-describedby': 'lightweight-tooltip' // for accessibility
});

return (
<>
{triggerWithListeners}
{isVisible && (
<div
ref={tooltipRef}
role="tooltip"
id="lightweight-tooltip"
className={classNames("tooltip-container", visualBuilderStyles()["tooltip-container"])}
>
{content}
<div ref={arrowRef} className={classNames("tooltip-arrow", visualBuilderStyles()["tooltip-arrow"])}></div>
</div>
)}
</>
);
};

function ToolbarTooltipContent({contentTypeName, referenceFieldName}: {contentTypeName: string, referenceFieldName: string}) {
return (
<div className={classNames("toolbar-tooltip-content", visualBuilderStyles()["toolbar-tooltip-content"])}>
{
contentTypeName && (
<div className={classNames("toolbar-tooltip-content-item", visualBuilderStyles()["toolbar-tooltip-content-item"])}>
<ContentTypeIcon />
<p>{contentTypeName}</p>
</div>
)
}
{
referenceFieldName && (
<div className={classNames("toolbar-tooltip-content-item", visualBuilderStyles()["toolbar-tooltip-content-item"])}>
<div dangerouslySetInnerHTML={{__html: FieldTypeIconsMap.reference}} className={classNames("visual-builder__field-icon", visualBuilderStyles()["visual-builder__field-icon"])}/>
<p>{referenceFieldName}</p>
</div>
)
}
</div>
)
}

export function ToolbarTooltip({children, data, disabled = false}: {children: JSX.Element, data: {contentTypeName: string, referenceFieldName: string}, disabled?: boolean}) {
if (disabled) {
return children;
}
const { contentTypeName, referenceFieldName } = data;
return (
<Tooltip content={<ToolbarTooltipContent contentTypeName={contentTypeName} referenceFieldName={referenceFieldName} />}>
{children}
</Tooltip>
)
}

export default Tooltip;
Loading
Loading