@greatapps/common 1.1.168 → 1.1.170

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 (162) hide show
  1. package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -1
  2. package/dist/components/account/DeleteAccountModal.mjs.map +1 -1
  3. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -1
  4. package/dist/components/account/hooks/useTwoFactorAuthForm.mjs.map +1 -1
  5. package/dist/components/layouts/AppMobileNavBar.mjs.map +1 -1
  6. package/dist/components/layouts/AppNavBar.mjs.map +1 -1
  7. package/dist/components/layouts/NavBar.mjs.map +1 -1
  8. package/dist/components/layouts/ProfilePopover.mjs +11 -12
  9. package/dist/components/layouts/ProfilePopover.mjs.map +1 -1
  10. package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
  11. package/dist/components/navigation/NavItemsContext.mjs.map +1 -1
  12. package/dist/components/navigation/shells/TabsMobileShell.mjs.map +1 -1
  13. package/dist/components/pages/notifications/Notifications.mjs.map +1 -1
  14. package/dist/components/ui/buttons/Button.mjs.map +1 -1
  15. package/dist/components/ui/buttons/CancelButton.mjs.map +1 -1
  16. package/dist/components/ui/buttons/CopyButton.mjs.map +1 -1
  17. package/dist/components/ui/data-display/Accordion.mjs.map +1 -1
  18. package/dist/components/ui/data-display/Badge.mjs.map +1 -1
  19. package/dist/components/ui/data-display/Card.mjs.map +1 -1
  20. package/dist/components/ui/data-display/Pagination.mjs.map +1 -1
  21. package/dist/components/ui/data-display/ScrollArea.mjs.map +1 -1
  22. package/dist/components/ui/data-display/Separator.mjs.map +1 -1
  23. package/dist/components/ui/data-display/Table.mjs.map +1 -1
  24. package/dist/components/ui/data-display/Tabs.mjs.map +1 -1
  25. package/dist/components/ui/feedback/CircularProgress.mjs.map +1 -1
  26. package/dist/components/ui/feedback/DefaultCircularProgress.mjs.map +1 -1
  27. package/dist/components/ui/feedback/LoadingOverlay.mjs.map +1 -1
  28. package/dist/components/ui/feedback/Progress.mjs.map +1 -1
  29. package/dist/components/ui/feedback/Toast.mjs.map +1 -1
  30. package/dist/components/ui/form/Calendar.mjs.map +1 -1
  31. package/dist/components/ui/form/Checkbox.mjs.map +1 -1
  32. package/dist/components/ui/form/DatePicker.mjs.map +1 -1
  33. package/dist/components/ui/form/DateRangePicker.mjs.map +1 -1
  34. package/dist/components/ui/form/Input.mjs.map +1 -1
  35. package/dist/components/ui/form/InputOtp.mjs.map +1 -1
  36. package/dist/components/ui/form/RadioGroup.mjs.map +1 -1
  37. package/dist/components/ui/form/Select.mjs +1 -1
  38. package/dist/components/ui/form/Select.mjs.map +1 -1
  39. package/dist/components/ui/form/Switch.mjs.map +1 -1
  40. package/dist/components/ui/form/Textarea.mjs.map +1 -1
  41. package/dist/components/ui/overlay/Command.mjs.map +1 -1
  42. package/dist/components/ui/overlay/Dialog.mjs.map +1 -1
  43. package/dist/components/ui/overlay/Popover.mjs.map +1 -1
  44. package/dist/components/ui/overlay/Sheet.mjs.map +1 -1
  45. package/dist/components/ui/overlay/Tooltip.mjs.map +1 -1
  46. package/dist/hooks/copy-to-clipboard.hook.mjs.map +1 -1
  47. package/dist/index.mjs +26 -24
  48. package/dist/index.mjs.map +1 -1
  49. package/dist/middleware.mjs.map +1 -1
  50. package/dist/modules/accounts/actions/account-management.action.mjs.map +1 -1
  51. package/dist/modules/accounts/actions/find-current-account.action.mjs.map +1 -1
  52. package/dist/modules/accounts/actions/list-currencies.action.mjs.map +1 -1
  53. package/dist/modules/accounts/actions/list-timezones.action.mjs.map +1 -1
  54. package/dist/modules/accounts/hooks/current-account.hook.mjs.map +1 -1
  55. package/dist/modules/accounts/hooks/useAccountManagement.mjs.map +1 -1
  56. package/dist/modules/accounts/hooks/useCurrencies.mjs.map +1 -1
  57. package/dist/modules/accounts/hooks/useTimezones.mjs.map +1 -1
  58. package/dist/modules/auth/hooks/login.hook.mjs.map +1 -1
  59. package/dist/modules/auth/hooks/logout.hook.mjs.map +1 -1
  60. package/dist/modules/auth/hooks/register.hook.mjs.map +1 -1
  61. package/dist/modules/ia-credits/actions/list-ia-credits.action.mjs.map +1 -1
  62. package/dist/modules/projects/actions/add-project-user.action.mjs.map +1 -1
  63. package/dist/modules/projects/actions/list-available-users.action.mjs.map +1 -1
  64. package/dist/modules/projects/actions/list-project-users.action.mjs.map +1 -1
  65. package/dist/modules/projects/actions/remove-project-user.action.mjs.map +1 -1
  66. package/dist/modules/projects/hooks/list-available-users.hook.mjs.map +1 -1
  67. package/dist/modules/projects/hooks/list-project-users.hook.mjs.map +1 -1
  68. package/dist/modules/subscriptions/actions/list-subscriptions.action.mjs.map +1 -1
  69. package/dist/modules/subscriptions/hooks/find-active-subscription.hook.mjs.map +1 -1
  70. package/dist/modules/subscriptions/hooks/list-subscriptions.hook.mjs.map +1 -1
  71. package/dist/modules/users/action/find-user-by-id.action.mjs.map +1 -1
  72. package/dist/modules/users/action/list-messages.action.mjs.map +1 -1
  73. package/dist/modules/users/action/mark-all-messages-as-read.action.mjs.map +1 -1
  74. package/dist/modules/users/action/mark-message-as-read.action.mjs.map +1 -1
  75. package/dist/modules/users/hooks/messages.hook.mjs.map +1 -1
  76. package/dist/modules/whitelabel/actions/find-whitelabel-styles.action.mjs.map +1 -1
  77. package/dist/modules/whitelabel/actions/find-whitelabel.action.mjs.map +1 -1
  78. package/dist/modules/whitelabel/services/whitelabel-styles.service.mjs.map +1 -1
  79. package/dist/providers/whitelabel.provider.mjs.map +1 -1
  80. package/dist/utils/safeServerAction.mjs.map +1 -1
  81. package/dist/utils/withAction.mjs.map +1 -1
  82. package/package.json +90 -90
  83. package/src/components/account/ConfirmDeleteAccountModal.tsx +55 -55
  84. package/src/components/account/DeleteAccountModal.tsx +130 -130
  85. package/src/components/account/IsntPossibleDeleteAccountModal.tsx +49 -49
  86. package/src/components/account/hooks/useTwoFactorAuthForm.tsx +101 -101
  87. package/src/components/layouts/AppMobileNavBar.tsx +125 -125
  88. package/src/components/layouts/AppNavBar.tsx +57 -57
  89. package/src/components/layouts/NavBar.tsx +139 -139
  90. package/src/components/layouts/ProfilePopover.tsx +9 -12
  91. package/src/components/navigation/CancelledSubscriptionBanner.tsx +69 -69
  92. package/src/components/navigation/NavItemsContext.ts +20 -20
  93. package/src/components/navigation/shells/TabsMobileShell.tsx +72 -72
  94. package/src/components/pages/notifications/Notifications.tsx +160 -160
  95. package/src/components/ui/buttons/Button.tsx +57 -57
  96. package/src/components/ui/buttons/CancelButton.tsx +14 -14
  97. package/src/components/ui/buttons/CopyButton.tsx +54 -54
  98. package/src/components/ui/data-display/Accordion.tsx +81 -81
  99. package/src/components/ui/data-display/Badge.tsx +47 -47
  100. package/src/components/ui/data-display/Card.tsx +93 -93
  101. package/src/components/ui/data-display/Pagination.tsx +126 -126
  102. package/src/components/ui/data-display/ScrollArea.tsx +59 -59
  103. package/src/components/ui/data-display/Separator.tsx +29 -29
  104. package/src/components/ui/data-display/Table.tsx +117 -117
  105. package/src/components/ui/data-display/Tabs.tsx +67 -67
  106. package/src/components/ui/feedback/CircularProgress.tsx +75 -75
  107. package/src/components/ui/feedback/DefaultCircularProgress.tsx +53 -53
  108. package/src/components/ui/feedback/LoadingOverlay.tsx +31 -31
  109. package/src/components/ui/feedback/Progress.tsx +40 -40
  110. package/src/components/ui/feedback/Toast.tsx +101 -101
  111. package/src/components/ui/form/Calendar.tsx +228 -228
  112. package/src/components/ui/form/Checkbox.tsx +31 -31
  113. package/src/components/ui/form/DatePicker.tsx +97 -97
  114. package/src/components/ui/form/DateRangePicker.tsx +108 -108
  115. package/src/components/ui/form/Input.tsx +20 -20
  116. package/src/components/ui/form/InputOtp.tsx +80 -80
  117. package/src/components/ui/form/RadioGroup.tsx +45 -45
  118. package/src/components/ui/form/Select.tsx +200 -200
  119. package/src/components/ui/form/Switch.tsx +32 -32
  120. package/src/components/ui/form/Textarea.tsx +19 -19
  121. package/src/components/ui/overlay/Command.tsx +185 -185
  122. package/src/components/ui/overlay/Dialog.tsx +151 -151
  123. package/src/components/ui/overlay/Popover.tsx +49 -49
  124. package/src/components/ui/overlay/Sheet.tsx +159 -159
  125. package/src/components/ui/overlay/Tooltip.tsx +62 -62
  126. package/src/hooks/copy-to-clipboard.hook.tsx +67 -67
  127. package/src/index.ts +351 -348
  128. package/src/middleware.ts +4 -4
  129. package/src/modules/accounts/actions/account-management.action.ts +90 -90
  130. package/src/modules/accounts/actions/find-current-account.action.ts +9 -9
  131. package/src/modules/accounts/actions/list-currencies.action.ts +8 -8
  132. package/src/modules/accounts/actions/list-timezones.action.ts +8 -8
  133. package/src/modules/accounts/hooks/current-account.hook.tsx +22 -22
  134. package/src/modules/accounts/hooks/useAccountManagement.ts +85 -85
  135. package/src/modules/accounts/hooks/useCurrencies.ts +15 -15
  136. package/src/modules/accounts/hooks/useTimezones.ts +15 -15
  137. package/src/modules/accounts/types.ts +214 -214
  138. package/src/modules/auth/hooks/login.hook.tsx +13 -13
  139. package/src/modules/auth/hooks/logout.hook.tsx +11 -11
  140. package/src/modules/auth/hooks/register.hook.tsx +11 -11
  141. package/src/modules/ia-credits/actions/list-ia-credits.action.ts +8 -8
  142. package/src/modules/projects/actions/add-project-user.action.ts +9 -9
  143. package/src/modules/projects/actions/list-available-users.action.ts +9 -9
  144. package/src/modules/projects/actions/list-project-users.action.ts +9 -9
  145. package/src/modules/projects/actions/remove-project-user.action.ts +9 -9
  146. package/src/modules/projects/hooks/list-available-users.hook.ts +33 -33
  147. package/src/modules/projects/hooks/list-project-users.hook.ts +33 -33
  148. package/src/modules/subscriptions/actions/list-subscriptions.action.ts +9 -9
  149. package/src/modules/subscriptions/hooks/find-active-subscription.hook.ts +19 -19
  150. package/src/modules/subscriptions/hooks/list-subscriptions.hook.ts +17 -17
  151. package/src/modules/users/action/find-user-by-id.action.ts +8 -8
  152. package/src/modules/users/action/list-messages.action.ts +12 -12
  153. package/src/modules/users/action/mark-all-messages-as-read.action.ts +8 -8
  154. package/src/modules/users/action/mark-message-as-read.action.ts +8 -8
  155. package/src/modules/users/hooks/messages.hook.ts +60 -60
  156. package/src/modules/whitelabel/actions/find-whitelabel-styles.action.ts +14 -14
  157. package/src/modules/whitelabel/actions/find-whitelabel.action.ts +23 -23
  158. package/src/modules/whitelabel/services/whitelabel-styles.service.ts +98 -98
  159. package/src/modules/whitelabel/styles-schema.ts +37 -37
  160. package/src/providers/whitelabel.provider.tsx +46 -46
  161. package/src/utils/safeServerAction.ts +21 -21
  162. package/src/utils/withAction.ts +13 -13
