qbs-react-grid 2.2.5 → 2.2.6

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.
@@ -0,0 +1,193 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ import { ThreeDotIcon } from './icons';
5
+ import TooltipComponent from './ToolTip';
6
+ import type { ActionProps } from '../commontypes';
7
+
8
+ type VerticalMenuDropdownProps = {
9
+ actionDropDown?: readonly ActionProps[];
10
+ handleMenuActions?: (actions: ActionProps, rowData: any) => void;
11
+ rowData: any;
12
+ tableBodyRef?: React.RefObject<HTMLDivElement>;
13
+ rowIndex?: number;
14
+ };
15
+
16
+ const VerticalMenuDropdown: React.FC<VerticalMenuDropdownProps> = ({
17
+ actionDropDown,
18
+ handleMenuActions,
19
+ rowData,
20
+ tableBodyRef,
21
+ rowIndex,
22
+ }) => {
23
+ const [openMenu, setOpenMenu] = useState(false);
24
+ const [position, setPosition] = useState({ top: 0, left: 0 });
25
+ const menuButtonRef = useRef<HTMLButtonElement>(null);
26
+ const menuRef = useRef<HTMLDivElement>(null);
27
+
28
+ const updateMenuPosition = () => {
29
+ if (!menuButtonRef.current) return;
30
+
31
+ const rect = menuButtonRef.current.getBoundingClientRect();
32
+ const viewportPadding = 8;
33
+ const menuGap = 4;
34
+ const visibleItems =
35
+ actionDropDown?.filter(
36
+ item =>
37
+ !item.hidden && !(item.hide?.call(item, rowData, rowIndex) ?? false),
38
+ ) ?? [];
39
+ const menuWidth =
40
+ menuRef.current && menuRef.current.offsetWidth > 0
41
+ ? menuRef.current.offsetWidth
42
+ : Math.max(120, visibleItems.length * 48);
43
+ const menuHeight = visibleItems.length * 40;
44
+ const spaceBelow = window.innerHeight - rect.bottom;
45
+ const openBelow = spaceBelow >= menuHeight + menuGap;
46
+
47
+ let left = rect.left;
48
+ if (left + menuWidth > window.innerWidth - viewportPadding) {
49
+ left = Math.max(viewportPadding, rect.right - menuWidth);
50
+ }
51
+
52
+ setPosition({
53
+ top: openBelow ? rect.bottom + menuGap : rect.top - menuHeight - menuGap,
54
+ left,
55
+ });
56
+ };
57
+
58
+ useEffect(() => {
59
+ if (!openMenu) return;
60
+ updateMenuPosition();
61
+ const frame = requestAnimationFrame(() => updateMenuPosition());
62
+ const resizeObserver =
63
+ menuRef.current && typeof ResizeObserver !== 'undefined'
64
+ ? new ResizeObserver(() => updateMenuPosition())
65
+ : null;
66
+ if (resizeObserver && menuRef.current) {
67
+ resizeObserver.observe(menuRef.current);
68
+ }
69
+ return () => {
70
+ cancelAnimationFrame(frame);
71
+ resizeObserver?.disconnect();
72
+ };
73
+ }, [openMenu]);
74
+
75
+ useEffect(() => {
76
+ if (!openMenu) return;
77
+
78
+ const handleClickOutside = (event: MouseEvent) => {
79
+ const target = event.target as Node;
80
+ if (
81
+ menuRef.current?.contains(target) ||
82
+ menuButtonRef.current?.contains(target)
83
+ ) {
84
+ return;
85
+ }
86
+ setOpenMenu(false);
87
+ };
88
+ const handleScroll = () => setOpenMenu(false);
89
+
90
+ document.addEventListener('click', handleClickOutside);
91
+ window.addEventListener('scroll', handleScroll, true);
92
+
93
+ return () => {
94
+ document.removeEventListener('click', handleClickOutside);
95
+ window.removeEventListener('scroll', handleScroll, true);
96
+ };
97
+ }, [openMenu]);
98
+
99
+ useEffect(() => {
100
+ const scrollbarHandle = document.querySelector('.rs-table-scrollbar-handle');
101
+ if (!scrollbarHandle) return;
102
+
103
+ const observer = new MutationObserver(mutations => {
104
+ for (const mutation of mutations) {
105
+ if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
106
+ setOpenMenu(false);
107
+ }
108
+ }
109
+ });
110
+
111
+ observer.observe(scrollbarHandle, {
112
+ attributes: true,
113
+ attributeFilter: ['style'],
114
+ });
115
+
116
+ return () => observer.disconnect();
117
+ }, [openMenu]);
118
+
119
+ const handleMenuItemClick = (slug: ActionProps) => {
120
+ handleMenuActions?.(slug, rowData);
121
+ slug.action?.(rowData);
122
+ setOpenMenu(false);
123
+ };
124
+
125
+ const visibleCount =
126
+ actionDropDown?.filter(
127
+ item => !item.hidden && !(item.hide?.call(item, rowData, rowIndex) ?? false),
128
+ ).length ?? 0;
129
+
130
+ const portalTarget =
131
+ document.getElementById('portal-root') ?? document.body;
132
+
133
+ const dropdownContent = (
134
+ <div
135
+ className="absolute z-[60] min-w-48 rounded-md vertical-menu-dropdown-content"
136
+ ref={menuRef}
137
+ style={{
138
+ top: position.top,
139
+ left: position.left,
140
+ position: 'fixed',
141
+ minWidth: 120,
142
+ width: 'max-content',
143
+ }}
144
+ >
145
+ <div className="py-1">
146
+ {actionDropDown?.map(item =>
147
+ !item.hidden && !(item.hide?.call(item, rowData, rowIndex) ?? false) ? (
148
+ <div
149
+ key={item.title}
150
+ 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"
151
+ onClick={e => {
152
+ e.preventDefault();
153
+ item.action?.(item);
154
+ handleMenuItemClick(item);
155
+ }}
156
+ >
157
+ <TooltipComponent title={item.toolTip} tableBodyRef={tableBodyRef}>
158
+ <div className="vertical-menu-icon-title flex items-center gap-2">
159
+ {item.icon && <span className="vertical-menu-icon">{item.icon}</span>}
160
+ <span className="vertical-menu-title">{item.title}</span>
161
+ </div>
162
+ </TooltipComponent>
163
+ </div>
164
+ ) : null,
165
+ )}
166
+ </div>
167
+ </div>
168
+ );
169
+
170
+ return (
171
+ <>
172
+ <div className="inline-block vertical-menu-dropdown-wrapper">
173
+ {visibleCount > 0 && (
174
+ <button
175
+ type="button"
176
+ 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"
177
+ onClick={event => {
178
+ event.stopPropagation();
179
+ if (!openMenu) updateMenuPosition();
180
+ setOpenMenu(prev => !prev);
181
+ }}
182
+ ref={menuButtonRef}
183
+ >
184
+ <ThreeDotIcon />
185
+ </button>
186
+ )}
187
+ </div>
188
+ {openMenu && portalTarget && ReactDOM.createPortal(dropdownContent, portalTarget)}
189
+ </>
190
+ );
191
+ };
192
+
193
+ export default VerticalMenuDropdown;