@oneuptime/common 7.0.4676 → 7.0.4707

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 (70) hide show
  1. package/Server/Middleware/ProjectAuthorization.ts +31 -29
  2. package/Server/Services/StatusPageService.ts +1 -1
  3. package/Tests/UI/Components/NavBar.test.tsx +12 -3
  4. package/UI/Components/ActionButton/ActionButtonSchema.ts +1 -0
  5. package/UI/Components/Banner/Banner.tsx +4 -1
  6. package/UI/Components/Breadcrumbs/Breadcrumbs.tsx +1 -1
  7. package/UI/Components/Card/Card.tsx +57 -46
  8. package/UI/Components/Detail/Detail.tsx +22 -1
  9. package/UI/Components/Detail/Field.ts +1 -0
  10. package/UI/Components/EventHistoryList/EventHistoryDayList.tsx +32 -25
  11. package/UI/Components/Feed/FeedItem.tsx +1 -1
  12. package/UI/Components/Footer/Footer.tsx +1 -1
  13. package/UI/Components/Forms/BasicForm.tsx +4 -1
  14. package/UI/Components/Icon/Icon.tsx +5 -3
  15. package/UI/Components/List/ListRow.tsx +22 -1
  16. package/UI/Components/ModelTable/BaseModelTable.tsx +2 -0
  17. package/UI/Components/ModelTable/Column.ts +1 -0
  18. package/UI/Components/Navbar/NavBar.tsx +309 -7
  19. package/UI/Components/OrderedStatesList/Item.tsx +22 -1
  20. package/UI/Components/Page/Page.tsx +2 -2
  21. package/UI/Components/Pagination/Pagination.tsx +1 -1
  22. package/UI/Components/SideMenu/SideMenu.tsx +289 -13
  23. package/UI/Components/SideMenu/SideMenuSection.tsx +2 -2
  24. package/UI/Components/Table/TableHeader.tsx +73 -53
  25. package/UI/Components/Table/TableRow.tsx +174 -148
  26. package/UI/Components/Table/Types/Column.ts +1 -0
  27. package/build/dist/Server/Middleware/ProjectAuthorization.js +19 -17
  28. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  29. package/build/dist/Server/Services/StatusPageService.js +2 -1
  30. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  31. package/build/dist/Tests/UI/Components/NavBar.test.js +8 -1
  32. package/build/dist/Tests/UI/Components/NavBar.test.js.map +1 -1
  33. package/build/dist/UI/Components/Banner/Banner.js +1 -1
  34. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  35. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js +1 -1
  36. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  37. package/build/dist/UI/Components/Card/Card.js +9 -13
  38. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  39. package/build/dist/UI/Components/Detail/Detail.js +17 -1
  40. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  41. package/build/dist/UI/Components/EventHistoryList/EventHistoryDayList.js +2 -2
  42. package/build/dist/UI/Components/EventHistoryList/EventHistoryDayList.js.map +1 -1
  43. package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
  44. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  45. package/build/dist/UI/Components/Footer/Footer.js +1 -1
  46. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  47. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  48. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  49. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  50. package/build/dist/UI/Components/List/ListRow.js +17 -1
  51. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  52. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -0
  53. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  54. package/build/dist/UI/Components/Navbar/NavBar.js +134 -4
  55. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  56. package/build/dist/UI/Components/OrderedStatesList/Item.js +17 -1
  57. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  58. package/build/dist/UI/Components/Page/Page.js +2 -2
  59. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  60. package/build/dist/UI/Components/Pagination/Pagination.js +1 -1
  61. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  62. package/build/dist/UI/Components/SideMenu/SideMenu.js +125 -11
  63. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  64. package/build/dist/UI/Components/SideMenu/SideMenuSection.js +2 -2
  65. package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
  66. package/build/dist/UI/Components/Table/TableHeader.js +18 -2
  67. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  68. package/build/dist/UI/Components/Table/TableRow.js +24 -3
  69. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  70. package/package.json +1 -1
