@addev-be/ui 3.3.0 → 3.3.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.
Files changed (234) hide show
  1. package/assets/icons/arrow-down-1-9.svg +1 -1
  2. package/assets/icons/arrow-down-a-z.svg +1 -1
  3. package/assets/icons/arrow-up-z-a.svg +1 -1
  4. package/assets/icons/check.svg +1 -1
  5. package/assets/icons/circle-check.svg +1 -1
  6. package/assets/icons/down.svg +1 -1
  7. package/assets/icons/filter-full.svg +1 -1
  8. package/assets/icons/filter.svg +1 -1
  9. package/assets/icons/hashtag.svg +1 -1
  10. package/assets/icons/image-slash.svg +1 -1
  11. package/assets/icons/left.svg +1 -1
  12. package/assets/icons/magnifier.svg +1 -1
  13. package/assets/icons/phone.svg +1 -1
  14. package/assets/icons/right.svg +1 -1
  15. package/assets/icons/sort-calendar-ascending.svg +5 -5
  16. package/assets/icons/spinner-third.svg +1 -1
  17. package/assets/icons/table-columns.svg +1 -1
  18. package/assets/icons/table-footer-slash.svg +4 -4
  19. package/assets/icons/table-footer.svg +3 -3
  20. package/assets/icons/table.svg +1 -1
  21. package/assets/icons/up.svg +1 -1
  22. package/assets/icons/user-tie.svg +1 -1
  23. package/assets/icons/x-bar.svg +3 -3
  24. package/dist/components/grid/DataGrid/helpers/columns.js +22 -2
  25. package/dist/components/grid/DataGrid/types.d.ts +2 -7
  26. package/dist/components/grid/SqlRequestDataGrid/helpers/columns.js +13 -1
  27. package/dist/helpers/styled/typography.d.ts +5 -5
  28. package/dist/helpers/styled/typography.js +5 -5
  29. package/eslint.config.js +3 -3
  30. package/package.json +2 -2
  31. package/src/components/auth/LoginForm.tsx +92 -92
  32. package/src/components/auth/LoginPage.tsx +32 -32
  33. package/src/components/auth/PasswordRecoveryForm.tsx +53 -53
  34. package/src/components/auth/PasswordResetForm.tsx +114 -114
  35. package/src/components/auth/index.ts +4 -4
  36. package/src/components/auth/styles.ts +14 -14
  37. package/src/components/common/Accordion/Accordion.tsx +140 -140
  38. package/src/components/common/Accordion/index.ts +2 -2
  39. package/src/components/common/Avatar/index.tsx +54 -54
  40. package/src/components/common/Avatar/styles.ts +61 -61
  41. package/src/components/common/Card/index.tsx +17 -17
  42. package/src/components/common/Card/styles.ts +38 -38
  43. package/src/components/common/ContextMenu/index.tsx +79 -79
  44. package/src/components/common/ContextMenu/styles.ts +119 -119
  45. package/src/components/common/Ellipsis.tsx +33 -33
  46. package/src/components/common/Label.tsx +93 -93
  47. package/src/components/common/Message/index.tsx +57 -57
  48. package/src/components/common/Message/styles.ts +44 -44
  49. package/src/components/common/PropertiesList/PropertyInput.tsx +173 -173
  50. package/src/components/common/PropertiesList/index.tsx +48 -48
  51. package/src/components/common/PropertiesList/types.ts +60 -60
  52. package/src/components/common/TabsView/TabsList.tsx +49 -49
  53. package/src/components/common/TabsView/TabsView.tsx +42 -42
  54. package/src/components/common/TabsView/index.ts +3 -3
  55. package/src/components/common/TabsView/styles.ts +83 -83
  56. package/src/components/common/TabsView/types.ts +12 -12
  57. package/src/components/common/TreeView/TreeContext.tsx +379 -379
  58. package/src/components/common/TreeView/TreeView.tsx +281 -281
  59. package/src/components/common/TreeView/index.ts +3 -3
  60. package/src/components/common/index.ts +10 -10
  61. package/src/components/forms/AutoTextArea.tsx +48 -48
  62. package/src/components/forms/BillitIdentifier/index.tsx +78 -78
  63. package/src/components/forms/BillitIdentifier/styles.tsx +43 -43
  64. package/src/components/forms/Button.tsx +130 -130
  65. package/src/components/forms/Form/Checkbox.tsx +12 -12
  66. package/src/components/forms/Form/CustomSelect.tsx +86 -86
  67. package/src/components/forms/Form/FormGroup.tsx +33 -33
  68. package/src/components/forms/Form/Input.tsx +16 -16
  69. package/src/components/forms/Form/Row.tsx +28 -28
  70. package/src/components/forms/Form/Select.tsx +99 -99
  71. package/src/components/forms/Form/TextArea.tsx +17 -17
  72. package/src/components/forms/Form/index.tsx +48 -48
  73. package/src/components/forms/Form/styles.ts +148 -148
  74. package/src/components/forms/IconButton.tsx +61 -61
  75. package/src/components/forms/IndeterminateCheckbox.tsx +46 -46
  76. package/src/components/forms/NumberInput.tsx +53 -53
  77. package/src/components/forms/Select.tsx +34 -34
  78. package/src/components/forms/VerticalLabel.tsx +20 -20
  79. package/src/components/forms/index.ts +11 -11
  80. package/src/components/forms/styles.ts +42 -42
  81. package/src/components/grid/DataGrid/DataGridCell.tsx +82 -82
  82. package/src/components/grid/DataGrid/DataGridColumnsModal/helpers.ts +9 -9
  83. package/src/components/grid/DataGrid/DataGridColumnsModal/hooks.tsx +59 -59
  84. package/src/components/grid/DataGrid/DataGridColumnsModal/index.tsx +182 -182
  85. package/src/components/grid/DataGrid/DataGridColumnsModal/styles.ts +104 -104
  86. package/src/components/grid/DataGrid/DataGridEditableCell/CheckboxEditableCell.tsx +37 -37
  87. package/src/components/grid/DataGrid/DataGridEditableCell/DateEditableCell.tsx +38 -38
  88. package/src/components/grid/DataGrid/DataGridEditableCell/NumberEditableCell.tsx +71 -71
  89. package/src/components/grid/DataGrid/DataGridEditableCell/TextEditableCell.tsx +37 -37
  90. package/src/components/grid/DataGrid/DataGridEditableCell/index.tsx +112 -112
  91. package/src/components/grid/DataGrid/DataGridEditableCell/styles.ts +35 -35
  92. package/src/components/grid/DataGrid/DataGridEditableCell/types.ts +18 -18
  93. package/src/components/grid/DataGrid/DataGridFilterMenu/FilterValuesScroller.tsx +129 -129
  94. package/src/components/grid/DataGrid/DataGridFilterMenu/helpers.ts +23 -23
  95. package/src/components/grid/DataGrid/DataGridFilterMenu/hooks.tsx +81 -81
  96. package/src/components/grid/DataGrid/DataGridFilterMenu/index.tsx +370 -370
  97. package/src/components/grid/DataGrid/DataGridFilterMenu/styles.ts +97 -97
  98. package/src/components/grid/DataGrid/DataGridFooter.tsx +48 -48
  99. package/src/components/grid/DataGrid/DataGridHeader.tsx +74 -74
  100. package/src/components/grid/DataGrid/DataGridHeaderCell.tsx +112 -112
  101. package/src/components/grid/DataGrid/DataGridRowTemplate.tsx +90 -90
  102. package/src/components/grid/DataGrid/DataGridToolbar.tsx +134 -134
  103. package/src/components/grid/DataGrid/FilterModalContent/index.tsx +137 -137
  104. package/src/components/grid/DataGrid/FilterModalContent/styles.ts +22 -22
  105. package/src/components/grid/DataGrid/constants.ts +6 -6
  106. package/src/components/grid/DataGrid/helpers/columns.tsx +458 -452
  107. package/src/components/grid/DataGrid/helpers/filters.ts +287 -287
  108. package/src/components/grid/DataGrid/helpers/index.ts +2 -2
  109. package/src/components/grid/DataGrid/hooks/index.ts +29 -29
  110. package/src/components/grid/DataGrid/hooks/useDataGrid.tsx +383 -383
  111. package/src/components/grid/DataGrid/hooks/useDataGridChangedRows.ts +97 -97
  112. package/src/components/grid/DataGrid/hooks/useDataGridCopy.ts +174 -174
  113. package/src/components/grid/DataGrid/hooks/useDataGridSettings.ts +48 -48
  114. package/src/components/grid/DataGrid/hooks/useRefreshModal.tsx +48 -48
  115. package/src/components/grid/DataGrid/index.tsx +111 -111
  116. package/src/components/grid/DataGrid/styles.ts +446 -446
  117. package/src/components/grid/DataGrid/types.ts +375 -380
  118. package/src/components/grid/SqlRequestDataGrid/helpers/columns.tsx +528 -526
  119. package/src/components/grid/SqlRequestDataGrid/helpers/index.ts +2 -2
  120. package/src/components/grid/SqlRequestDataGrid/helpers/rows.ts +24 -24
  121. package/src/components/grid/SqlRequestDataGrid/helpers/sqlRequests.ts +17 -17
  122. package/src/components/grid/SqlRequestDataGrid/index.tsx +417 -417
  123. package/src/components/grid/SqlRequestDataGrid/styles.ts +15 -15
  124. package/src/components/grid/SqlRequestDataGrid/types.ts +74 -74
  125. package/src/components/grid/SqlRequestForeignList/index.tsx +254 -254
  126. package/src/components/grid/SqlRequestForeignList/styles.ts +43 -43
  127. package/src/components/grid/SqlRequestForeignList/types.ts +32 -32
  128. package/src/components/grid/SqlRequestGrid/filters/FiltersSidebar.tsx +108 -108
  129. package/src/components/grid/SqlRequestGrid/filters/styles.ts +88 -88
  130. package/src/components/grid/SqlRequestGrid/helpers/index.ts +1 -1
  131. package/src/components/grid/SqlRequestGrid/helpers/sqlRequests.ts +16 -16
  132. package/src/components/grid/SqlRequestGrid/index.tsx +304 -304
  133. package/src/components/grid/SqlRequestGrid/styles.ts +20 -20
  134. package/src/components/grid/SqlRequestGrid/types.ts +73 -73
  135. package/src/components/grid/VirtualScroller/hooks.ts +71 -71
  136. package/src/components/grid/VirtualScroller/index.tsx +89 -89
  137. package/src/components/grid/VirtualScroller/styles.ts +57 -57
  138. package/src/components/grid/VirtualScroller/types.ts +10 -10
  139. package/src/components/grid/index.ts +11 -11
  140. package/src/components/layout/Columns.ts +28 -28
  141. package/src/components/layout/ContextMenu/ContextMenu.tsx +471 -471
  142. package/src/components/layout/ContextMenu/index.ts +23 -23
  143. package/src/components/layout/Dropdown/index.tsx +113 -113
  144. package/src/components/layout/Dropdown/styles.ts +53 -53
  145. package/src/components/layout/Flexbox.ts +21 -21
  146. package/src/components/layout/Grid/index.tsx +8 -8
  147. package/src/components/layout/Grid/styles.ts +34 -34
  148. package/src/components/layout/LeftMenu/LeftMenu.tsx +89 -89
  149. package/src/components/layout/LeftMenu/index.ts +2 -2
  150. package/src/components/layout/Loading/index.tsx +29 -29
  151. package/src/components/layout/Loading/styles.ts +29 -29
  152. package/src/components/layout/Masonry/index.tsx +29 -29
  153. package/src/components/layout/Masonry/styles.ts +20 -20
  154. package/src/components/layout/Modal/index.tsx +51 -51
  155. package/src/components/layout/Modal/styles.ts +125 -125
  156. package/src/components/layout/PanelContainer/PanelContainer.tsx +198 -198
  157. package/src/components/layout/PanelContainer/index.ts +2 -2
  158. package/src/components/layout/index.ts +10 -10
  159. package/src/components/search/HighlightedText.tsx +41 -41
  160. package/src/components/search/QuickSearchBar.tsx +125 -125
  161. package/src/components/search/QuickSearchResults.tsx +90 -90
  162. package/src/components/search/index.ts +5 -5
  163. package/src/components/search/styles.ts +96 -96
  164. package/src/components/search/types.ts +29 -29
  165. package/src/config/index.ts +10 -10
  166. package/src/helpers/cn.ts +6 -6
  167. package/src/helpers/components.ts +9 -9
  168. package/src/helpers/dates.ts +17 -17
  169. package/src/helpers/getScrollbarSize.ts +14 -14
  170. package/src/helpers/index.ts +8 -8
  171. package/src/helpers/numbers.ts +63 -63
  172. package/src/helpers/responsive.ts +83 -83
  173. package/src/helpers/styled/size.ts +24 -24
  174. package/src/helpers/styled/space.ts +111 -111
  175. package/src/helpers/styled/typography.ts +25 -25
  176. package/src/helpers/text.ts +13 -13
  177. package/src/helpers/types.ts +9 -9
  178. package/src/hooks/index.ts +9 -9
  179. package/src/hooks/providers.ts +14 -14
  180. package/src/hooks/useContainerMediaQuery.ts +7 -7
  181. package/src/hooks/useElementSize.ts +24 -24
  182. package/src/hooks/useMediaQuery.ts +9 -9
  183. package/src/hooks/useMediaQueryForWidth.ts +35 -35
  184. package/src/hooks/useMutableState.test.ts +410 -410
  185. package/src/hooks/useMutableState.ts +39 -39
  186. package/src/hooks/useShowArchived.ts +28 -28
  187. package/src/hooks/useWindowSize.ts +20 -20
  188. package/src/icons/index.tsx +138 -138
  189. package/src/providers/AuthenticationProvider/helpers.ts +3 -3
  190. package/src/providers/AuthenticationProvider/index.tsx +303 -303
  191. package/src/providers/LoadingProvider/index.tsx +47 -47
  192. package/src/providers/PortalsProvider/index.tsx +54 -54
  193. package/src/providers/PortalsProvider/styles.ts +31 -31
  194. package/src/providers/SettingsProvider/index.tsx +70 -70
  195. package/src/providers/ToastProvider/index.tsx +93 -93
  196. package/src/providers/TrackingProvider/hooks.ts +14 -14
  197. package/src/providers/TrackingProvider/index.tsx +71 -71
  198. package/src/providers/UiProviders/index.tsx +74 -74
  199. package/src/providers/UiProviders/styles.ts +10 -10
  200. package/src/providers/index.ts +6 -6
  201. package/src/services/HttpService.ts +92 -92
  202. package/src/services/WebSocketService.ts +155 -155
  203. package/src/services/advancedRequests.ts +102 -102
  204. package/src/services/base.ts +23 -23
  205. package/src/services/globalSearch.ts +32 -32
  206. package/src/services/hooks.ts +92 -92
  207. package/src/services/index.ts +20 -20
  208. package/src/services/requests/auth.ts +44 -44
  209. package/src/services/requests/generic.ts +76 -76
  210. package/src/services/requests/printing.ts +12 -12
  211. package/src/services/requests/tracking.ts +12 -12
  212. package/src/services/requests/userProfiles.ts +35 -35
  213. package/src/services/requests/users.ts +28 -28
  214. package/src/services/smartQueries.ts +122 -122
  215. package/src/services/sqlRequests.ts +119 -119
  216. package/src/services/types/auth.ts +98 -98
  217. package/src/services/types/base.ts +10 -10
  218. package/src/services/types/generic.ts +102 -102
  219. package/src/services/types/printing.ts +10 -10
  220. package/src/services/types/tracking.ts +29 -29
  221. package/src/services/types/userProfiles.ts +79 -79
  222. package/src/services/types/users.ts +74 -74
  223. package/src/services/updateSqlRequests.ts +32 -32
  224. package/src/styles/animations.scss +30 -30
  225. package/src/styles/index.scss +42 -42
  226. package/src/theme/ThemeProvider.ts +73 -73
  227. package/src/theme/defaultTheme.ts +471 -471
  228. package/src/theme/helpers.ts +89 -89
  229. package/src/theme/index.ts +4 -4
  230. package/src/theme/types.ts +139 -139
  231. package/src/types/index.ts +8 -8
  232. package/src/typings.d.ts +2 -2
  233. package/tsconfig.json +41 -41
  234. package/tsconfig.tsbuildinfo +1 -0
