qbs-react-grid 2.2.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,88 +1,149 @@
1
- // import React, { useRef, useState } from 'react';
2
-
3
- // const TooltipComponent: React.FC<any> = ({ title, children, tableBodyRef }) => {
4
- // const [dropdownPosition, setDropdownPosition] = useState('bottom-position');
5
- // const dropRef = useRef(null);
6
- // const menuButtonRef = useRef<HTMLElement>(null);
7
- // const adjustDropdownPosition = () => {
8
- // if (menuButtonRef.current && tableBodyRef?.current) {
9
- // const inputBoxRect = menuButtonRef.current?.getBoundingClientRect();
10
- // const tableRect = tableBodyRef.current.getBoundingClientRect();
11
- // // Calculate positions relative to the table
12
- // const spaceAbove = inputBoxRect.top - tableRect.top;
13
- // const spaceBelow = tableRect.bottom - inputBoxRect.bottom;
14
-
15
- // if (spaceAbove > spaceBelow) {
16
- // setDropdownPosition('top-position');
17
- // } else {
18
- // setDropdownPosition('bottom-position');
19
- // }
20
- // }
21
- // };
22
-
23
- // return (
24
- // <div className={`qbs-table-tooltip ${dropdownPosition == 'bottom-position' ? 'down' : 'up'} `}>
25
- // <span
26
- // ref={menuButtonRef}
27
- // style={{ display: 'flex' }}
28
- // onMouseEnter={() => adjustDropdownPosition()}
29
- // >
30
- // {children}
31
- // </span>
32
- // <span ref={dropRef} className={'tooltiptext'}>
33
- // {title}
34
- // </span>
35
- // </div>
36
- // );
37
- // };
38
-
39
- // export default TooltipComponent;
40
- import React, { useRef, useState } from 'react';
41
-
42
- const TooltipComponent: React.FC<any> = ({ title, children, tableBodyRef }) => {
43
- const [dropdownPosition, setDropdownPosition] = useState<'up' | 'down'>('down');
44
- const menuButtonRef = useRef<HTMLElement>(null);
45
-
46
- const adjustDropdownPosition = () => {
47
- if (menuButtonRef.current && tableBodyRef?.current) {
48
- const triggerRect = menuButtonRef.current.getBoundingClientRect();
49
- const tableRect = tableBodyRef.current.getBoundingClientRect();
50
-
51
- const spaceAbove = triggerRect.top - tableRect.top;
52
- const spaceBelow = tableRect.bottom - triggerRect.bottom;
53
-
54
- setDropdownPosition(spaceAbove > spaceBelow ? 'up' : 'down');
1
+ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ type TooltipComponentProps = {
5
+ title?: React.ReactNode;
6
+ children: React.ReactNode;
7
+ tableBodyRef?: React.RefObject<HTMLDivElement | null>;
8
+ /** When false, renders children only (no tooltip). */
9
+ enabled?: boolean;
10
+ };
11
+
12
+ const VIEWPORT_PADDING = 8;
13
+ const TOOLTIP_GAP = 8;
14
+
15
+ const TooltipComponent: React.FC<TooltipComponentProps> = ({
16
+ title,
17
+ children,
18
+ tableBodyRef,
19
+ enabled = true,
20
+ }) => {
21
+ const [visible, setVisible] = useState(false);
22
+ const [positioned, setPositioned] = useState(false);
23
+ const [placement, setPlacement] = useState<'up' | 'down'>('down');
24
+ const [coords, setCoords] = useState({ top: 0, left: 0 });
25
+ const [arrowOffset, setArrowOffset] = useState(0);
26
+ const triggerRef = useRef<HTMLSpanElement>(null);
27
+ const tooltipRef = useRef<HTMLSpanElement>(null);
28
+
29
+ const updatePosition = useCallback(() => {
30
+ const trigger = triggerRef.current;
31
+ const tooltip = tooltipRef.current;
32
+ if (!trigger || !tooltip) {
33
+ return;
34
+ }
35
+
36
+ const triggerRect = trigger.getBoundingClientRect();
37
+ const tooltipRect = tooltip.getBoundingClientRect();
38
+ const boundaryRect =
39
+ tableBodyRef?.current?.getBoundingClientRect() ??
40
+ trigger.closest('.qbs-table')?.getBoundingClientRect();
41
+
42
+ const spaceAbove = boundaryRect
43
+ ? triggerRect.top - boundaryRect.top
44
+ : triggerRect.top;
45
+ const spaceBelow = boundaryRect
46
+ ? boundaryRect.bottom - triggerRect.bottom
47
+ : window.innerHeight - triggerRect.bottom;
48
+
49
+ const nextPlacement =
50
+ spaceBelow >= tooltipRect.height + TOOLTIP_GAP || spaceBelow >= spaceAbove ? 'down' : 'up';
51
+
52
+ const triggerCenter = triggerRect.left + triggerRect.width / 2;
53
+ let left = triggerCenter - tooltipRect.width / 2;
54
+
55
+ if (left < VIEWPORT_PADDING) {
56
+ left = VIEWPORT_PADDING;
57
+ } else if (left + tooltipRect.width > window.innerWidth - VIEWPORT_PADDING) {
58
+ left = window.innerWidth - VIEWPORT_PADDING - tooltipRect.width;
55
59
  }
60
+
61
+ const top =
62
+ nextPlacement === 'down'
63
+ ? triggerRect.bottom + TOOLTIP_GAP
64
+ : triggerRect.top - tooltipRect.height - TOOLTIP_GAP;
65
+
66
+ setPlacement(nextPlacement);
67
+ setCoords({ top, left });
68
+ setArrowOffset(triggerCenter - left);
69
+ setPositioned(true);
70
+ }, [tableBodyRef]);
71
+
72
+ const showTooltip = () => {
73
+ setPositioned(false);
74
+ setVisible(true);
75
+ };
76
+
77
+ const hideTooltip = () => {
78
+ setVisible(false);
79
+ setPositioned(false);
56
80
  };
57
81
 
82
+ useLayoutEffect(() => {
83
+ if (!visible) {
84
+ return;
85
+ }
86
+
87
+ updatePosition();
88
+ const frame = window.requestAnimationFrame(updatePosition);
89
+ return () => window.cancelAnimationFrame(frame);
90
+ }, [visible, title, updatePosition]);
91
+
92
+ useEffect(() => {
93
+ if (!visible) {
94
+ return;
95
+ }
96
+
97
+ const handleReposition = () => updatePosition();
98
+ window.addEventListener('resize', handleReposition);
99
+ window.addEventListener('scroll', handleReposition, true);
100
+ return () => {
101
+ window.removeEventListener('resize', handleReposition);
102
+ window.removeEventListener('scroll', handleReposition, true);
103
+ };
104
+ }, [visible, updatePosition]);
105
+
106
+ if (!title || enabled === false) {
107
+ return <>{children}</>;
108
+ }
109
+
110
+ const portalRoot = typeof document !== 'undefined' ? document.body : null;
111
+
58
112
  return (
59
- <div
60
- className={`qbs-table-tooltip ${dropdownPosition}`}
61
- onMouseEnter={adjustDropdownPosition}
62
- onMouseLeave={() => {
63
- const tooltip = menuButtonRef?.current?.querySelector('.tooltiptext') as HTMLElement;
64
- if (tooltip) {
65
- tooltip.style.visibility = 'hidden';
66
- tooltip.style.opacity = '0';
67
- }
68
- }}
69
- >
113
+ <>
70
114
  <span
71
- ref={menuButtonRef}
72
- style={{ display: 'flex' }}
73
- onMouseEnter={() => {
74
- adjustDropdownPosition();
75
- const tooltip = menuButtonRef?.current?.querySelector('.tooltiptext') as HTMLElement;
76
- if (tooltip) {
77
- tooltip.style.visibility = 'visible';
78
- tooltip.style.opacity = '1';
79
- }
80
- }}
115
+ ref={triggerRef}
116
+ className="qbs-table-tooltip-trigger"
117
+ style={{ display: 'inline-flex' }}
118
+ onMouseEnter={showTooltip}
119
+ onMouseLeave={hideTooltip}
120
+ onFocus={showTooltip}
121
+ onBlur={hideTooltip}
81
122
  >
82
123
  {children}
83
- <span className="tooltiptext">{title}</span>
84
124
  </span>
85
- </div>
125
+ {visible &&
126
+ portalRoot &&
127
+ ReactDOM.createPortal(
128
+ <span
129
+ ref={tooltipRef}
130
+ role="tooltip"
131
+ className={`qbs-table-tooltip-floating tooltiptext qbs-table-tooltip-floating--${placement} ${
132
+ positioned ? 'is-positioned' : ''
133
+ }`}
134
+ style={
135
+ {
136
+ top: coords.top,
137
+ left: coords.left,
138
+ '--tooltip-arrow-offset': `${arrowOffset}px`,
139
+ } as React.CSSProperties
140
+ }
141
+ >
142
+ {title}
143
+ </span>,
144
+ portalRoot,
145
+ )}
146
+ </>
86
147
  );
87
148
  };
88
149
 
@@ -132,7 +132,7 @@ const VerticalMenuDropdown: React.FC<Props> = ({
132
132
  const portalTarget = document.getElementById('portal-root');
133
133
  const dropdownContent = (
134
134
  <div
135
- className="absolute z-50 min-w-48 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 vertical-menu-dropdown-content"
135
+ className="absolute z-[60] min-w-48 rounded-md vertical-menu-dropdown-content"
136
136
  ref={menuRef}
137
137
  style={{
138
138
  width: 200,
@@ -146,7 +146,7 @@ const VerticalMenuDropdown: React.FC<Props> = ({
146
146
  !item?.hidden && !item?.hide?.(rowData, rowIndex) ? (
147
147
  <div
148
148
  key={item.title}
149
- className="vertical-menu-item px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer flex items-center gap-2 transition-colors"
149
+ className="vertical-menu-item px-4 py-2 text-sm text-base-black hover:bg-gray-light-1 cursor-pointer flex items-center gap-2 transition-colors"
150
150
  onClick={e => {
151
151
  e.preventDefault();
152
152
  item.action?.(item);
@@ -171,7 +171,7 @@ const VerticalMenuDropdown: React.FC<Props> = ({
171
171
  <div className="inline-block vertical-menu-dropdown-wrapper">
172
172
  {handleShowHideMenu() > 0 && (
173
173
  <button
174
- className="vertical-menu-trigger-button p-2 rounded hover:bg-gray-100 transition-colors"
174
+ className="vertical-menu-trigger-button p-2 rounded text-base-gray hover:bg-gray-light-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
175
175
  onClick={toggleMenu}
176
176
  ref={menuButtonRef}
177
177
  >
@@ -5,7 +5,7 @@ export const ThreeDotIcon: React.FC<any> = () => {
5
5
  <svg width="4" height="16" viewBox="0 0 4 16" fill="none" xmlns="http://www.w3.org/2000/svg">
6
6
  <path
7
7
  d="M2 2.16665L2 2.17498M2 7.99998L2 8.00831M2 13.8333L2 13.8416M2 2.99998C1.53976 2.99998 1.16667 2.62688 1.16667 2.16665C1.16667 1.70641 1.53976 1.33331 2 1.33331C2.46024 1.33331 2.83333 1.70641 2.83333 2.16665C2.83333 2.62688 2.46024 2.99998 2 2.99998ZM2 8.83331C1.53976 8.83331 1.16667 8.46022 1.16667 7.99998C1.16667 7.53974 1.53976 7.16665 2 7.16665C2.46024 7.16665 2.83333 7.53974 2.83333 7.99998C2.83333 8.46022 2.46024 8.83331 2 8.83331ZM2 14.6666C1.53976 14.6666 1.16666 14.2935 1.16666 13.8333C1.16666 13.3731 1.53976 13 2 13C2.46024 13 2.83333 13.3731 2.83333 13.8333C2.83333 14.2935 2.46024 14.6666 2 14.6666Z"
8
- stroke="#313131"
8
+ stroke="currentColor"
9
9
  strokeWidth="1.5"
10
10
  strokeLinecap="round"
11
11
  strokeLinejoin="round"