@@ -1,24 +1,326 @@
1
- import React, { FunctionComponent, ReactElement } from "react";
1
+ import React, {
2
+ FunctionComponent,
3
+ ReactElement,
4
+ useState,
5
+ useEffect,
6
+ } from "react";
7
+ import Route from "../../../Types/API/Route";
8
+ import URL from "../../../Types/API/URL";
9
+ import IconProp from "../../../Types/Icon/IconProp";
10
+ import NavBarItem from "./NavBarItem";
11
+ import NavBarMenu from "./NavBarMenu";
12
+ import NavBarMenuItem from "./NavBarMenuItem";
13
+ import Button, { ButtonStyleType } from "../Button/Button";
14
+ import Navigation from "../../Utils/Navigation";
15
+ import useComponentOutsideClick from "../../Types/UseComponentOutsideClick";
16
+
17
+ export interface NavItem {
18
+ id: string;
19
+ title: string;
20
+ icon: IconProp;
21
+ route: Route;
22
+ activeRoute?: Route | undefined;
23
+ exact?: boolean | undefined;
24
+ description?: string | undefined;
25
+ }
26
+
27
+ export interface MoreMenuItem {
28
+ title: string;
29
+ description: string;
30
+ route: Route;
31
+ icon: IconProp;
32
+ }
2
33
 
3
34
  export interface ComponentProps {
4
- children: ReactElement | Array<ReactElement>;
5
- className?: string | undefined;
6
- rightElement?: ReactElement | undefined;
35
+ items?: NavItem[];
36
+ rightElement?: NavItem;
37
+ moreMenuItems?: MoreMenuItem[];
38
+ moreMenuFooter?: {
39
+ title: string;
40
+ description: string;
41
+ link: URL;
42
+ };
43
+ className?: string;
44
+ // Legacy support for children-based usage
45
+ children?: ReactElement | Array<ReactElement>;
7
46
  }
8
47
 