@@ -1,471 +1,471 @@
1
- import React, {
2
- HTMLAttributes,
3
- createContext,
4
- useContext,
5
- useEffect,
6
- useRef,
7
- useState,
8
- } from 'react';
9
-
10
- import { cn } from '@helpers/cn';
11
-
12
- interface GlobalContextMenuContextValue {
13
- currentMenuId: string | null;
14
- setCurrentMenuId: (id: string | null) => void;
15
- }
16
-
17
- const GlobalContextMenuContext = createContext<
18
- GlobalContextMenuContextValue | undefined
19
- >(undefined);
20
-
21
- export const GlobalContextMenuProvider: React.FC<{
22
- children: React.ReactNode;
23
- }> = ({ children }) => {
24
- const [currentMenuId, setCurrentMenuId] = useState<string | null>(null);
25
-
26
- useEffect(() => {
27
- const handleClick = () => setCurrentMenuId(null);
28
- document.addEventListener('click', handleClick);
29
- return () => document.removeEventListener('click', handleClick);
30
- }, []);
31
-
32
- return (
33
- <GlobalContextMenuContext.Provider
34
- value={{ currentMenuId, setCurrentMenuId }}
35
- >
36
- {children}
37
- </GlobalContextMenuContext.Provider>
38
- );
39
- };
40
-
41
- const useGlobalContextMenu = () => {
42
- const context = useContext(GlobalContextMenuContext);
43
- if (!context) {
44
- throw new Error(
45
- 'ContextMenu must be used within GlobalContextMenuProvider'
46
- );
47
- }
48
- return context;
49
- };
50
-
51
- interface ContextMenuContextValue {
52
- menuId: string;
53
- isOpen: boolean;
54
- position: { x: number; y: number };
55
- openMenu: (x: number, y: number) => void;
56
- closeMenu: () => void;
57
- }
58
-
59
- const ContextMenuContext = createContext<ContextMenuContextValue | undefined>(
60
- undefined
61
- );
62
-
63
- const useContextMenu = () => {
64
- const context = useContext(ContextMenuContext);
65
- if (!context) {
66
- throw new Error('ContextMenu components must be used within a ContextMenu');
67
- }
68
- return context;
69
- };
70
-
71
- let menuIdCounter = 0;
72
-
73
- export interface ContextMenuProps {
74
- children: React.ReactNode;
75
- }
76
-
77
- export const ContextMenu: React.FC<ContextMenuProps> = ({ children }) => {
78
- const { currentMenuId, setCurrentMenuId } = useGlobalContextMenu();
79
- const [menuId] = useState(() => `context-menu-${menuIdCounter++}`);
80
- const [position, setPosition] = useState({ x: 0, y: 0 });
81
-
82
- const isOpen = currentMenuId === menuId;
83
-
84
- const openMenu = (x: number, y: number) => {
85
- setPosition({ x, y });
86
- setCurrentMenuId(menuId);
87
- };
88
-
89
- const closeMenu = () => {
90
- if (currentMenuId === menuId) {
91
- setCurrentMenuId(null);
92
- }
93
- };
94
-
95
- return (
96
- <ContextMenuContext.Provider
97
- value={{ menuId, isOpen, position, openMenu, closeMenu }}
98
- >
99
- {children}
100
- </ContextMenuContext.Provider>
101
- );
102
- };
103
-
104
- export interface ContextMenuTriggerProps extends HTMLAttributes<HTMLDivElement> {
105
- children: React.ReactNode;
106
- }
107
-
108
- export const ContextMenuTrigger = React.forwardRef<
109
- HTMLDivElement,
110
- ContextMenuTriggerProps
111
- >(({ children, className, ...props }, ref) => {
112
- const { openMenu } = useContextMenu();
113
-
114
- const handleContextMenu = (e: React.MouseEvent) => {
115
- e.preventDefault();
116
- openMenu(e.clientX, e.clientY);
117
- };
118
-
119
- return (
120
- <div
121
- ref={ref}
122
- onContextMenu={handleContextMenu}
123
- className={cn('cursor-pointer', className)}
124
- {...props}
125
- >
126
- {children}
127
- </div>
128
- );
129
- });
130
-
131
- ContextMenuTrigger.displayName = 'ContextMenuTrigger';
132
-
133
- export interface ContextMenuContentProps extends HTMLAttributes<HTMLDivElement> {
134
- children: React.ReactNode;
135
- }
136
-
137
- export const ContextMenuContent = React.forwardRef<
138
- HTMLDivElement,
139
- ContextMenuContentProps
140
- >(({ children, className, ...props }, ref) => {
141
- const { isOpen, position } = useContextMenu();
142
- const contentRef = useRef<HTMLDivElement>(null);
143
-
144
- useEffect(() => {
145
- if (isOpen && contentRef.current) {
146
- const rect = contentRef.current.getBoundingClientRect();
147
- const viewportWidth = window.innerWidth;
148
- const viewportHeight = window.innerHeight;
149
-
150
- let adjustedX = position.x;
151
- let adjustedY = position.y;
152
-
153
- if (position.x + rect.width > viewportWidth) {
154
- adjustedX = viewportWidth - rect.width - 10;
155
- }
156
-
157
- if (position.y + rect.height > viewportHeight) {
158
- adjustedY = viewportHeight - rect.height - 10;
159
- }
160
-
161
- contentRef.current.style.left = `${adjustedX}px`;
162
- contentRef.current.style.top = `${adjustedY}px`;
163
- }
164
- }, [isOpen, position]);
165
-
166
- if (!isOpen) return null;
167
-
168
- return (
169
- <div
170
- ref={(node) => {
171
- if (typeof ref === 'function') ref(node);
172
- else if (ref) ref.current = node;
173
- contentRef.current = node;
174
- }}
175
- className={cn(
176
- 'fixed z-50 min-w-[12rem] bg-popover text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95',
177
- 'rounded-theme-md p-theme-xs',
178
- className
179
- )}
180
- onClick={(e) => e.stopPropagation()}
181
- {...props}
182
- >
183
- {children}
184
- </div>
185
- );
186
- });
187
-
188
- ContextMenuContent.displayName = 'ContextMenuContent';
189
-
190
- export interface ContextMenuItemProps extends HTMLAttributes<HTMLDivElement> {
191
- children: React.ReactNode;
192
- disabled?: boolean;
193
- onSelect?: () => void;
194
- }
195
-
196
- export const ContextMenuItem = React.forwardRef<
197
- HTMLDivElement,
198
- ContextMenuItemProps
199
- >(({ children, className, disabled, onSelect, ...props }, ref) => {
200
- const { closeMenu } = useContextMenu();
201
-
202
- const handleClick = () => {
203
- if (!disabled && onSelect) {
204
- onSelect();
205
- closeMenu();
206
- }
207
- };
208
-
209
- return (
210
- <div
211
- ref={ref}
212
- className={cn(
213
- 'relative flex cursor-pointer select-none items-center text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
214
- 'px-theme-sm py-theme-xs rounded-theme-sm',
215
- disabled && 'pointer-events-none opacity-50',
216
- className
217
- )}
218
- onClick={handleClick}
219
- {...props}
220
- >
221
- {children}
222
- </div>
223
- );
224
- });
225
-
226
- ContextMenuItem.displayName = 'ContextMenuItem';
227
-
228
- export interface ContextMenuSeparatorProps extends HTMLAttributes<HTMLDivElement> {}
229
-
230
- export const ContextMenuSeparator = React.forwardRef<
231
- HTMLDivElement,
232
- ContextMenuSeparatorProps
233
- >(({ className, ...props }, ref) => {
234
- return (
235
- <div
236
- ref={ref}
237
- className={cn('bg-border h-px my-theme-xs', className)}
238
- {...props}
239
- />
240
- );
241
- });
242
-
243
- ContextMenuSeparator.displayName = 'ContextMenuSeparator';
244
-
245
- export interface ContextMenuLabelProps extends HTMLAttributes<HTMLDivElement> {
246
- children: React.ReactNode;
247
- }
248
-
249
- export const ContextMenuLabel = React.forwardRef<
250
- HTMLDivElement,
251
- ContextMenuLabelProps
252
- >(({ children, className, ...props }, ref) => {
253
- return (
254
- <div
255
- ref={ref}
256
- className={cn(
257
- 'font-semibold text-foreground px-theme-sm py-theme-xs text-theme-sm',
258
- className
259
- )}
260
- {...props}
261
- >
262
- {children}
263
- </div>
264
- );
265
- });
266
-
267
- ContextMenuLabel.displayName = 'ContextMenuLabel';
268
-
269
- interface SubMenuContextValue {
270
- isOpen: boolean;
271
- openSubMenu: () => void;
272
- closeSubMenu: () => void;
273
- }
274
-
275
- const SubMenuContext = createContext<SubMenuContextValue | undefined>(
276
- undefined
277
- );
278
-
279
- const useSubMenu = () => {
280
- const context = useContext(SubMenuContext);
281
- if (!context) {
282
- throw new Error('SubMenu components must be used within a ContextMenuSub');
283
- }
284
- return context;
285
- };
286
-
287
- export interface ContextMenuSubProps {
288
- children: React.ReactNode;
289
- }
290
-
291
- export const ContextMenuSub: React.FC<ContextMenuSubProps> = ({ children }) => {
292
- const [isOpen, setIsOpen] = useState(false);
293
- const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
294
-
295
- const openSubMenu = () => {
296
- if (closeTimerRef.current) {
297
- clearTimeout(closeTimerRef.current);
298
- closeTimerRef.current = null;
299
- }
300
- setIsOpen(true);
301
- };
302
-
303
- const closeSubMenu = () => {
304
- closeTimerRef.current = setTimeout(() => {
305
- setIsOpen(false);
306
- closeTimerRef.current = null;
307
- }, 150);
308
- };
309
-
310
- useEffect(() => {
311
- return () => {
312
- if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
313
- };
314
- }, []);
315
-
316
- return (
317
- <SubMenuContext.Provider value={{ isOpen, openSubMenu, closeSubMenu }}>
318
- <div className="relative w-full">{children}</div>
319
- </SubMenuContext.Provider>
320
- );
321
- };
322
-
323
- export interface ContextMenuSubTriggerProps extends HTMLAttributes<HTMLDivElement> {
324
- children: React.ReactNode;
325
- disabled?: boolean;
326
- }
327
-
328
- export const ContextMenuSubTrigger = React.forwardRef<
329
- HTMLDivElement,
330
- ContextMenuSubTriggerProps
331
- >(({ children, className, disabled, ...props }, ref) => {
332
- const { openSubMenu } = useSubMenu();
333
-
334
- return (
335
- <div
336
- ref={ref}
337
- className={cn(
338
- 'flex cursor-pointer select-none items-center justify-between text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
339
- 'px-theme-sm py-theme-xs rounded-theme-sm',
340
- disabled && 'pointer-events-none opacity-50',
341
- className
342
- )}
343
- onMouseEnter={openSubMenu}
344
- onClick={(e) => e.stopPropagation()}
345
- {...props}
346
- >
347
- {children}
348
- <svg
349
- width="15"
350
- height="15"
351
- viewBox="0 0 15 15"
352
- fill="none"
353
- xmlns="http://www.w3.org/2000/svg"
354
- className="ml-auto"
355
- >
356
- <path
357
- d="M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z"
358
- fill="currentColor"
359
- fillRule="evenodd"
360
- clipRule="evenodd"
361
- />
362
- </svg>
363
- </div>
364
- );
365
- });
366
-
367
- ContextMenuSubTrigger.displayName = 'ContextMenuSubTrigger';
368
-
369
- export interface ContextMenuSubContentProps extends HTMLAttributes<HTMLDivElement> {
370
- children: React.ReactNode;
371
- }
372
-
373
- export const ContextMenuSubContent = React.forwardRef<
374
- HTMLDivElement,
375
- ContextMenuSubContentProps
376
- >(({ children, className, ...props }, ref) => {
377
- const { isOpen, openSubMenu, closeSubMenu } = useSubMenu();
378
- const contentRef = useRef<HTMLDivElement>(null);
379
- const [position, setPosition] = useState<{
380
- left?: string;
381
- right?: string;
382
- top?: string;
383
- }>({});
384
-
385
- useEffect(() => {
386
- const handleMouseLeave = (e: MouseEvent) => {
387
- if (
388
- contentRef.current &&
389
- !contentRef.current.contains(e.relatedTarget as Node)
390
- ) {
391
- const triggerParent = contentRef.current.parentElement;
392
- if (triggerParent && !triggerParent.contains(e.relatedTarget as Node)) {
393
- closeSubMenu();
394
- }
395
- }
396
- };
397
-
398
- const element = contentRef.current?.parentElement;
399
- if (element) {
400
- element.addEventListener('mouseleave', handleMouseLeave);
401
- return () => element.removeEventListener('mouseleave', handleMouseLeave);
402
- }
403
- }, [closeSubMenu]);
404
-
405
- useEffect(() => {
406
- if (isOpen && contentRef.current) {
407
- const submenuRect = contentRef.current.getBoundingClientRect();
408
- const parentElement = contentRef.current.parentElement;
409
-
410
- if (parentElement) {
411
- const parentRect = parentElement.getBoundingClientRect();
412
- const viewportWidth = window.innerWidth;
413
- const viewportHeight = window.innerHeight;
414
-
415
- const spaceOnRight = viewportWidth - parentRect.right;
416
- const spaceOnLeft = parentRect.left;
417
-
418
- const newPosition: { left?: string; right?: string; top?: string } = {};
419
-
420
- if (spaceOnRight >= submenuRect.width + 8) {
421
- newPosition.left = '100%';
422
- } else if (spaceOnLeft >= submenuRect.width + 8) {
423
- newPosition.right = '100%';
424
- } else {
425
- newPosition.left = '100%';
426
- }
427
-
428
- if (parentRect.top + submenuRect.height > viewportHeight) {
429
- const adjustedTop = Math.max(
430
- 0,
431
- viewportHeight - submenuRect.height - 10
432
- );
433
- newPosition.top = `${adjustedTop - parentRect.top}px`;
434
- } else {
435
- newPosition.top = '0';
436
- }
437
-
438
- setPosition(newPosition);
439
- }
440
- }
441
- }, [isOpen]);
442
-
443
- if (!isOpen) return null;
444
-
445
- return (
446
- <div
447
- ref={(node) => {
448
- if (typeof ref === 'function') ref(node);
449
- else if (ref) ref.current = node;
450
- contentRef.current = node;
451
- }}
452
- className={cn(
453
- 'absolute min-w-[12rem] overflow-hidden bg-popover text-popover-foreground shadow-lg border border-border animate-in fade-in-0 zoom-in-95 z-50',
454
- 'rounded-theme-md p-theme-xs',
455
- className
456
- )}
457
- style={{
458
- left: position.left,
459
- right: position.right,
460
- top: position.top || '0',
461
- }}
462
- onMouseEnter={openSubMenu}
463
- onClick={(e) => e.stopPropagation()}
464
- {...props}
465
- >
466
- {children}
467
- </div>
468
- );
469
- });
470
-
471
- ContextMenuSubContent.displayName = 'ContextMenuSubContent';
1
+ import React, {
2
+ HTMLAttributes,
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+
10
+ import { cn } from '@helpers/cn';
11
+
12
+ interface GlobalContextMenuContextValue {
13
+ currentMenuId: string | null;
14
+ setCurrentMenuId: (id: string | null) => void;
15
+ }
16
+
17
+ const GlobalContextMenuContext = createContext<
18
+ GlobalContextMenuContextValue | undefined
19
+ >(undefined);
20
+
21
+ export const GlobalContextMenuProvider: React.FC<{
22
+ children: React.ReactNode;
23
+ }> = ({ children }) => {
24
+ const [currentMenuId, setCurrentMenuId] = useState<string | null>(null);
25
+
26
+ useEffect(() => {
27
+ const handleClick = () => setCurrentMenuId(null);
28
+ document.addEventListener('click', handleClick);
29
+ return () => document.removeEventListener('click', handleClick);
30
+ }, []);
31
+
32
+ return (
33
+ <GlobalContextMenuContext.Provider
34
+ value={{ currentMenuId, setCurrentMenuId }}
35
+ >
36
+ {children}
37
+ </GlobalContextMenuContext.Provider>
38
+ );
39
+ };
40
+
41
+ const useGlobalContextMenu = () => {
42
+ const context = useContext(GlobalContextMenuContext);
43
+ if (!context) {
44
+ throw new Error(
45
+ 'ContextMenu must be used within GlobalContextMenuProvider'
46
+ );
47
+ }
48
+ return context;
49
+ };
50
+
51
+ interface ContextMenuContextValue {
52
+ menuId: string;
53
+ isOpen: boolean;
54
+ position: { x: number; y: number };
55
+ openMenu: (x: number, y: number) => void;
56
+ closeMenu: () => void;
57
+ }
58
+
59
+ const ContextMenuContext = createContext<ContextMenuContextValue | undefined>(
60
+ undefined
61
+ );
62
+
63
+ const useContextMenu = () => {
64
+ const context = useContext(ContextMenuContext);
65
+ if (!context) {
66
+ throw new Error('ContextMenu components must be used within a ContextMenu');
67
+ }
68
+ return context;
69
+ };
70
+
71
+ let menuIdCounter = 0;
72
+
73
+ export interface ContextMenuProps {
74
+ children: React.ReactNode;
75
+ }
76
+
77
+ export const ContextMenu: React.FC<ContextMenuProps> = ({ children }) => {
78
+ const { currentMenuId, setCurrentMenuId } = useGlobalContextMenu();
79
+ const [menuId] = useState(() => `context-menu-${menuIdCounter++}`);
80
+ const [position, setPosition] = useState({ x: 0, y: 0 });
81
+
82
+ const isOpen = currentMenuId === menuId;
83
+
84
+ const openMenu = (x: number, y: number) => {
85
+ setPosition({ x, y });
86
+ setCurrentMenuId(menuId);
87
+ };
88
+
89
+ const closeMenu = () => {
90
+ if (currentMenuId === menuId) {
91
+ setCurrentMenuId(null);
92
+ }
93
+ };
94
+
95
+ return (
96
+ <ContextMenuContext.Provider
97
+ value={{ menuId, isOpen, position, openMenu, closeMenu }}
98
+ >
99
+ {children}
100
+ </ContextMenuContext.Provider>
101
+ );
102
+ };
103
+
104
+ export interface ContextMenuTriggerProps extends HTMLAttributes<HTMLDivElement> {
105
+ children: React.ReactNode;
106
+ }
107
+
108
+ export const ContextMenuTrigger = React.forwardRef<
109
+ HTMLDivElement,
110
+ ContextMenuTriggerProps
111
+ >(({ children, className, ...props }, ref) => {
112
+ const { openMenu } = useContextMenu();
113
+
114
+ const handleContextMenu = (e: React.MouseEvent) => {
115
+ e.preventDefault();
116
+ openMenu(e.clientX, e.clientY);
117
+ };
118
+
119
+ return (
120
+ <div
121
+ ref={ref}
122
+ onContextMenu={handleContextMenu}
123
+ className={cn('cursor-pointer', className)}
124
+ {...props}
125
+ >
126
+ {children}
127
+ </div>
128
+ );
129
+ });
130
+
131
+ ContextMenuTrigger.displayName = 'ContextMenuTrigger';
132
+
133
+ export interface ContextMenuContentProps extends HTMLAttributes<HTMLDivElement> {
134
+ children: React.ReactNode;
135
+ }
136
+
137
+ export const ContextMenuContent = React.forwardRef<
138
+ HTMLDivElement,
139
+ ContextMenuContentProps
140
+ >(({ children, className, ...props }, ref) => {
141
+ const { isOpen, position } = useContextMenu();
142
+ const contentRef = useRef<HTMLDivElement>(null);
143
+
144
+ useEffect(() => {
145
+ if (isOpen && contentRef.current) {
146
+ const rect = contentRef.current.getBoundingClientRect();
147
+ const viewportWidth = window.innerWidth;
148
+ const viewportHeight = window.innerHeight;
149
+
150
+ let adjustedX = position.x;
151
+ let adjustedY = position.y;
152
+
153
+ if (position.x + rect.width > viewportWidth) {
154
+ adjustedX = viewportWidth - rect.width - 10;
155
+ }
156
+
157
+ if (position.y + rect.height > viewportHeight) {
158
+ adjustedY = viewportHeight - rect.height - 10;
159
+ }
160
+
161
+ contentRef.current.style.left = `${adjustedX}px`;
162
+ contentRef.current.style.top = `${adjustedY}px`;
163
+ }
164
+ }, [isOpen, position]);
165
+
166
+ if (!isOpen) return null;
167
+
168
+ return (
169
+ <div
170
+ ref={(node) => {
171
+ if (typeof ref === 'function') ref(node);
172
+ else if (ref) ref.current = node;
173
+ contentRef.current = node;
174
+ }}
175
+ className={cn(
176
+ 'fixed z-50 min-w-[12rem] bg-popover text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95',
177
+ 'rounded-theme-md p-theme-xs',
178
+ className
179
+ )}
180
+ onClick={(e) => e.stopPropagation()}
181
+ {...props}
182
+ >
183
+ {children}
184
+ </div>
185
+ );
186
+ });
187
+
188
+ ContextMenuContent.displayName = 'ContextMenuContent';
189
+
190
+ export interface ContextMenuItemProps extends HTMLAttributes<HTMLDivElement> {
191
+ children: React.ReactNode;
192
+ disabled?: boolean;
193
+ onSelect?: () => void;
194
+ }
195
+
196
+ export const ContextMenuItem = React.forwardRef<
197
+ HTMLDivElement,
198
+ ContextMenuItemProps
199
+ >(({ children, className, disabled, onSelect, ...props }, ref) => {
200
+ const { closeMenu } = useContextMenu();
201
+
202
+ const handleClick = () => {
203
+ if (!disabled && onSelect) {
204
+ onSelect();
205
+ closeMenu();
206
+ }
207
+ };
208
+
209
+ return (
210
+ <div
211
+ ref={ref}
212
+ className={cn(
213
+ 'relative flex cursor-pointer select-none items-center text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
214
+ 'px-theme-sm py-theme-xs rounded-theme-sm',
215
+ disabled && 'pointer-events-none opacity-50',
216
+ className
217
+ )}
218
+ onClick={handleClick}
219
+ {...props}
220
+ >
221
+ {children}
222
+ </div>
223
+ );
224
+ });
225
+
226
+ ContextMenuItem.displayName = 'ContextMenuItem';
227
+
228
+ export interface ContextMenuSeparatorProps extends HTMLAttributes<HTMLDivElement> {}
229
+
230
+ export const ContextMenuSeparator = React.forwardRef<
231
+ HTMLDivElement,
232
+ ContextMenuSeparatorProps
233
+ >(({ className, ...props }, ref) => {
234
+ return (
235
+ <div
236
+ ref={ref}
237
+ className={cn('bg-border h-px my-theme-xs', className)}
238
+ {...props}
239
+ />
240
+ );
241
+ });
242
+
243
+ ContextMenuSeparator.displayName = 'ContextMenuSeparator';
244
+
245
+ export interface ContextMenuLabelProps extends HTMLAttributes<HTMLDivElement> {
246
+ children: React.ReactNode;
247
+ }
248
+
249
+ export const ContextMenuLabel = React.forwardRef<
250
+ HTMLDivElement,
251
+ ContextMenuLabelProps
252
+ >(({ children, className, ...props }, ref) => {
253
+ return (
254
+ <div
255
+ ref={ref}
256
+ className={cn(
257
+ 'font-semibold text-foreground px-theme-sm py-theme-xs text-theme-sm',
258
+ className
259
+ )}
260
+ {...props}
261
+ >
262
+ {children}
263
+ </div>
264
+ );
265
+ });
266
+
267
+ ContextMenuLabel.displayName = 'ContextMenuLabel';
268
+
269
+ interface SubMenuContextValue {
270
+ isOpen: boolean;
271
+ openSubMenu: () => void;
272
+ closeSubMenu: () => void;
273
+ }
274
+
275
+ const SubMenuContext = createContext<SubMenuContextValue | undefined>(
276
+ undefined
277
+ );
278
+
279
+ const useSubMenu = () => {
280
+ const context = useContext(SubMenuContext);
281
+ if (!context) {
282
+ throw new Error('SubMenu components must be used within a ContextMenuSub');
283
+ }
284
+ return context;
285
+ };
286
+
287
+ export interface ContextMenuSubProps {
288
+ children: React.ReactNode;
289
+ }
290
+
291
+ export const ContextMenuSub: React.FC<ContextMenuSubProps> = ({ children }) => {
292
+ const [isOpen, setIsOpen] = useState(false);
293
+ const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
294
+
295
+ const openSubMenu = () => {
296
+ if (closeTimerRef.current) {
297
+ clearTimeout(closeTimerRef.current);
298
+ closeTimerRef.current = null;
299
+ }
300
+ setIsOpen(true);
301
+ };
302
+
303
+ const closeSubMenu = () => {
304
+ closeTimerRef.current = setTimeout(() => {
305
+ setIsOpen(false);
306
+ closeTimerRef.current = null;
307
+ }, 150);
308
+ };
309
+
310
+ useEffect(() => {
311
+ return () => {
312
+ if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
313
+ };
314
+ }, []);
315
+
316
+ return (
317
+ <SubMenuContext.Provider value={{ isOpen, openSubMenu, closeSubMenu }}>
318
+ <div className="relative w-full">{children}</div>
319
+ </SubMenuContext.Provider>
320
+ );
321
+ };
322
+
323
+ export interface ContextMenuSubTriggerProps extends HTMLAttributes<HTMLDivElement> {
324
+ children: React.ReactNode;
325
+ disabled?: boolean;
326
+ }
327
+
328
+ export const ContextMenuSubTrigger = React.forwardRef<
329
+ HTMLDivElement,
330
+ ContextMenuSubTriggerProps
331
+ >(({ children, className, disabled, ...props }, ref) => {
332
+ const { openSubMenu } = useSubMenu();
333
+
334
+ return (
335
+ <div
336
+ ref={ref}
337
+ className={cn(
338
+ 'flex cursor-pointer select-none items-center justify-between text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
339
+ 'px-theme-sm py-theme-xs rounded-theme-sm',
340
+ disabled && 'pointer-events-none opacity-50',
341
+ className
342
+ )}
343
+ onMouseEnter={openSubMenu}
344
+ onClick={(e) => e.stopPropagation()}
345
+ {...props}
346
+ >
347
+ {children}
348
+ <svg
349
+ width="15"
350
+ height="15"
351
+ viewBox="0 0 15 15"
352
+ fill="none"
353
+ xmlns="http://www.w3.org/2000/svg"
354
+ className="ml-auto"
355
+ >
356
+ <path
357
+ d="M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z"
358
+ fill="currentColor"
359
+ fillRule="evenodd"
360
+ clipRule="evenodd"
361
+ />
362
+ </svg>
363
+ </div>
364
+ );
365
+ });
366
+
367
+ ContextMenuSubTrigger.displayName = 'ContextMenuSubTrigger';
368
+
369
+ export interface ContextMenuSubContentProps extends HTMLAttributes<HTMLDivElement> {
370
+ children: React.ReactNode;
371
+ }
372
+
373
+ export const ContextMenuSubContent = React.forwardRef<
374
+ HTMLDivElement,
375
+ ContextMenuSubContentProps
376
+ >(({ children, className, ...props }, ref) => {
377
+ const { isOpen, openSubMenu, closeSubMenu } = useSubMenu();
378
+ const contentRef = useRef<HTMLDivElement>(null);
379
+ const [position, setPosition] = useState<{
380
+ left?: string;
381
+ right?: string;
382
+ top?: string;
383
+ }>({});
384
+
385
+ useEffect(() => {
386
+ const handleMouseLeave = (e: MouseEvent) => {
387
+ if (
388
+ contentRef.current &&
389
+ !contentRef.current.contains(e.relatedTarget as Node)
390
+ ) {
391
+ const triggerParent = contentRef.current.parentElement;
392
+ if (triggerParent && !triggerParent.contains(e.relatedTarget as Node)) {
393
+ closeSubMenu();
394
+ }
395
+ }
396
+ };
397
+
398
+ const element = contentRef.current?.parentElement;
399
+ if (element) {
400
+ element.addEventListener('mouseleave', handleMouseLeave);
401
+ return () => element.removeEventListener('mouseleave', handleMouseLeave);
402
+ }
403
+ }, [closeSubMenu]);
404
+
405
+ useEffect(() => {
406
+ if (isOpen && contentRef.current) {
407
+ const submenuRect = contentRef.current.getBoundingClientRect();
408
+ const parentElement = contentRef.current.parentElement;
409
+
410
+ if (parentElement) {
411
+ const parentRect = parentElement.getBoundingClientRect();
412
+ const viewportWidth = window.innerWidth;
413
+ const viewportHeight = window.innerHeight;
414
+
415
+ const spaceOnRight = viewportWidth - parentRect.right;
416
+ const spaceOnLeft = parentRect.left;
417
+
418
+ const newPosition: { left?: string; right?: string; top?: string } = {};
419
+
420
+ if (spaceOnRight >= submenuRect.width + 8) {
421
+ newPosition.left = '100%';
422
+ } else if (spaceOnLeft >= submenuRect.width + 8) {
423
+ newPosition.right = '100%';
424
+ } else {
425
+ newPosition.left = '100%';
426
+ }
427
+
428
+ if (parentRect.top + submenuRect.height > viewportHeight) {
429
+ const adjustedTop = Math.max(
430
+ 0,
431
+ viewportHeight - submenuRect.height - 10
432
+ );
433
+ newPosition.top = `${adjustedTop - parentRect.top}px`;
434
+ } else {
435
+ newPosition.top = '0';
436
+ }
437
+
438
+ setPosition(newPosition);
439
+ }
440
+ }
441
+ }, [isOpen]);
442
+
443
+ if (!isOpen) return null;
444
+
445
+ return (
446
+ <div
447
+ ref={(node) => {
448
+ if (typeof ref === 'function') ref(node);
449
+ else if (ref) ref.current = node;
450
+ contentRef.current = node;
451
+ }}
452
+ className={cn(
453
+ 'absolute min-w-[12rem] overflow-hidden bg-popover text-popover-foreground shadow-lg border border-border animate-in fade-in-0 zoom-in-95 z-50',
454
+ 'rounded-theme-md p-theme-xs',
455
+ className
456
+ )}
457
+ style={{
458
+ left: position.left,
459
+ right: position.right,
460
+ top: position.top || '0',
461
+ }}
462
+ onMouseEnter={openSubMenu}
463
+ onClick={(e) => e.stopPropagation()}
464
+ {...props}
465
+ >
466
+ {children}
467
+ </div>
468
+ );
469
+ });
470
+
471
+ ContextMenuSubContent.displayName = 'ContextMenuSubContent';