@@ -4,9 +4,9 @@ import { useState } from "react";
4
4
  import { useRouter } from "next/navigation";
5
5
  import * as PopoverPrimitive from "@radix-ui/react-popover";
6
6
  import { Popover } from "../ui/overlay/Popover";
7
- import { Button } from "../ui/buttons/Button";
8
7
  import { Progress } from "../ui/feedback/Progress";
9
8
  import { Separator } from "../ui/data-display/Separator";
9
+ import { Settings } from "lucide-react";
10
10
  import { NavBarItem } from "./NavBarItem";
11
11
  import { UserAvatar } from "../ui/data-display/UserAvatar";
12
12
  import { LoadingOverlay } from "../ui/feedback/LoadingOverlay";
@@ -92,7 +92,7 @@ function ProfilePopover({
92
92
  align={align}
93
93
  sideOffset={8}
94
94
  >
95
- <div className="grid grid-cols-[32px_1fr_auto] items-center gap-3 p-3 w-full">
95
+ <div className="grid grid-cols-[32px_1fr] items-center gap-3 p-3 w-full">
96
96
  {user && (
97
97
  <UserAvatar
98
98
  key={user.id}
@@ -110,16 +110,6 @@ function ProfilePopover({
110
110
  {user?.email || ""}
111
111
  </span>
112
112
  </div>
113
-
114
- {onEditProfile && (
115
- <Button
116
- variant="secondary"
117
- className="paragraph-small-semibold h-8! whitespace-nowrap px-3 w-fit"
118
- onClick={onEditProfile}
119
- >
120
- Editar Perfil
121
- </Button>
122
- )}
123
113
  </div>
124
114
 
125
115
  <div className="flex flex-col gap-2 border-t border-gray-200 rounded-2xl p-1.5">
@@ -173,6 +163,13 @@ function ProfilePopover({
173
163
  ))}
174
164
  </div>
175
165
  )}
166
+ {onEditProfile && (
167
+ <NavBarItem
168
+ icon={<Settings size={16} />}
169
+ label="Configurações"
170
+ onClick={onEditProfile}
171
+ />
172
+ )}
176
173
  <Separator />
177
174
  <NavBarItem label="Sair" onClick={handleLogout} />
178
175
  </div>
@@ -1,69 +1,69 @@
1
- 'use client';
2
-
3
- import { useState, useEffect, useCallback } from 'react';
4
- import { IconAlertOctagon, IconX } from '@tabler/icons-react';
5
- import { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';
6
-
7
- function daysBetween(a: Date, b: Date): number {
8
- const msPerDay = 24 * 60 * 60 * 1000;
9
- const da = new Date(a.getFullYear(), a.getMonth(), a.getDate());
10
- const db = new Date(b.getFullYear(), b.getMonth(), b.getDate());
11
- return Math.round((db.getTime() - da.getTime()) / msPerDay);
12
- }
13
-
14
- interface CancelledSubscriptionBannerProps {
15
- onReactivate?: () => void;
16
- }
17
-
18
- export function CancelledSubscriptionBanner({ onReactivate }: CancelledSubscriptionBannerProps) {
19
- const { data: { data: [subscription] = [] } = {} } = useActiveSubscription();
20
- const [visible, setVisible] = useState(true);
21
- const [animated, setAnimated] = useState(false);
22
-
23
- useEffect(() => {
24
- const timer = setTimeout(() => setAnimated(true), 50);
25
- return () => clearTimeout(timer);
26
- }, []);
27
-
28
- const dismiss = useCallback(() => setVisible(false), []);
29
-
30
- const cancellationDate = subscription?.date_cancellation;
31
- if (!cancellationDate || !visible) return null;
32
-
33
- const today = new Date();
34
- const daysSinceCancellation = daysBetween(new Date(cancellationDate), today);
35
- if (daysSinceCancellation < 0 || daysSinceCancellation > 5) return null;
36
-
37
- const daysRemaining = Math.max(0, 5 - daysSinceCancellation);
38
-
39
- return (
40
- <div className="fixed top-[80px] md:top-0 lg:left-[273px] left-0 right-0 z-[60] flex justify-center pointer-events-none px-4 lg:px-0">
41
- <div
42
- className={`flex flex-col sm:flex-row items-start sm:items-center justify-between bg-red-500 rounded-lg px-3 py-2 sm:py-1 w-full lg:w-[805px] max-w-full pointer-events-auto transition-transform duration-500 ease-out gap-2 sm:gap-0 ${animated ? 'translate-y-0 md:translate-y-9' : '-translate-y-full'}`}
43
- >
44
- {daysRemaining > 0 && (
45
- <div className="flex items-center gap-2 flex-1 w-full sm:w-auto">
46
- <IconAlertOctagon size={16} className="text-white shrink-0" />
47
- <span className="paragraph-xsmall-semibold text-white">
48
- Assinatura cancelada! Em {daysRemaining} dias suas páginas serão tiradas do ar!
49
- </span>
50
- </div>
51
- )}
52
- <div className="flex items-center gap-1.5 w-full sm:w-auto justify-between sm:justify-start">
53
- <button
54
- type="button"
55
- className="flex items-center justify-center p-1.5 cursor-pointer"
56
- onClick={onReactivate}
57
- >
58
- <span className="paragraph-xsmall-semibold text-white underline">
59
- Reativar assinatura
60
- </span>
61
- </button>
62
- <button type="button" className="cursor-pointer shrink-0" onClick={dismiss}>
63
- <IconX size={18} className="text-red-200" />
64
- </button>
65
- </div>
66
- </div>
67
- </div>
68
- );
69
- }
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { IconAlertOctagon, IconX } from '@tabler/icons-react';
5
+ import { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';
6
+
7
+ function daysBetween(a: Date, b: Date): number {
8
+ const msPerDay = 24 * 60 * 60 * 1000;
9
+ const da = new Date(a.getFullYear(), a.getMonth(), a.getDate());
10
+ const db = new Date(b.getFullYear(), b.getMonth(), b.getDate());
11
+ return Math.round((db.getTime() - da.getTime()) / msPerDay);
12
+ }
13
+
14
+ interface CancelledSubscriptionBannerProps {
15
+ onReactivate?: () => void;
16
+ }
17
+
18
+ export function CancelledSubscriptionBanner({ onReactivate }: CancelledSubscriptionBannerProps) {
19
+ const { data: { data: [subscription] = [] } = {} } = useActiveSubscription();
20
+ const [visible, setVisible] = useState(true);
21
+ const [animated, setAnimated] = useState(false);
22
+
23
+ useEffect(() => {
24
+ const timer = setTimeout(() => setAnimated(true), 50);
25
+ return () => clearTimeout(timer);
26
+ }, []);
27
+
28
+ const dismiss = useCallback(() => setVisible(false), []);
29
+
30
+ const cancellationDate = subscription?.date_cancellation;
31
+ if (!cancellationDate || !visible) return null;
32
+
33
+ const today = new Date();
34
+ const daysSinceCancellation = daysBetween(new Date(cancellationDate), today);
35
+ if (daysSinceCancellation < 0 || daysSinceCancellation > 5) return null;
36
+
37
+ const daysRemaining = Math.max(0, 5 - daysSinceCancellation);
38
+
39
+ return (
40
+ <div className="fixed top-[80px] md:top-0 lg:left-[273px] left-0 right-0 z-[60] flex justify-center pointer-events-none px-4 lg:px-0">
41
+ <div
42
+ className={`flex flex-col sm:flex-row items-start sm:items-center justify-between bg-red-500 rounded-lg px-3 py-2 sm:py-1 w-full lg:w-[805px] max-w-full pointer-events-auto transition-transform duration-500 ease-out gap-2 sm:gap-0 ${animated ? 'translate-y-0 md:translate-y-9' : '-translate-y-full'}`}
43
+ >
44
+ {daysRemaining > 0 && (
45
+ <div className="flex items-center gap-2 flex-1 w-full sm:w-auto">
46
+ <IconAlertOctagon size={16} className="text-white shrink-0" />
47
+ <span className="paragraph-xsmall-semibold text-white">
48
+ Assinatura cancelada! Em {daysRemaining} dias suas páginas serão tiradas do ar!
49
+ </span>
50
+ </div>
51
+ )}
52
+ <div className="flex items-center gap-1.5 w-full sm:w-auto justify-between sm:justify-start">
53
+ <button
54
+ type="button"
55
+ className="flex items-center justify-center p-1.5 cursor-pointer"
56
+ onClick={onReactivate}
57
+ >
58
+ <span className="paragraph-xsmall-semibold text-white underline">
59
+ Reativar assinatura
60
+ </span>
61
+ </button>
62
+ <button type="button" className="cursor-pointer shrink-0" onClick={dismiss}>
63
+ <IconX size={18} className="text-red-200" />
64
+ </button>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ );
69
+ }
@@ -1,20 +1,20 @@
1
- 'use client';
2
-
3
- import { createContext, useContext } from 'react';
4
- import type { NavItemConfig } from './subcomponents/NavItems';
5
-
6
- interface NavItemsContextValue {
7
- navItems: NavItemConfig[];
8
- setNavItems: (items: NavItemConfig[]) => void;
9
- mobileVariant: 'sheet' | 'tabs';
10
- }
11
-
12
- export const NavItemsContext = createContext<NavItemsContextValue>({
13
- navItems: [],
14
- setNavItems: () => {},
15
- mobileVariant: 'sheet',
16
- });
17
-
18
- export function useNavItemsContext(): NavItemsContextValue {
19
- return useContext(NavItemsContext);
20
- }
1
+ 'use client';
2
+
3
+ import { createContext, useContext } from 'react';
4
+ import type { NavItemConfig } from './subcomponents/NavItems';
5
+
6
+ interface NavItemsContextValue {
7
+ navItems: NavItemConfig[];
8
+ setNavItems: (items: NavItemConfig[]) => void;
9
+ mobileVariant: 'sheet' | 'tabs';
10
+ }
11
+
12
+ export const NavItemsContext = createContext<NavItemsContextValue>({
13
+ navItems: [],
14
+ setNavItems: () => {},
15
+ mobileVariant: 'sheet',
16
+ });
17
+
18
+ export function useNavItemsContext(): NavItemsContextValue {
19
+ return useContext(NavItemsContext);
20
+ }
@@ -1,72 +1,72 @@
1
- 'use client';
2
-
3
- import { ReactNode, useRef, useEffect } from 'react';
4
- import { AppNavigationContext } from '../AppNavigationContext';
5
- import { useNavItemsContext } from '../NavItemsContext';
6
-
7
- function InternalTabsBar({ topOffset = '72px' }: { topOffset?: string }) {
8
- const { navItems } = useNavItemsContext();
9
- const tabsListRef = useRef<HTMLDivElement>(null);
10
- const activeTabRef = useRef<HTMLButtonElement>(null);
11
-
12
- const activeLabel = navItems.find((i) => i.isActive)?.label;
13
-
14
- // Scroll active tab into center when active item changes
15
- useEffect(() => {
16
- if (activeTabRef.current && tabsListRef.current) {
17
- const tabsList = tabsListRef.current;
18
- const activeTabElement = activeTabRef.current;
19
- const tabsListRect = tabsList.getBoundingClientRect();
20
- const activeTabRect = activeTabElement.getBoundingClientRect();
21
- const scrollLeft =
22
- activeTabElement.offsetLeft - tabsListRect.width / 2 + activeTabRect.width / 2;
23
- tabsList.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
24
- }
25
- }, [activeLabel]);
26
-
27
- if (!navItems.length) return null;
28
-
29
- return (
30
- <div
31
- ref={tabsListRef}
32
- className="md:hidden fixed left-0 right-0 z-40 bg-white border-b border-gray-200 flex overflow-x-auto scrollbar-hide"
33
- style={{ top: topOffset }}
34
- >
35
- {navItems.map((item) => (
36
- <button
37
- key={item.label}
38
- ref={item.isActive ? activeTabRef : undefined}
39
- onClick={item.onClick}
40
- className={`flex items-center gap-2 px-4 py-3 shrink-0 border-b-2 transition-colors ${
41
- item.isActive
42
- ? 'border-gray-950 text-gray-950'
43
- : 'border-transparent text-gray-500 hover:text-gray-900'
44
- }`}
45
- >
46
- {item.icon && <item.icon size={16} className="shrink-0" />}
47
- <span
48
- className={
49
- item.isActive ? 'paragraph-small-semibold' : 'paragraph-small-medium'
50
- }
51
- >
52
- {item.label}
53
- </span>
54
- </button>
55
- ))}
56
- </div>
57
- );
58
- }
59
-
60
- export function TabsMobileShell({ children, topOffset }: { children: ReactNode; topOffset?: string }) {
61
- return (
62
- <AppNavigationContext.Provider value={{ breakpoint: 'mobile' }}>
63
- {/*
64
- Children are hidden visually, but NavItems' useEffect still fires inside
65
- display:none — React effects are tied to commit phase, not layout visibility.
66
- This registers navItems in NavItemsContext so InternalTabsBar can read them.
67
- */}
68
- <div className="hidden">{children}</div>
69
- <InternalTabsBar topOffset={topOffset} />
70
- </AppNavigationContext.Provider>
71
- );
72
- }
1
+ 'use client';
2
+
3
+ import { ReactNode, useRef, useEffect } from 'react';
4
+ import { AppNavigationContext } from '../AppNavigationContext';
5
+ import { useNavItemsContext } from '../NavItemsContext';
6
+
7
+ function InternalTabsBar({ topOffset = '72px' }: { topOffset?: string }) {
8
+ const { navItems } = useNavItemsContext();
9
+ const tabsListRef = useRef<HTMLDivElement>(null);
10
+ const activeTabRef = useRef<HTMLButtonElement>(null);
11
+
12
+ const activeLabel = navItems.find((i) => i.isActive)?.label;
13
+
14
+ // Scroll active tab into center when active item changes
15
+ useEffect(() => {
16
+ if (activeTabRef.current && tabsListRef.current) {
17
+ const tabsList = tabsListRef.current;
18
+ const activeTabElement = activeTabRef.current;
19
+ const tabsListRect = tabsList.getBoundingClientRect();
20
+ const activeTabRect = activeTabElement.getBoundingClientRect();
21
+ const scrollLeft =
22
+ activeTabElement.offsetLeft - tabsListRect.width / 2 + activeTabRect.width / 2;
23
+ tabsList.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
24
+ }
25
+ }, [activeLabel]);
26
+
27
+ if (!navItems.length) return null;
28
+
29
+ return (
30
+ <div
31
+ ref={tabsListRef}
32
+ className="md:hidden fixed left-0 right-0 z-40 bg-white border-b border-gray-200 flex overflow-x-auto scrollbar-hide"
33
+ style={{ top: topOffset }}
34
+ >
35
+ {navItems.map((item) => (
36
+ <button
37
+ key={item.label}
38
+ ref={item.isActive ? activeTabRef : undefined}
39
+ onClick={item.onClick}
40
+ className={`flex items-center gap-2 px-4 py-3 shrink-0 border-b-2 transition-colors ${
41
+ item.isActive
42
+ ? 'border-gray-950 text-gray-950'
43
+ : 'border-transparent text-gray-500 hover:text-gray-900'
44
+ }`}
45
+ >
46
+ {item.icon && <item.icon size={16} className="shrink-0" />}
47
+ <span
48
+ className={
49
+ item.isActive ? 'paragraph-small-semibold' : 'paragraph-small-medium'
50
+ }
51
+ >
52
+ {item.label}
53
+ </span>
54
+ </button>
55
+ ))}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ export function TabsMobileShell({ children, topOffset }: { children: ReactNode; topOffset?: string }) {
61
+ return (
62
+ <AppNavigationContext.Provider value={{ breakpoint: 'mobile' }}>
63
+ {/*
64
+ Children are hidden visually, but NavItems' useEffect still fires inside
65
+ display:none — React effects are tied to commit phase, not layout visibility.
66
+ This registers navItems in NavItemsContext so InternalTabsBar can read them.
67
+ */}
68
+ <div className="hidden">{children}</div>
69
+ <InternalTabsBar topOffset={topOffset} />
70
+ </AppNavigationContext.Provider>
71
+ );
72
+ }