9
48
  const Navbar: FunctionComponent<ComponentProps> = (
10
49
  props: ComponentProps,
11
50
  ): ReactElement => {
51
+ const [isMobile, setIsMobile] = useState<boolean>(false);
52
+ const [isMobileMenuVisible, setIsMobileMenuVisible] =
53
+ useState<boolean>(false);
54
+ const [isMoreMenuVisible, setIsMoreMenuVisible] = useState<boolean>(false);
55
+ const [moreMenuTimeout, setMoreMenuTimeout] = useState<ReturnType<
56
+ typeof setTimeout
57
+ > | null>(null);
58
+
59
+ // Use the existing outside click hook for mobile menu
60
+ const {
61
+ ref: mobileMenuRef,
62
+ isComponentVisible: isMobileMenuOpen,
63
+ setIsComponentVisible: setIsMobileMenuOpen,
64
+ } = useComponentOutsideClick(false);
65
+
66
+ // Sync local state with hook state
67
+ useEffect(() => {
68
+ setIsMobileMenuVisible(isMobileMenuOpen);
69
+ }, [isMobileMenuOpen]);
70
+
71
+ // Check if we're on mobile
72
+ useEffect(() => {
73
+ const checkMobile: () => void = (): void => {
74
+ setIsMobile(window.innerWidth < 768); // md breakpoint
75
+ };
76
+
77
+ checkMobile();
78
+ window.addEventListener("resize", checkMobile);
79
+
80
+ return () => {
81
+ return window.removeEventListener("resize", checkMobile);
82
+ };
83
+ }, []);
84
+
85
+ // More menu functions
86
+ const hideMoreMenu: () => void = (): void => {
87
+ if (moreMenuTimeout) {
88
+ clearTimeout(moreMenuTimeout);
89
+ setMoreMenuTimeout(null);
90
+ }
91
+
92
+ const timeout: ReturnType<typeof setTimeout> = setTimeout(() => {
93
+ setIsMoreMenuVisible(false);
94
+ }, 500);
95
+
96
+ setMoreMenuTimeout(timeout);
97
+ };
98
+
99
+ const forceHideMoreMenu: () => void = (): void => {
100
+ if (moreMenuTimeout) {
101
+ clearTimeout(moreMenuTimeout);
102
+ setMoreMenuTimeout(null);
103
+ }
104
+
105
+ setIsMoreMenuVisible(false);
106
+ };
107
+
108
+ const showMoreMenu: () => void = (): void => {
109
+ if (moreMenuTimeout) {
110
+ clearTimeout(moreMenuTimeout);
111
+ }
112
+ setIsMoreMenuVisible(true);
113
+ };
114
+
115
+ // Legacy support: if children are provided, render the old way
116
+ if (props.children) {
117
+ const className: string =
118
+ props.className || "flex text-center lg:space-x-8 lg:py-2 bg-white ";
119
+
120
+ return (
121
+ <nav className={props.rightElement ? `flex justify-between` : ""}>
122
+ <div data-testid="nav-children" className={className}>
123
+ {props.children}
124
+ </div>
125
+ {props.rightElement && (
126
+ <div className={className}>
127
+ <NavBarItem
128
+ title={props.rightElement.title}
129
+ icon={props.rightElement.icon}
130
+ route={props.rightElement.route}
131
+ activeRoute={props.rightElement.activeRoute}
132
+ exact={props.rightElement.exact ?? false}
133
+ />
134
+ </div>
135
+ )}
136
+ </nav>
137
+ );
138
+ }
139
+
140
+ // New props-based implementation
141
+ if (!props.items || props.items.length === 0) {
142
+ return <></>;
143
+ }
144
+
145
+ // Build all nav items including more menu items for mobile
146
+ const allNavItems: Array<any> = [...props.items];
147
+ if (props.moreMenuItems) {
148
+ allNavItems.push(
149
+ ...props.moreMenuItems.map((item: any) => {
150
+ return {
151
+ id: `more-${item.title.toLowerCase().replace(/\s+/g, "-")}`,
152
+ title: item.title,
153
+ icon: item.icon,
154
+ route: item.route,
155
+ description: item.description,
156
+ };
157
+ }),
158
+ );
159
+ }
160
+
161
+ // Add right element to mobile menu
162
+ if (props.rightElement) {
163
+ allNavItems.push({
164
+ id: `right-${props.rightElement.title.toLowerCase().replace(/\s+/g, "-")}`,
165
+ title: props.rightElement.title,
166
+ icon: props.rightElement.icon,
167
+ route: props.rightElement.route,
168
+ activeRoute: props.rightElement.activeRoute,
169
+ exact: props.rightElement.exact,
170
+ });
171
+ }
172
+
173
+ // Find the currently active item
174
+ const activeItem: any =
175
+ allNavItems.find((item: any) => {
176
+ const routeToCheck: any = item.activeRoute || item.route;
177
+ return item.exact
178
+ ? Navigation.isOnThisPage(routeToCheck)
179
+ : Navigation.isStartWith(routeToCheck);
180
+ }) || allNavItems[0];
181
+
182
+ // Mobile view
183
+ if (isMobile && activeItem) {
184
+ return (
185
+ <div className="relative md:hidden">
186
+ <nav className="bg-white text-center justify-between py-2 mt-5">
187
+ {/* Mobile: Show only active item and hamburger menu */}
188
+ <div className="flex items-center justify-between w-full">
189
+ <NavBarItem
190
+ id={activeItem.id}
191
+ title={activeItem.title}
192
+ icon={activeItem.icon}
193
+ exact={true}
194
+ route={undefined}
195
+ onClick={() => {
196
+ return setIsMobileMenuOpen(!isMobileMenuVisible);
197
+ }}
198
+ isRenderedOnMobile={true}
199
+ />
200
+
201
+ <Button
202
+ buttonStyle={ButtonStyleType.OUTLINE}
203
+ onClick={() => {
204
+ return setIsMobileMenuOpen(!isMobileMenuVisible);
205
+ }}
206
+ className="ml-2 p-2"
207
+ icon={isMobileMenuOpen ? IconProp.Close : IconProp.Bars3}
208
+ dataTestId="mobile-nav-toggle"
209
+ />
210
+ </div>
211
+ </nav>
212
+
213
+ {/* Mobile dropdown menu */}
214
+ {isMobileMenuOpen && (
215
+ <div
216
+ ref={mobileMenuRef}
217
+ className="absolute top-full left-0 right-0 z-50 mt-1 transition-all duration-200 ease-in-out"
218
+ >
219
+ <nav className="bg-white rounded-lg shadow-lg px-3 py-3 space-y-1 border border-gray-200">
220
+ {allNavItems.map((item: any) => {
221
+ return (
222
+ <div key={item.id} className="block w-full">
223
+ <NavBarItem
224
+ id={item.id}
225
+ title={item.title}
226
+ icon={item.icon}
227
+ exact={item.exact ?? false}
228
+ route={item.route}
229
+ activeRoute={item.activeRoute}
230
+ onClick={() => {
231
+ return setIsMobileMenuOpen(false);
232
+ }}
233
+ isRenderedOnMobile={true}
234
+ />
235
+ </div>
236
+ );
237
+ })}
238
+ </nav>
239
+ </div>
240
+ )}
241
+ </div>
242
+ );
243
+ }
244
+
245
+ // Desktop view
12
246
  const className: string =
13
- props.className || "flex text-center lg:space-x-8 lg:py-2 bg-white ";
247
+ props.className ||
248
+ "bg-white flex text-center lg:space-x-8 lg:py-2 hidden md:flex";
14
249
 
15
250
  return (
16
251
  <nav className={props.rightElement ? `flex justify-between` : ""}>
17
252
  <div data-testid="nav-children" className={className}>
18
- {props.children}
253
+ {props.items.map((item: any) => {
254
+ return (
255
+ <NavBarItem
256
+ key={item.id}
257
+ id={item.id}
258
+ title={item.title}
259
+ icon={item.icon}
260
+ activeRoute={item.activeRoute}
261
+ route={item.route}
262
+ exact={item.exact ?? false}
263
+ />
264
+ );
265
+ })}
266
+
267
+ {/* More menu for desktop */}
268
+ {props.moreMenuItems && props.moreMenuItems.length > 0 && (
269
+ <NavBarItem
270
+ title="More"
271
+ icon={IconProp.More}
272
+ onMouseLeave={hideMoreMenu}
273
+ onMouseOver={showMoreMenu}
274
+ onClick={showMoreMenu}
275
+ >
276
+ <div onMouseOver={showMoreMenu} onMouseLeave={hideMoreMenu}>
277
+ {isMoreMenuVisible &&
278
+ (props.moreMenuFooter ? (
279
+ <NavBarMenu footer={props.moreMenuFooter}>
280
+ {props.moreMenuItems.map((item: any) => {
281
+ return (
282
+ <NavBarMenuItem
283
+ key={item.title}
284
+ title={item.title}
285
+ description={item.description}
286
+ route={item.route}
287
+ icon={item.icon}
288
+ onClick={forceHideMoreMenu}
289
+ />
290
+ );
291
+ })}
292
+ </NavBarMenu>
293
+ ) : (
294
+ <NavBarMenu>
295
+ {props.moreMenuItems.map((item: any) => {
296
+ return (
297
+ <NavBarMenuItem
298
+ key={item.title}
299
+ title={item.title}
300
+ description={item.description}
301
+ route={item.route}
302
+ icon={item.icon}
303
+ onClick={forceHideMoreMenu}
304
+ />
305
+ );
306
+ })}
307
+ </NavBarMenu>
308
+ ))}
309
+ </div>
310
+ </NavBarItem>
311
+ )}
19
312
  </div>
313
+
20
314
  {props.rightElement && (
21
- <div className={className}>{props.rightElement}</div>
315
+ <div className={className}>
316
+ <NavBarItem
317
+ title={props.rightElement.title}
318
+ icon={props.rightElement.icon}
319
+ route={props.rightElement.route}
320
+ activeRoute={props.rightElement.activeRoute}
321
+ exact={props.rightElement.exact ?? false}
322
+ />
323
+ </div>
22
324
  )}
23
325
  </nav>
24
326
  );
@@ -2,7 +2,7 @@ import ActionButtonSchema from "../ActionButton/ActionButtonSchema";
2
2
  import Button, { ButtonSize } from "../Button/Button";
3
3
  import ConfirmModal from "../Modal/ConfirmModal";
4
4
  import GenericObject from "../../../Types/GenericObject";
5
- import React, { ReactElement, useState } from "react";
5
+ import React, { ReactElement, useState, useEffect } from "react";
6
6
 
7
7
  export interface ComponentProps<T extends GenericObject> {
8
8
  item: T;
@@ -28,6 +28,22 @@ const Item: ItemFunction = <T extends GenericObject>(
28
28
 
29
29
  const [error, setError] = useState<string>("");
30
30
 
31
+ // Track mobile view for responsive behavior
32
+ const [isMobile, setIsMobile] = useState<boolean>(false);
33
+
34
+ useEffect(() => {
35
+ const checkMobile: () => void = (): void => {
36
+ setIsMobile(window.innerWidth < 768); // md breakpoint
37
+ };
38
+
39
+ checkMobile();
40
+ window.addEventListener("resize", checkMobile);
41
+
42
+ return () => {
43
+ window.removeEventListener("resize", checkMobile);
44
+ };
45
+ }, []);
46
+
31
47
  return (
32
48
  <div className="text-center border border-gray-300 rounded p-10 space-y-4 w-fit">
33
49
  {error && (
@@ -74,6 +90,11 @@ const Item: ItemFunction = <T extends GenericObject>(
74
90
  return <></>;
75
91
  }
76
92
 
93
+ // Hide button on mobile if hideOnMobile is true
94
+ if (button.hideOnMobile && isMobile) {
95
+ return <></>;
96
+ }
97
+
77
98
  return (
78
99
  <div key={i} className="">
79
100
  <Button
@@ -52,7 +52,7 @@ const Page: FunctionComponent<ComponentProps> = (
52
52
  </div>
53
53
  )}
54
54
  {props.title && (
55
- <div className="mt-2 md:flex md:items-center md:justify-between">
55
+ <div className="mt-2 md:flex md:items-center md:justify-between hidden md:block">
56
56
  <div className="min-w-0">
57
57
  <h2 className="text-xl leading-7 text-gray-900 sm:truncate sm:text-2xl sm:tracking-tight">
58
58
  {props.title}
@@ -74,7 +74,7 @@ const Page: FunctionComponent<ComponentProps> = (
74
74
  </div>
75
75
  )}
76
76
  {props.isLoading && (
77
- <div className="col-span-10">
77
+ <div className="lg:col-span-10 md:col-span-9">
78
78
  <PageLoader isVisible={true} />
79
79
  </div>
80
80
  )}
@@ -71,7 +71,7 @@ const Pagination: FunctionComponent<ComponentProps> = (
71
71
  className="flex items-center justify-between border-t border-gray-200 bg-white px-4"
72
72
  data-testid={props.dataTestId}
73
73
  >
74
- <div>
74
+ <div className="hidden md:block">
75
75
  <p className="text-sm text-gray-500">
76
76
  {!props.isLoading && (
77
77
  <span>