PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.6.0
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.6.0
3.6.0 3.5.3 3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / src / style-customizer-v2 / HoverTip.tsx
everest-forms / src / style-customizer-v2 Last commit date
AiAssistant.tsx 2 days ago App.tsx 2 days ago BuilderSync.ts 2 days ago ControlRenderer.tsx 2 days ago HoverTip.tsx 2 days ago Popover.tsx 2 days ago PreviewBridge.ts 2 days ago PreviewPane.tsx 2 days ago constants.ts 2 days ago cssVars.ts 2 days ago globals.d.ts 2 days ago index.tsx 2 days ago panes.tsx 2 days ago store.ts 2 days ago style.scss 2 days ago types.ts 2 days ago
HoverTip.tsx
109 lines
1 /**
2 * Style Customizer v2 — hover/focus tooltip.
3 *
4 * A self-dismissing info bubble: opens on pointer-enter / keyboard focus of its trigger and
5 * closes on pointer-leave, blur, scroll, resize or Escape. Portaled into the panel root so it
6 * inherits the panel tokens and clears the sidebar's overflow, positioned against the trigger.
7 */
8 import React from 'react';
9 import { createPortal } from 'react-dom';
10
11 type Placement = 'top' | 'bottom';
12
13 function tipHost(): HTMLElement {
14 return document.getElementById( 'everest-forms-panel-style' ) || document.body;
15 }
16
17 export function HoverTip( {
18 tip,
19 label,
20 className,
21 children,
22 }: {
23 tip: React.ReactNode;
24 label: string;
25 className?: string;
26 children: React.ReactNode;
27 } ) {
28 const triggerRef = React.useRef< HTMLButtonElement >( null );
29 const tipRef = React.useRef< HTMLDivElement >( null );
30 const [ open, setOpen ] = React.useState( false );
31 const [ pos, setPos ] = React.useState< { left: number; top: number; placement: Placement } >( {
32 left: -9999,
33 top: -9999,
34 placement: 'bottom',
35 } );
36
37 React.useLayoutEffect( () => {
38 if ( ! open ) {
39 return;
40 }
41 const trigger = triggerRef.current;
42 const el = tipRef.current;
43 if ( ! trigger || ! el ) {
44 return;
45 }
46 const r = trigger.getBoundingClientRect();
47 const w = el.offsetWidth;
48 const h = el.offsetHeight;
49 let left = r.left + r.width / 2 - w / 2;
50 left = Math.min( Math.max( 8, left ), window.innerWidth - w - 8 );
51 let top = r.bottom + 8;
52 let placement: Placement = 'bottom';
53 if ( top + h > window.innerHeight - 8 ) {
54 top = r.top - h - 8;
55 placement = 'top';
56 }
57 setPos( { left, top: Math.max( 8, top ), placement } );
58 }, [ open, tip ] );
59
60 React.useEffect( () => {
61 if ( ! open ) {
62 return;
63 }
64 const close = () => setOpen( false );
65 const onKey = ( e: KeyboardEvent ) => {
66 if ( e.key === 'Escape' ) {
67 setOpen( false );
68 }
69 };
70 window.addEventListener( 'scroll', close, true );
71 window.addEventListener( 'resize', close );
72 document.addEventListener( 'keydown', onKey );
73 return () => {
74 window.removeEventListener( 'scroll', close, true );
75 window.removeEventListener( 'resize', close );
76 document.removeEventListener( 'keydown', onKey );
77 };
78 }, [ open ] );
79
80 return (
81 <>
82 <button
83 ref={ triggerRef }
84 type="button"
85 className={ className }
86 aria-label={ label }
87 onMouseEnter={ () => setOpen( true ) }
88 onMouseLeave={ () => setOpen( false ) }
89 onFocus={ () => setOpen( true ) }
90 onBlur={ () => setOpen( false ) }
91 >
92 { children }
93 </button>
94 { open &&
95 createPortal(
96 <div
97 ref={ tipRef }
98 className={ 'scv2-hovertip is-' + pos.placement }
99 role="tooltip"
100 style={ { left: pos.left, top: pos.top } }
101 >
102 { tip }
103 </div>,
104 tipHost()
105 ) }
106 </>
107 );
108 }
109