@greatapps/common 1.1.91 → 1.1.93

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 (25) hide show
  1. package/dist/components/layouts/MdSideBarNavigation.mjs +18 -3
  2. package/dist/components/layouts/MdSideBarNavigation.mjs.map +1 -1
  3. package/dist/components/navigation/AppNavigation.mjs +8 -4
  4. package/dist/components/navigation/AppNavigation.mjs.map +1 -1
  5. package/dist/components/navigation/NavItem.mjs +12 -2
  6. package/dist/components/navigation/NavItem.mjs.map +1 -1
  7. package/dist/components/navigation/NavItemsContext.mjs +16 -0
  8. package/dist/components/navigation/NavItemsContext.mjs.map +1 -0
  9. package/dist/components/navigation/shells/TabsMobileShell.mjs +58 -0
  10. package/dist/components/navigation/shells/TabsMobileShell.mjs.map +1 -0
  11. package/dist/components/navigation/subcomponents/NavItems.mjs +6 -0
  12. package/dist/components/navigation/subcomponents/NavItems.mjs.map +1 -1
  13. package/dist/components/navigation/subcomponents/ProjectSelector.mjs +9 -1
  14. package/dist/components/navigation/subcomponents/ProjectSelector.mjs.map +1 -1
  15. package/dist/store/useMobileNavbarSheet.mjs +6 -3
  16. package/dist/store/useMobileNavbarSheet.mjs.map +1 -1
  17. package/package.json +1 -1
  18. package/src/components/layouts/MdSideBarNavigation.tsx +16 -1
  19. package/src/components/navigation/AppNavigation.tsx +15 -5
  20. package/src/components/navigation/NavItem.tsx +14 -2
  21. package/src/components/navigation/NavItemsContext.ts +20 -0
  22. package/src/components/navigation/shells/TabsMobileShell.tsx +71 -0
  23. package/src/components/navigation/subcomponents/NavItems.tsx +8 -1
  24. package/src/components/navigation/subcomponents/ProjectSelector.tsx +10 -1
  25. package/src/store/useMobileNavbarSheet.ts +9 -3
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { ChevronLeft } from "lucide-react";
3
4
  import { useMdSidebarStore } from "../../store/useMdSidebarStore";
4
5
  import { cn } from "../../infra/utils/clsx";
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/overlay/Tooltip";
5
7
  function MdSideBarNavigation({
6
8
  className,
7
9
  children,
@@ -20,13 +22,26 @@ function MdSideBarNavigation({
20
22
  "div",
21
23
  {
22
24
  className: `hidden md:flex lg:hidden fixed top-0 bottom-0 left-[73px] z-30 transition-transform duration-300 ease-in-out overflow-visible ${isExpanded ? "translate-x-0" : "-translate-x-full"}`,
23
- children: /* @__PURE__ */ jsx(
25
+ children: /* @__PURE__ */ jsxs(
24
26
  "div",
25
27
  {
26
28
  "data-slot": "md-sidebar-navigation",
27
- className: cn("w-[273px] h-full flex flex-col p-4 bg-white border-r border-gray-200", className),
29
+ className: cn("relative w-[273px] h-full flex flex-col p-4 bg-white border-r border-gray-200", className),
28
30
  ...props,
29
- children
31
+ children: [
32
+ isExpanded && /* @__PURE__ */ jsxs(Tooltip, { children: [
33
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
34
+ "button",
35
+ {
36
+ onClick: collapse,
37
+ className: "absolute top-[68px] -right-4 size-8 flex items-center justify-center rounded-full border border-gray-200 bg-white hover:bg-gray-100 cursor-pointer transition-colors z-40",
38
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16, className: "text-gray-500" })
39
+ }
40
+ ) }),
41
+ /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Recolher menu" })
42
+ ] }),
43
+ children
44
+ ]
30
45
  }
31
46
  )
32
47
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/layouts/MdSideBarNavigation.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { useMdSidebarStore } from \"../../store/useMdSidebarStore\"\r\nimport { cn } from \"../../infra/utils/clsx\"\r\n\r\nfunction MdSideBarNavigation({\r\n className,\r\n children,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n const { isExpanded, collapse } = useMdSidebarStore()\r\n\r\n return (\r\n <>\r\n <div\r\n className={`hidden md:block lg:hidden fixed inset-0 bg-black/50 z-20 transition-opacity duration-300 ${\r\n isExpanded ? \"opacity-100\" : \"opacity-0 pointer-events-none\"\r\n }`}\r\n onClick={collapse}\r\n />\r\n <div\r\n className={`hidden md:flex lg:hidden fixed top-0 bottom-0 left-[73px] z-30 transition-transform duration-300 ease-in-out overflow-visible ${\r\n isExpanded ? \"translate-x-0\" : \"-translate-x-full\"\r\n }`}\r\n >\r\n <div\r\n data-slot=\"md-sidebar-navigation\"\r\n className={cn(\"w-[273px] h-full flex flex-col p-4 bg-white border-r border-gray-200\", className)}\r\n {...props}\r\n >\r\n {children}\r\n </div>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nexport { MdSideBarNavigation };\r\n"],"mappings":";AAcQ,mBACI,KADJ;AAXR,SAAS,yBAAyB;AAClC,SAAS,UAAU;AAEnB,SAAS,oBAAoB;AAAA,EACzB;AAAA,EACA;AAAA,EACA,GAAG;AACP,GAAgC;AAC5B,QAAM,EAAE,YAAY,SAAS,IAAI,kBAAkB;AAEnD,SACI,iCACI;AAAA;AAAA,MAAC;AAAA;AAAA,QACG,WAAW,4FACP,aAAa,gBAAgB,+BACjC;AAAA,QACA,SAAS;AAAA;AAAA,IACb;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACG,WAAW,iIACP,aAAa,kBAAkB,mBACnC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACG,aAAU;AAAA,YACV,WAAW,GAAG,wEAAwE,SAAS;AAAA,YAC9F,GAAG;AAAA,YAEH;AAAA;AAAA,QACL;AAAA;AAAA,IACJ;AAAA,KACJ;AAER;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/layouts/MdSideBarNavigation.tsx"],"sourcesContent":["\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { ChevronLeft } from \"lucide-react\"\r\nimport { useMdSidebarStore } from \"../../store/useMdSidebarStore\"\r\nimport { cn } from \"../../infra/utils/clsx\"\r\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../ui/overlay/Tooltip\"\r\n\r\nfunction MdSideBarNavigation({\r\n className,\r\n children,\r\n ...props\r\n}: React.ComponentProps<\"div\">) {\r\n const { isExpanded, collapse } = useMdSidebarStore()\r\n\r\n return (\r\n <>\r\n <div\r\n className={`hidden md:block lg:hidden fixed inset-0 bg-black/50 z-20 transition-opacity duration-300 ${\r\n isExpanded ? \"opacity-100\" : \"opacity-0 pointer-events-none\"\r\n }`}\r\n onClick={collapse}\r\n />\r\n <div\r\n className={`hidden md:flex lg:hidden fixed top-0 bottom-0 left-[73px] z-30 transition-transform duration-300 ease-in-out overflow-visible ${\r\n isExpanded ? \"translate-x-0\" : \"-translate-x-full\"\r\n }`}\r\n >\r\n <div\r\n data-slot=\"md-sidebar-navigation\"\r\n className={cn(\"relative w-[273px] h-full flex flex-col p-4 bg-white border-r border-gray-200\", className)}\r\n {...props}\r\n >\r\n {isExpanded && (\r\n <Tooltip>\r\n <TooltipTrigger asChild>\r\n <button\r\n onClick={collapse}\r\n className=\"absolute top-[68px] -right-4 size-8 flex items-center justify-center rounded-full border border-gray-200 bg-white hover:bg-gray-100 cursor-pointer transition-colors z-40\"\r\n >\r\n <ChevronLeft size={16} className=\"text-gray-500\" />\r\n </button>\r\n </TooltipTrigger>\r\n <TooltipContent side=\"right\">Recolher menu</TooltipContent>\r\n </Tooltip>\r\n )}\r\n {children}\r\n </div>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nexport { MdSideBarNavigation };\r\n"],"mappings":";AAgBQ,mBACI,KAiBY,YAlBhB;AAbR,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,UAAU;AACnB,SAAS,SAAS,gBAAgB,sBAAsB;AAExD,SAAS,oBAAoB;AAAA,EACzB;AAAA,EACA;AAAA,EACA,GAAG;AACP,GAAgC;AAC5B,QAAM,EAAE,YAAY,SAAS,IAAI,kBAAkB;AAEnD,SACI,iCACI;AAAA;AAAA,MAAC;AAAA;AAAA,QACG,WAAW,4FACP,aAAa,gBAAgB,+BACjC;AAAA,QACA,SAAS;AAAA;AAAA,IACb;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACG,WAAW,iIACP,aAAa,kBAAkB,mBACnC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACG,aAAU;AAAA,YACV,WAAW,GAAG,iFAAiF,SAAS;AAAA,YACvG,GAAG;AAAA,YAEH;AAAA,4BACG,qBAAC,WACG;AAAA,oCAAC,kBAAe,SAAO,MACnB;AAAA,kBAAC;AAAA;AAAA,oBACG,SAAS;AAAA,oBACT,WAAU;AAAA,oBAEV,8BAAC,eAAY,MAAM,IAAI,WAAU,iBAAgB;AAAA;AAAA,gBACrD,GACJ;AAAA,gBACA,oBAAC,kBAAe,MAAK,SAAQ,2BAAa;AAAA,iBAC9C;AAAA,cAEH;AAAA;AAAA;AAAA,QACL;AAAA;AAAA,IACJ;AAAA,KACJ;AAER;","names":[]}
@@ -1,17 +1,21 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
3
4
  import { DesktopShell } from "./shells/DesktopShell";
4
5
  import { MdShell } from "./shells/MdShell";
5
6
  import { MobileShell } from "./shells/MobileShell";
7
+ import { TabsMobileShell } from "./shells/TabsMobileShell";
6
8
  import { NavItems } from "./subcomponents/NavItems";
7
9
  import { ProjectSelector } from "./subcomponents/ProjectSelector";
8
10
  import { PlanCard } from "./subcomponents/PlanCard";
9
11
  import { CreditsCard } from "./subcomponents/CreditsCard";
10
- function AppNavigationRoot({ children }) {
11
- return /* @__PURE__ */ jsxs(Fragment, { children: [
12
+ import { NavItemsContext } from "./NavItemsContext";
13
+ function AppNavigationRoot({ children, mobileVariant = "sheet" }) {
14
+ const [navItems, setNavItems] = useState([]);
15
+ return /* @__PURE__ */ jsxs(NavItemsContext.Provider, { value: { navItems, setNavItems, mobileVariant }, children: [
12
16
  /* @__PURE__ */ jsx(DesktopShell, { children }),
13
17
  /* @__PURE__ */ jsx(MdShell, { children }),
14
- /* @__PURE__ */ jsx(MobileShell, { children })
18
+ mobileVariant === "sheet" ? /* @__PURE__ */ jsx(MobileShell, { children }) : /* @__PURE__ */ jsx(TabsMobileShell, { children })
15
19
  ] });
16
20
  }
17
21
  const AppNavigation = Object.assign(AppNavigationRoot, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/navigation/AppNavigation.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { ReactNode } from 'react';\r\nimport { DesktopShell } from './shells/DesktopShell';\r\nimport { MdShell } from './shells/MdShell';\r\nimport { MobileShell } from './shells/MobileShell';\r\nimport { NavItems } from './subcomponents/NavItems';\r\nimport { ProjectSelector } from './subcomponents/ProjectSelector';\r\nimport { PlanCard } from './subcomponents/PlanCard';\r\nimport { CreditsCard } from './subcomponents/CreditsCard';\r\n\r\nexport type { NavItemConfig } from './subcomponents/NavItems';\r\nexport type { ProjectSelectorProps } from './subcomponents/ProjectSelector';\r\nexport type { PlanCardProps } from './subcomponents/PlanCard';\r\nexport type { CreditsCardProps } from './subcomponents/CreditsCard';\r\n\r\ninterface AppNavigationProps {\r\n children: ReactNode;\r\n}\r\n\r\nfunction AppNavigationRoot({ children }: AppNavigationProps) {\r\n return (\r\n <>\r\n <DesktopShell>{children}</DesktopShell>\r\n <MdShell>{children}</MdShell>\r\n <MobileShell>{children}</MobileShell>\r\n </>\r\n );\r\n}\r\n\r\nexport const AppNavigation = Object.assign(AppNavigationRoot, {\r\n NavItems,\r\n ProjectSelector,\r\n PlanCard,\r\n CreditsCard,\r\n});\r\n"],"mappings":";AAsBI,mBACE,KADF;AAnBJ,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAW5B,SAAS,kBAAkB,EAAE,SAAS,GAAuB;AAC3D,SACE,iCACE;AAAA,wBAAC,gBAAc,UAAS;AAAA,IACxB,oBAAC,WAAS,UAAS;AAAA,IACnB,oBAAC,eAAa,UAAS;AAAA,KACzB;AAEJ;AAEO,MAAM,gBAAgB,OAAO,OAAO,mBAAmB;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/navigation/AppNavigation.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { ReactNode, useState } from 'react';\r\nimport { DesktopShell } from './shells/DesktopShell';\r\nimport { MdShell } from './shells/MdShell';\r\nimport { MobileShell } from './shells/MobileShell';\r\nimport { TabsMobileShell } from './shells/TabsMobileShell';\r\nimport { NavItems } from './subcomponents/NavItems';\r\nimport { ProjectSelector } from './subcomponents/ProjectSelector';\r\nimport { PlanCard } from './subcomponents/PlanCard';\r\nimport { CreditsCard } from './subcomponents/CreditsCard';\r\nimport { NavItemsContext } from './NavItemsContext';\r\nimport type { NavItemConfig } from './subcomponents/NavItems';\r\n\r\nexport type { NavItemConfig } from './subcomponents/NavItems';\r\nexport type { ProjectSelectorProps } from './subcomponents/ProjectSelector';\r\nexport type { PlanCardProps } from './subcomponents/PlanCard';\r\nexport type { CreditsCardProps } from './subcomponents/CreditsCard';\r\n\r\ninterface AppNavigationProps {\r\n children: ReactNode;\r\n mobileVariant?: 'sheet' | 'tabs';\r\n}\r\n\r\nfunction AppNavigationRoot({ children, mobileVariant = 'sheet' }: AppNavigationProps) {\r\n const [navItems, setNavItems] = useState<NavItemConfig[]>([]);\r\n\r\n return (\r\n <NavItemsContext.Provider value={{ navItems, setNavItems, mobileVariant }}>\r\n <DesktopShell>{children}</DesktopShell>\r\n <MdShell>{children}</MdShell>\r\n {mobileVariant === 'sheet' ? (\r\n <MobileShell>{children}</MobileShell>\r\n ) : (\r\n <TabsMobileShell>{children}</TabsMobileShell>\r\n )}\r\n </NavItemsContext.Provider>\r\n );\r\n}\r\n\r\nexport const AppNavigation = Object.assign(AppNavigationRoot, {\r\n NavItems,\r\n ProjectSelector,\r\n PlanCard,\r\n CreditsCard,\r\n});\r\n"],"mappings":";AA4BI,SACE,KADF;AA1BJ,SAAoB,gBAAgB;AACpC,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAahC,SAAS,kBAAkB,EAAE,UAAU,gBAAgB,QAAQ,GAAuB;AACpF,QAAM,CAAC,UAAU,WAAW,IAAI,SAA0B,CAAC,CAAC;AAE5D,SACE,qBAAC,gBAAgB,UAAhB,EAAyB,OAAO,EAAE,UAAU,aAAa,cAAc,GACtE;AAAA,wBAAC,gBAAc,UAAS;AAAA,IACxB,oBAAC,WAAS,UAAS;AAAA,IAClB,kBAAkB,UACjB,oBAAC,eAAa,UAAS,IAEvB,oBAAC,mBAAiB,UAAS;AAAA,KAE/B;AAEJ;AAEO,MAAM,gBAAgB,OAAO,OAAO,mBAAmB;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;","names":[]}
@@ -1,17 +1,27 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useAppNavigationContext } from "./AppNavigationContext";
4
+ import { useMobileNavbarSheet } from "../../store/useMobileNavbarSheet";
3
5
  function NavItem({ icon: Icon, label, onClick, isActive = false }) {
6
+ const { breakpoint } = useAppNavigationContext();
7
+ const { close } = useMobileNavbarSheet();
8
+ const handleClick = () => {
9
+ onClick();
10
+ if (breakpoint === "mobile") {
11
+ close();
12
+ }
13
+ };
4
14
  return /* @__PURE__ */ jsxs(
5
15
  "div",
6
16
  {
7
17
  className: `rounded-md flex items-center gap-2 p-3 cursor-pointer transition-colors ${isActive ? "bg-gray-50" : "bg-white hover:bg-gray-100"} active:bg-gray-50`,
8
- onClick,
18
+ onClick: handleClick,
9
19
  children: [
10
20
  Icon && /* @__PURE__ */ jsx(
11
21
  Icon,
12
22
  {
13
23
  size: 20,
14
- color: isActive ? "var(--color-gray-950)" : "var(--color-gray-400)"
24
+ className: isActive ? "text-gray-950" : "text-gray-400"
15
25
  }
16
26
  ),
17
27
  /* @__PURE__ */ jsx("span", { className: `paragraph-small-medium ${isActive ? "text-gray-950" : "text-gray-500"}`, children: label })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/navigation/NavItem.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { ElementType } from 'react';\r\n\r\ninterface NavItemProps {\r\n icon?: ElementType;\r\n label: string;\r\n onClick: () => void;\r\n isActive?: boolean;\r\n}\r\n\r\nexport function NavItem({ icon: Icon, label, onClick, isActive = false }: NavItemProps) {\r\n return (\r\n <div\r\n className={`rounded-md flex items-center gap-2 p-3 cursor-pointer transition-colors ${\r\n isActive ? 'bg-gray-50' : 'bg-white hover:bg-gray-100'\r\n } active:bg-gray-50`}\r\n onClick={onClick}\r\n >\r\n {Icon && (\r\n <Icon\r\n size={20}\r\n color={isActive ? 'var(--color-gray-950)' : 'var(--color-gray-400)'}\r\n />\r\n )}\r\n <span className={`paragraph-small-medium ${isActive ? 'text-gray-950' : 'text-gray-500'}`}>\r\n {label}\r\n </span>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";AAaI,SAOI,KAPJ;AAFG,SAAS,QAAQ,EAAE,MAAM,MAAM,OAAO,SAAS,WAAW,MAAM,GAAiB;AACtF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,2EACT,WAAW,eAAe,4BAC5B;AAAA,MACA;AAAA,MAEC;AAAA,gBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,OAAO,WAAW,0BAA0B;AAAA;AAAA,QAC9C;AAAA,QAEF,oBAAC,UAAK,WAAW,0BAA0B,WAAW,kBAAkB,eAAe,IACpF,iBACH;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/navigation/NavItem.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { ElementType } from 'react';\r\nimport { useAppNavigationContext } from './AppNavigationContext';\r\nimport { useMobileNavbarSheet } from '../../store/useMobileNavbarSheet';\r\n\r\ninterface NavItemProps {\r\n icon?: ElementType;\r\n label: string;\r\n onClick: () => void;\r\n isActive?: boolean;\r\n}\r\n\r\nexport function NavItem({ icon: Icon, label, onClick, isActive = false }: NavItemProps) {\r\n const { breakpoint } = useAppNavigationContext();\r\n const { close } = useMobileNavbarSheet();\r\n\r\n const handleClick = () => {\r\n onClick();\r\n if (breakpoint === 'mobile') {\r\n close();\r\n }\r\n };\r\n\r\n return (\r\n <div\r\n className={`rounded-md flex items-center gap-2 p-3 cursor-pointer transition-colors ${\r\n isActive ? 'bg-gray-50' : 'bg-white hover:bg-gray-100'\r\n } active:bg-gray-50`}\r\n onClick={handleClick}\r\n >\r\n {Icon && (\r\n <Icon\r\n size={20}\r\n className={isActive ? 'text-gray-950' : 'text-gray-400'}\r\n />\r\n )}\r\n <span className={`paragraph-small-medium ${isActive ? 'text-gray-950' : 'text-gray-500'}`}>\r\n {label}\r\n </span>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";AAyBI,SAOI,KAPJ;AAtBJ,SAAS,+BAA+B;AACxC,SAAS,4BAA4B;AAS9B,SAAS,QAAQ,EAAE,MAAM,MAAM,OAAO,SAAS,WAAW,MAAM,GAAiB;AACtF,QAAM,EAAE,WAAW,IAAI,wBAAwB;AAC/C,QAAM,EAAE,MAAM,IAAI,qBAAqB;AAEvC,QAAM,cAAc,MAAM;AACxB,YAAQ;AACR,QAAI,eAAe,UAAU;AAC3B,YAAM;AAAA,IACR;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,2EACT,WAAW,eAAe,4BAC5B;AAAA,MACA,SAAS;AAAA,MAER;AAAA,gBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,WAAW,WAAW,kBAAkB;AAAA;AAAA,QAC1C;AAAA,QAEF,oBAAC,UAAK,WAAW,0BAA0B,WAAW,kBAAkB,eAAe,IACpF,iBACH;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { createContext, useContext } from "react";
3
+ const NavItemsContext = createContext({
4
+ navItems: [],
5
+ setNavItems: () => {
6
+ },
7
+ mobileVariant: "sheet"
8
+ });
9
+ function useNavItemsContext() {
10
+ return useContext(NavItemsContext);
11
+ }
12
+ export {
13
+ NavItemsContext,
14
+ useNavItemsContext
15
+ };
16
+ //# sourceMappingURL=NavItemsContext.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/navigation/NavItemsContext.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext } from 'react';\nimport type { NavItemConfig } from './subcomponents/NavItems';\n\ninterface NavItemsContextValue {\n navItems: NavItemConfig[];\n setNavItems: (items: NavItemConfig[]) => void;\n mobileVariant: 'sheet' | 'tabs';\n}\n\nexport const NavItemsContext = createContext<NavItemsContextValue>({\n navItems: [],\n setNavItems: () => {},\n mobileVariant: 'sheet',\n});\n\nexport function useNavItemsContext(): NavItemsContextValue {\n return useContext(NavItemsContext);\n}\n"],"mappings":";AAEA,SAAS,eAAe,kBAAkB;AASnC,MAAM,kBAAkB,cAAoC;AAAA,EACjE,UAAU,CAAC;AAAA,EACX,aAAa,MAAM;AAAA,EAAC;AAAA,EACpB,eAAe;AACjB,CAAC;AAEM,SAAS,qBAA2C;AACzD,SAAO,WAAW,eAAe;AACnC;","names":[]}
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useRef, useEffect } from "react";
4
+ import { AppNavigationContext } from "../AppNavigationContext";
5
+ import { useNavItemsContext } from "../NavItemsContext";
6
+ function InternalTabsBar() {
7
+ const { navItems } = useNavItemsContext();
8
+ const tabsListRef = useRef(null);
9
+ const activeTabRef = useRef(null);
10
+ const activeLabel = navItems.find((i) => i.isActive)?.label;
11
+ useEffect(() => {
12
+ if (activeTabRef.current && tabsListRef.current) {
13
+ const tabsList = tabsListRef.current;
14
+ const activeTabElement = activeTabRef.current;
15
+ const tabsListRect = tabsList.getBoundingClientRect();
16
+ const activeTabRect = activeTabElement.getBoundingClientRect();
17
+ const scrollLeft = activeTabElement.offsetLeft - tabsListRect.width / 2 + activeTabRect.width / 2;
18
+ tabsList.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
19
+ }
20
+ }, [activeLabel]);
21
+ if (!navItems.length) return null;
22
+ return /* @__PURE__ */ jsx(
23
+ "div",
24
+ {
25
+ ref: tabsListRef,
26
+ className: "md:hidden fixed top-[72px] left-0 right-0 z-40 bg-white border-b border-gray-200 flex overflow-x-auto scrollbar-hide",
27
+ children: navItems.map((item) => /* @__PURE__ */ jsxs(
28
+ "button",
29
+ {
30
+ ref: item.isActive ? activeTabRef : void 0,
31
+ onClick: item.onClick,
32
+ className: `flex items-center gap-2 px-4 py-3 shrink-0 border-b-2 transition-colors ${item.isActive ? "border-gray-950 text-gray-950" : "border-transparent text-gray-500 hover:text-gray-900"}`,
33
+ children: [
34
+ item.icon && /* @__PURE__ */ jsx(item.icon, { size: 16, className: "shrink-0" }),
35
+ /* @__PURE__ */ jsx(
36
+ "span",
37
+ {
38
+ className: item.isActive ? "paragraph-small-semibold" : "paragraph-small-medium",
39
+ children: item.label
40
+ }
41
+ )
42
+ ]
43
+ },
44
+ item.label
45
+ ))
46
+ }
47
+ );
48
+ }
49
+ function TabsMobileShell({ children }) {
50
+ return /* @__PURE__ */ jsxs(AppNavigationContext.Provider, { value: { breakpoint: "mobile" }, children: [
51
+ /* @__PURE__ */ jsx("div", { className: "hidden", children }),
52
+ /* @__PURE__ */ jsx(InternalTabsBar, {})
53
+ ] });
54
+ }
55
+ export {
56
+ TabsMobileShell
57
+ };
58
+ //# sourceMappingURL=TabsMobileShell.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/navigation/shells/TabsMobileShell.tsx"],"sourcesContent":["'use client';\n\nimport { ReactNode, useRef, useEffect } from 'react';\nimport { AppNavigationContext } from '../AppNavigationContext';\nimport { useNavItemsContext } from '../NavItemsContext';\n\nfunction InternalTabsBar() {\n const { navItems } = useNavItemsContext();\n const tabsListRef = useRef<HTMLDivElement>(null);\n const activeTabRef = useRef<HTMLButtonElement>(null);\n\n const activeLabel = navItems.find((i) => i.isActive)?.label;\n\n // Scroll active tab into center when active item changes\n useEffect(() => {\n if (activeTabRef.current && tabsListRef.current) {\n const tabsList = tabsListRef.current;\n const activeTabElement = activeTabRef.current;\n const tabsListRect = tabsList.getBoundingClientRect();\n const activeTabRect = activeTabElement.getBoundingClientRect();\n const scrollLeft =\n activeTabElement.offsetLeft - tabsListRect.width / 2 + activeTabRect.width / 2;\n tabsList.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });\n }\n }, [activeLabel]);\n\n if (!navItems.length) return null;\n\n return (\n <div\n ref={tabsListRef}\n className=\"md:hidden fixed top-[72px] left-0 right-0 z-40 bg-white border-b border-gray-200 flex overflow-x-auto scrollbar-hide\"\n >\n {navItems.map((item) => (\n <button\n key={item.label}\n ref={item.isActive ? activeTabRef : undefined}\n onClick={item.onClick}\n className={`flex items-center gap-2 px-4 py-3 shrink-0 border-b-2 transition-colors ${\n item.isActive\n ? 'border-gray-950 text-gray-950'\n : 'border-transparent text-gray-500 hover:text-gray-900'\n }`}\n >\n {item.icon && <item.icon size={16} className=\"shrink-0\" />}\n <span\n className={\n item.isActive ? 'paragraph-small-semibold' : 'paragraph-small-medium'\n }\n >\n {item.label}\n </span>\n </button>\n ))}\n </div>\n );\n}\n\nexport function TabsMobileShell({ children }: { children: ReactNode }) {\n return (\n <AppNavigationContext.Provider value={{ breakpoint: 'mobile' }}>\n {/*\n Children are hidden visually, but NavItems' useEffect still fires inside\n display:none — React effects are tied to commit phase, not layout visibility.\n This registers navItems in NavItemsContext so InternalTabsBar can read them.\n */}\n <div className=\"hidden\">{children}</div>\n <InternalTabsBar />\n </AppNavigationContext.Provider>\n );\n}\n"],"mappings":";AAkCQ,SAUgB,KAVhB;AAhCR,SAAoB,QAAQ,iBAAiB;AAC7C,SAAS,4BAA4B;AACrC,SAAS,0BAA0B;AAEnC,SAAS,kBAAkB;AACzB,QAAM,EAAE,SAAS,IAAI,mBAAmB;AACxC,QAAM,cAAc,OAAuB,IAAI;AAC/C,QAAM,eAAe,OAA0B,IAAI;AAEnD,QAAM,cAAc,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAGtD,YAAU,MAAM;AACd,QAAI,aAAa,WAAW,YAAY,SAAS;AAC/C,YAAM,WAAW,YAAY;AAC7B,YAAM,mBAAmB,aAAa;AACtC,YAAM,eAAe,SAAS,sBAAsB;AACpD,YAAM,gBAAgB,iBAAiB,sBAAsB;AAC7D,YAAM,aACJ,iBAAiB,aAAa,aAAa,QAAQ,IAAI,cAAc,QAAQ;AAC/E,eAAS,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,UAAU,GAAG,UAAU,SAAS,CAAC;AAAA,IACzE;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,MAAI,CAAC,SAAS,OAAQ,QAAO;AAE7B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAU;AAAA,MAET,mBAAS,IAAI,CAAC,SACb;AAAA,QAAC;AAAA;AAAA,UAEC,KAAK,KAAK,WAAW,eAAe;AAAA,UACpC,SAAS,KAAK;AAAA,UACd,WAAW,2EACT,KAAK,WACD,kCACA,sDACN;AAAA,UAEC;AAAA,iBAAK,QAAQ,oBAAC,KAAK,MAAL,EAAU,MAAM,IAAI,WAAU,YAAW;AAAA,YACxD;AAAA,cAAC;AAAA;AAAA,gBACC,WACE,KAAK,WAAW,6BAA6B;AAAA,gBAG9C,eAAK;AAAA;AAAA,YACR;AAAA;AAAA;AAAA,QAhBK,KAAK;AAAA,MAiBZ,CACD;AAAA;AAAA,EACH;AAEJ;AAEO,SAAS,gBAAgB,EAAE,SAAS,GAA4B;AACrE,SACE,qBAAC,qBAAqB,UAArB,EAA8B,OAAO,EAAE,YAAY,SAAS,GAM3D;AAAA,wBAAC,SAAI,WAAU,UAAU,UAAS;AAAA,IAClC,oBAAC,mBAAgB;AAAA,KACnB;AAEJ;","names":[]}
@@ -1,7 +1,13 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
+ import { useEffect } from "react";
3
4
  import { NavItem } from "../NavItem";
5
+ import { useNavItemsContext } from "../NavItemsContext";
4
6
  function NavItems({ items }) {
7
+ const { setNavItems } = useNavItemsContext();
8
+ useEffect(() => {
9
+ setNavItems(items);
10
+ }, [items, setNavItems]);
5
11
  return /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: items.map((item) => /* @__PURE__ */ jsx(
6
12
  NavItem,
7
13
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/navigation/subcomponents/NavItems.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { ElementType } from 'react';\r\nimport { NavItem } from '../NavItem';\r\n\r\nexport interface NavItemConfig {\r\n label: string;\r\n isActive: boolean;\r\n icon: ElementType;\r\n onClick: () => void;\r\n}\r\n\r\ninterface NavItemsProps {\r\n items: NavItemConfig[];\r\n}\r\n\r\nexport function NavItems({ items }: NavItemsProps) {\r\n return (\r\n <div className=\"flex flex-col\">\r\n {items.map((item) => (\r\n <NavItem\r\n key={item.label}\r\n label={item.label}\r\n icon={item.icon}\r\n isActive={item.isActive}\r\n onClick={item.onClick}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n"],"mappings":";AAoBQ;AAjBR,SAAS,eAAe;AAajB,SAAS,SAAS,EAAE,MAAM,GAAkB;AACjD,SACE,oBAAC,SAAI,WAAU,iBACZ,gBAAM,IAAI,CAAC,SACV;AAAA,IAAC;AAAA;AAAA,MAEC,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,SAAS,KAAK;AAAA;AAAA,IAJT,KAAK;AAAA,EAKZ,CACD,GACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/navigation/subcomponents/NavItems.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { ElementType, useEffect } from 'react';\r\nimport { NavItem } from '../NavItem';\r\nimport { useNavItemsContext } from '../NavItemsContext';\r\n\r\nexport interface NavItemConfig {\r\n label: string;\r\n isActive: boolean;\r\n icon: ElementType;\r\n onClick: () => void;\r\n}\r\n\r\ninterface NavItemsProps {\r\n items: NavItemConfig[];\r\n}\r\n\r\nexport function NavItems({ items }: NavItemsProps) {\r\n const { setNavItems } = useNavItemsContext();\r\n\r\n useEffect(() => {\r\n setNavItems(items);\r\n }, [items, setNavItems]);\r\n\r\n return (\r\n <div className=\"flex flex-col\">\r\n {items.map((item) => (\r\n <NavItem\r\n key={item.label}\r\n label={item.label}\r\n icon={item.icon}\r\n isActive={item.isActive}\r\n onClick={item.onClick}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n"],"mappings":";AA2BQ;AAzBR,SAAsB,iBAAiB;AACvC,SAAS,eAAe;AACxB,SAAS,0BAA0B;AAa5B,SAAS,SAAS,EAAE,MAAM,GAAkB;AACjD,QAAM,EAAE,YAAY,IAAI,mBAAmB;AAE3C,YAAU,MAAM;AACd,gBAAY,KAAK;AAAA,EACnB,GAAG,CAAC,OAAO,WAAW,CAAC;AAEvB,SACE,oBAAC,SAAI,WAAU,iBACZ,gBAAM,IAAI,CAAC,SACV;AAAA,IAAC;AAAA;AAAA,MAEC,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,SAAS,KAAK;AAAA;AAAA,IAJT,KAAK;AAAA,EAKZ,CACD,GACH;AAEJ;","names":[]}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useState, useRef } from "react";
3
+ import { useState, useRef, useEffect } from "react";
4
+ import { useMobileNavbarSheet } from "../../../store/useMobileNavbarSheet";
4
5
  import Image from "next/image";
5
6
  import { useAppNavigationContext } from "../AppNavigationContext";
6
7
  import { ProjectSelectorPopover } from "../ProjectSelectorPopover";
@@ -25,6 +26,13 @@ function ProjectSelector({
25
26
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
26
27
  const [activeSection, setActiveSection] = useState(null);
27
28
  const titleRef = useRef(null);
29
+ const { openProjectList, clearProjectList } = useMobileNavbarSheet();
30
+ useEffect(() => {
31
+ if (openProjectList && breakpoint === "mobile") {
32
+ setActiveSection("projects");
33
+ clearProjectList();
34
+ }
35
+ }, [openProjectList, breakpoint, clearProjectList]);
28
36
  if (breakpoint === "desktop" || breakpoint === "md") {
29
37
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
30
38
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 truncate select-none", ref: titleRef, children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/navigation/subcomponents/ProjectSelector.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useState, useRef, RefObject } from 'react';\r\nimport Image from 'next/image';\r\nimport { useAppNavigationContext } from '../AppNavigationContext';\r\nimport { ProjectSelectorPopover } from '../ProjectSelectorPopover';\r\nimport { UsersSelectorPopover } from '../../layouts/UsersSelectorPopover';\r\nimport { Button, Command, CommandEmpty, CommandInput, CommandItem, CommandList } from '../../../index';\r\nimport { ChevronDown, Plus, Settings } from 'lucide-react';\r\nimport type { NavigationProject } from '../types';\r\n\r\nexport interface ProjectSelectorProps {\r\n projects: NavigationProject[];\r\n selectedProject: NavigationProject | null;\r\n isLoading?: boolean;\r\n onSelectProject: (project: NavigationProject) => void;\r\n onAddProject: () => void;\r\n onManageProjects: () => void;\r\n}\r\n\r\nfunction ProjectItemSkeleton() {\r\n return (\r\n <div className=\"flex items-center gap-2 p-2\">\r\n <div className=\"size-8 rounded-lg bg-gray-100 animate-pulse shrink-0\" />\r\n <div className=\"h-3.5 w-32 rounded bg-gray-100 animate-pulse\" />\r\n </div>\r\n );\r\n}\r\n\r\nexport function ProjectSelector({\r\n projects,\r\n selectedProject,\r\n isLoading,\r\n onSelectProject,\r\n onAddProject,\r\n onManageProjects,\r\n}: ProjectSelectorProps) {\r\n const { breakpoint } = useAppNavigationContext();\r\n const [isPopoverOpen, setIsPopoverOpen] = useState(false);\r\n const [activeSection, setActiveSection] = useState<'projects' | null>(null);\r\n const titleRef = useRef<HTMLDivElement>(null);\r\n\r\n // Desktop and md: title + popover + users selector (same visual as current gpages sidebars)\r\n if (breakpoint === 'desktop' || breakpoint === 'md') {\r\n return (\r\n <div className=\"flex items-center justify-between\">\r\n <div className=\"flex items-center gap-2 truncate select-none\" ref={titleRef}>\r\n {isLoading ? (\r\n <div className=\"h-4 w-32 rounded bg-gray-100 animate-pulse\" />\r\n ) : (\r\n <span\r\n className=\"cursor-pointer paragraph-medium-semibold text-gray-600 truncate\"\r\n onClick={() => setIsPopoverOpen((v) => !v)}\r\n >\r\n {selectedProject?.title && selectedProject.title.length > 15\r\n ? `${selectedProject.title.slice(0, 15)}…`\r\n : selectedProject?.title}\r\n </span>\r\n )}\r\n <ProjectSelectorPopover\r\n open={isPopoverOpen}\r\n setOpen={setIsPopoverOpen}\r\n titleRef={titleRef as unknown as RefObject<HTMLSpanElement | null>}\r\n projects={projects}\r\n selectedProject={selectedProject}\r\n onSelectProject={onSelectProject}\r\n onAddProject={onAddProject}\r\n onManageProjects={onManageProjects}\r\n />\r\n </div>\r\n <div className=\"flex items-center gap-2\">\r\n <UsersSelectorPopover projectId={selectedProject?.id} />\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n // Mobile: inline collapsible accordion\r\n return (\r\n <div className=\"flex flex-col gap-2.5\">\r\n <div className=\"flex items-center justify-between\">\r\n <div className=\"flex items-center gap-1\">\r\n <span className=\"paragraph-medium-semibold text-gray-600\">{selectedProject?.title}</span>\r\n <ChevronDown\r\n size={20}\r\n onClick={() => setActiveSection(activeSection === 'projects' ? null : 'projects')}\r\n className={`transition-transform duration-200 cursor-pointer ${\r\n activeSection === 'projects' ? 'rotate-180 text-gray-950' : 'text-gray-400'\r\n }`}\r\n />\r\n </div>\r\n <UsersSelectorPopover projectId={selectedProject?.id} />\r\n </div>\r\n {activeSection === 'projects' && (\r\n <div className=\"flex flex-col p-3 gap-2 border border-gray-200 rounded-lg animate-in fade-in duration-400\">\r\n <div className=\"flex items-center justify-between\">\r\n <span className=\"paragraph-small-semibold\">Projetos</span>\r\n <div className=\"flex items-center gap-1.5\">\r\n <Button variant=\"secondary\" className=\"h-8! paragraph-small-semibold text-gray-950\" onClick={onAddProject}>\r\n <Plus size={16} />\r\n Adicionar\r\n </Button>\r\n <Button variant=\"secondary\" className=\"p-0 size-8\" onClick={onManageProjects}>\r\n <Settings size={18} />\r\n </Button>\r\n </div>\r\n </div>\r\n <Command className=\"gap-2\">\r\n <CommandInput placeholder=\"Busque aqui\" />\r\n <CommandList className=\"custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]\">\r\n <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>\r\n {isLoading ? (\r\n <>\r\n <ProjectItemSkeleton />\r\n <ProjectItemSkeleton />\r\n </>\r\n ) : (\r\n projects.map((project) => (\r\n <CommandItem\r\n key={project.id}\r\n value={project.title}\r\n className={selectedProject?.id === project.id ? 'bg-gray-50' : ''}\r\n onSelect={() => { onSelectProject(project); setActiveSection(null); }}\r\n >\r\n <Image\r\n alt=\"Icon Project\"\r\n src={\r\n project.photo\r\n ? `${process.env.NEXT_PUBLIC_STORAGE_URL}/${project.photo}`\r\n : '/images/Logomark.png'\r\n }\r\n width={32}\r\n height={32}\r\n className=\"rounded-lg\"\r\n />\r\n {project.title}\r\n </CommandItem>\r\n ))\r\n )}\r\n </CommandList>\r\n </Command>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/navigation/subcomponents/ProjectSelector.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useState, useRef, useEffect, RefObject } from 'react';\r\nimport { useMobileNavbarSheet } from '../../../store/useMobileNavbarSheet';\r\nimport Image from 'next/image';\r\nimport { useAppNavigationContext } from '../AppNavigationContext';\r\nimport { ProjectSelectorPopover } from '../ProjectSelectorPopover';\r\nimport { UsersSelectorPopover } from '../../layouts/UsersSelectorPopover';\r\nimport { Button, Command, CommandEmpty, CommandInput, CommandItem, CommandList } from '../../../index';\r\nimport { ChevronDown, Plus, Settings } from 'lucide-react';\r\nimport type { NavigationProject } from '../types';\r\n\r\nexport interface ProjectSelectorProps {\r\n projects: NavigationProject[];\r\n selectedProject: NavigationProject | null;\r\n isLoading?: boolean;\r\n onSelectProject: (project: NavigationProject) => void;\r\n onAddProject: () => void;\r\n onManageProjects: () => void;\r\n}\r\n\r\nfunction ProjectItemSkeleton() {\r\n return (\r\n <div className=\"flex items-center gap-2 p-2\">\r\n <div className=\"size-8 rounded-lg bg-gray-100 animate-pulse shrink-0\" />\r\n <div className=\"h-3.5 w-32 rounded bg-gray-100 animate-pulse\" />\r\n </div>\r\n );\r\n}\r\n\r\nexport function ProjectSelector({\r\n projects,\r\n selectedProject,\r\n isLoading,\r\n onSelectProject,\r\n onAddProject,\r\n onManageProjects,\r\n}: ProjectSelectorProps) {\r\n const { breakpoint } = useAppNavigationContext();\r\n const [isPopoverOpen, setIsPopoverOpen] = useState(false);\r\n const [activeSection, setActiveSection] = useState<'projects' | null>(null);\r\n const titleRef = useRef<HTMLDivElement>(null);\r\n const { openProjectList, clearProjectList } = useMobileNavbarSheet();\r\n\r\n useEffect(() => {\r\n if (openProjectList && breakpoint === 'mobile') {\r\n setActiveSection('projects');\r\n clearProjectList();\r\n }\r\n }, [openProjectList, breakpoint, clearProjectList]);\r\n\r\n // Desktop and md: title + popover + users selector (same visual as current gpages sidebars)\r\n if (breakpoint === 'desktop' || breakpoint === 'md') {\r\n return (\r\n <div className=\"flex items-center justify-between\">\r\n <div className=\"flex items-center gap-2 truncate select-none\" ref={titleRef}>\r\n {isLoading ? (\r\n <div className=\"h-4 w-32 rounded bg-gray-100 animate-pulse\" />\r\n ) : (\r\n <span\r\n className=\"cursor-pointer paragraph-medium-semibold text-gray-600 truncate\"\r\n onClick={() => setIsPopoverOpen((v) => !v)}\r\n >\r\n {selectedProject?.title && selectedProject.title.length > 15\r\n ? `${selectedProject.title.slice(0, 15)}…`\r\n : selectedProject?.title}\r\n </span>\r\n )}\r\n <ProjectSelectorPopover\r\n open={isPopoverOpen}\r\n setOpen={setIsPopoverOpen}\r\n titleRef={titleRef as unknown as RefObject<HTMLSpanElement | null>}\r\n projects={projects}\r\n selectedProject={selectedProject}\r\n onSelectProject={onSelectProject}\r\n onAddProject={onAddProject}\r\n onManageProjects={onManageProjects}\r\n />\r\n </div>\r\n <div className=\"flex items-center gap-2\">\r\n <UsersSelectorPopover projectId={selectedProject?.id} />\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n // Mobile: inline collapsible accordion\r\n return (\r\n <div className=\"flex flex-col gap-2.5\">\r\n <div className=\"flex items-center justify-between\">\r\n <div className=\"flex items-center gap-1\">\r\n <span className=\"paragraph-medium-semibold text-gray-600\">{selectedProject?.title}</span>\r\n <ChevronDown\r\n size={20}\r\n onClick={() => setActiveSection(activeSection === 'projects' ? null : 'projects')}\r\n className={`transition-transform duration-200 cursor-pointer ${\r\n activeSection === 'projects' ? 'rotate-180 text-gray-950' : 'text-gray-400'\r\n }`}\r\n />\r\n </div>\r\n <UsersSelectorPopover projectId={selectedProject?.id} />\r\n </div>\r\n {activeSection === 'projects' && (\r\n <div className=\"flex flex-col p-3 gap-2 border border-gray-200 rounded-lg animate-in fade-in duration-400\">\r\n <div className=\"flex items-center justify-between\">\r\n <span className=\"paragraph-small-semibold\">Projetos</span>\r\n <div className=\"flex items-center gap-1.5\">\r\n <Button variant=\"secondary\" className=\"h-8! paragraph-small-semibold text-gray-950\" onClick={onAddProject}>\r\n <Plus size={16} />\r\n Adicionar\r\n </Button>\r\n <Button variant=\"secondary\" className=\"p-0 size-8\" onClick={onManageProjects}>\r\n <Settings size={18} />\r\n </Button>\r\n </div>\r\n </div>\r\n <Command className=\"gap-2\">\r\n <CommandInput placeholder=\"Busque aqui\" />\r\n <CommandList className=\"custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]\">\r\n <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>\r\n {isLoading ? (\r\n <>\r\n <ProjectItemSkeleton />\r\n <ProjectItemSkeleton />\r\n </>\r\n ) : (\r\n projects.map((project) => (\r\n <CommandItem\r\n key={project.id}\r\n value={project.title}\r\n className={selectedProject?.id === project.id ? 'bg-gray-50' : ''}\r\n onSelect={() => { onSelectProject(project); setActiveSection(null); }}\r\n >\r\n <Image\r\n alt=\"Icon Project\"\r\n src={\r\n project.photo\r\n ? `${process.env.NEXT_PUBLIC_STORAGE_URL}/${project.photo}`\r\n : '/images/Logomark.png'\r\n }\r\n width={32}\r\n height={32}\r\n className=\"rounded-lg\"\r\n />\r\n {project.title}\r\n </CommandItem>\r\n ))\r\n )}\r\n </CommandList>\r\n </Command>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":[]}
@@ -1,9 +1,12 @@
1
1
  import { create } from "zustand";
2
2
  const useMobileNavbarSheet = create((set) => ({
3
3
  isOpen: false,
4
- open: () => set({ isOpen: true }),
5
- close: () => set({ isOpen: false }),
6
- toggle: () => set((state) => ({ isOpen: !state.isOpen }))
4
+ openProjectList: false,
5
+ open: () => set({ isOpen: true, openProjectList: false }),
6
+ close: () => set({ isOpen: false, openProjectList: false }),
7
+ toggle: () => set((state) => ({ isOpen: !state.isOpen, openProjectList: false })),
8
+ openWithProjectList: () => set({ isOpen: true, openProjectList: true }),
9
+ clearProjectList: () => set({ openProjectList: false })
7
10
  }));
8
11
  export {
9
12
  useMobileNavbarSheet
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/store/useMobileNavbarSheet.ts"],"sourcesContent":["import { create } from 'zustand';\r\n\r\ninterface MobileNavbarSheetStore {\r\n isOpen: boolean;\r\n open: () => void;\r\n close: () => void;\r\n toggle: () => void;\r\n}\r\n\r\nexport const useMobileNavbarSheet = create<MobileNavbarSheetStore>((set) => ({\r\n isOpen: false,\r\n open: () => set({ isOpen: true }),\r\n close: () => set({ isOpen: false }),\r\n toggle: () => set((state) => ({ isOpen: !state.isOpen })),\r\n}));\r\n"],"mappings":"AAAA,SAAS,cAAc;AAShB,MAAM,uBAAuB,OAA+B,CAAC,SAAS;AAAA,EAC3E,QAAQ;AAAA,EACR,MAAM,MAAM,IAAI,EAAE,QAAQ,KAAK,CAAC;AAAA,EAChC,OAAO,MAAM,IAAI,EAAE,QAAQ,MAAM,CAAC;AAAA,EAClC,QAAQ,MAAM,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,OAAO,EAAE;AAC1D,EAAE;","names":[]}
1
+ {"version":3,"sources":["../../src/store/useMobileNavbarSheet.ts"],"sourcesContent":["import { create } from 'zustand';\r\n\r\ninterface MobileNavbarSheetStore {\r\n isOpen: boolean;\r\n openProjectList: boolean;\r\n open: () => void;\r\n close: () => void;\r\n toggle: () => void;\r\n openWithProjectList: () => void;\r\n clearProjectList: () => void;\r\n}\r\n\r\nexport const useMobileNavbarSheet = create<MobileNavbarSheetStore>((set) => ({\r\n isOpen: false,\r\n openProjectList: false,\r\n open: () => set({ isOpen: true, openProjectList: false }),\r\n close: () => set({ isOpen: false, openProjectList: false }),\r\n toggle: () => set((state) => ({ isOpen: !state.isOpen, openProjectList: false })),\r\n openWithProjectList: () => set({ isOpen: true, openProjectList: true }),\r\n clearProjectList: () => set({ openProjectList: false }),\r\n}));\r\n"],"mappings":"AAAA,SAAS,cAAc;AAYhB,MAAM,uBAAuB,OAA+B,CAAC,SAAS;AAAA,EAC3E,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,MAAM,MAAM,IAAI,EAAE,QAAQ,MAAM,iBAAiB,MAAM,CAAC;AAAA,EACxD,OAAO,MAAM,IAAI,EAAE,QAAQ,OAAO,iBAAiB,MAAM,CAAC;AAAA,EAC1D,QAAQ,MAAM,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,QAAQ,iBAAiB,MAAM,EAAE;AAAA,EAChF,qBAAqB,MAAM,IAAI,EAAE,QAAQ,MAAM,iBAAiB,KAAK,CAAC;AAAA,EACtE,kBAAkB,MAAM,IAAI,EAAE,iBAAiB,MAAM,CAAC;AACxD,EAAE;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.91",
3
+ "version": "1.1.93",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -1,8 +1,10 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react"
4
+ import { ChevronLeft } from "lucide-react"
4
5
  import { useMdSidebarStore } from "../../store/useMdSidebarStore"
5
6
  import { cn } from "../../infra/utils/clsx"
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/overlay/Tooltip"
6
8
 
7
9
  function MdSideBarNavigation({
8
10
  className,
@@ -26,9 +28,22 @@ function MdSideBarNavigation({
26
28
  >
27
29
  <div
28
30
  data-slot="md-sidebar-navigation"
29
- className={cn("w-[273px] h-full flex flex-col p-4 bg-white border-r border-gray-200", className)}
31
+ className={cn("relative w-[273px] h-full flex flex-col p-4 bg-white border-r border-gray-200", className)}
30
32
  {...props}
31
33
  >
34
+ {isExpanded && (
35
+ <Tooltip>
36
+ <TooltipTrigger asChild>
37
+ <button
38
+ onClick={collapse}
39
+ className="absolute top-[68px] -right-4 size-8 flex items-center justify-center rounded-full border border-gray-200 bg-white hover:bg-gray-100 cursor-pointer transition-colors z-40"
40
+ >
41
+ <ChevronLeft size={16} className="text-gray-500" />
42
+ </button>
43
+ </TooltipTrigger>
44
+ <TooltipContent side="right">Recolher menu</TooltipContent>
45
+ </Tooltip>
46
+ )}
32
47
  {children}
33
48
  </div>
34
49
  </div>
@@ -1,13 +1,16 @@
1
1
  'use client';
2
2
 
3
- import { ReactNode } from 'react';
3
+ import { ReactNode, useState } from 'react';
4
4
  import { DesktopShell } from './shells/DesktopShell';
5
5
  import { MdShell } from './shells/MdShell';
6
6
  import { MobileShell } from './shells/MobileShell';
7
+ import { TabsMobileShell } from './shells/TabsMobileShell';
7
8
  import { NavItems } from './subcomponents/NavItems';
8
9
  import { ProjectSelector } from './subcomponents/ProjectSelector';
9
10
  import { PlanCard } from './subcomponents/PlanCard';
10
11
  import { CreditsCard } from './subcomponents/CreditsCard';
12
+ import { NavItemsContext } from './NavItemsContext';
13
+ import type { NavItemConfig } from './subcomponents/NavItems';
11
14
 
12
15
  export type { NavItemConfig } from './subcomponents/NavItems';
13
16
  export type { ProjectSelectorProps } from './subcomponents/ProjectSelector';
@@ -16,15 +19,22 @@ export type { CreditsCardProps } from './subcomponents/CreditsCard';
16
19
 
17
20
  interface AppNavigationProps {
18
21
  children: ReactNode;
22
+ mobileVariant?: 'sheet' | 'tabs';
19
23
  }
20
24
 
21
- function AppNavigationRoot({ children }: AppNavigationProps) {
25
+ function AppNavigationRoot({ children, mobileVariant = 'sheet' }: AppNavigationProps) {
26
+ const [navItems, setNavItems] = useState<NavItemConfig[]>([]);
27
+
22
28
  return (
23
- <>
29
+ <NavItemsContext.Provider value={{ navItems, setNavItems, mobileVariant }}>
24
30
  <DesktopShell>{children}</DesktopShell>
25
31
  <MdShell>{children}</MdShell>
26
- <MobileShell>{children}</MobileShell>
27
- </>
32
+ {mobileVariant === 'sheet' ? (
33
+ <MobileShell>{children}</MobileShell>
34
+ ) : (
35
+ <TabsMobileShell>{children}</TabsMobileShell>
36
+ )}
37
+ </NavItemsContext.Provider>
28
38
  );
29
39
  }
30
40
 
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { ElementType } from 'react';
4
+ import { useAppNavigationContext } from './AppNavigationContext';
5
+ import { useMobileNavbarSheet } from '../../store/useMobileNavbarSheet';
4
6
 
5
7
  interface NavItemProps {
6
8
  icon?: ElementType;
@@ -10,17 +12,27 @@ interface NavItemProps {
10
12
  }
11
13
 
12
14
  export function NavItem({ icon: Icon, label, onClick, isActive = false }: NavItemProps) {
15
+ const { breakpoint } = useAppNavigationContext();
16
+ const { close } = useMobileNavbarSheet();
17
+
18
+ const handleClick = () => {
19
+ onClick();
20
+ if (breakpoint === 'mobile') {
21
+ close();
22
+ }
23
+ };
24
+
13
25
  return (
14
26
  <div
15
27
  className={`rounded-md flex items-center gap-2 p-3 cursor-pointer transition-colors ${
16
28
  isActive ? 'bg-gray-50' : 'bg-white hover:bg-gray-100'
17
29
  } active:bg-gray-50`}
18
- onClick={onClick}
30
+ onClick={handleClick}
19
31
  >
20
32
  {Icon && (
21
33
  <Icon
22
34
  size={20}
23
- color={isActive ? 'var(--color-gray-950)' : 'var(--color-gray-400)'}
35
+ className={isActive ? 'text-gray-950' : 'text-gray-400'}
24
36
  />
25
37
  )}
26
38
  <span className={`paragraph-small-medium ${isActive ? 'text-gray-950' : 'text-gray-500'}`}>
@@ -0,0 +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
+ }
@@ -0,0 +1,71 @@
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() {
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 top-[72px] left-0 right-0 z-40 bg-white border-b border-gray-200 flex overflow-x-auto scrollbar-hide"
33
+ >
34
+ {navItems.map((item) => (
35
+ <button
36
+ key={item.label}
37
+ ref={item.isActive ? activeTabRef : undefined}
38
+ onClick={item.onClick}
39
+ className={`flex items-center gap-2 px-4 py-3 shrink-0 border-b-2 transition-colors ${
40
+ item.isActive
41
+ ? 'border-gray-950 text-gray-950'
42
+ : 'border-transparent text-gray-500 hover:text-gray-900'
43
+ }`}
44
+ >
45
+ {item.icon && <item.icon size={16} className="shrink-0" />}
46
+ <span
47
+ className={
48
+ item.isActive ? 'paragraph-small-semibold' : 'paragraph-small-medium'
49
+ }
50
+ >
51
+ {item.label}
52
+ </span>
53
+ </button>
54
+ ))}
55
+ </div>
56
+ );
57
+ }
58
+
59
+ export function TabsMobileShell({ children }: { children: ReactNode }) {
60
+ return (
61
+ <AppNavigationContext.Provider value={{ breakpoint: 'mobile' }}>
62
+ {/*
63
+ Children are hidden visually, but NavItems' useEffect still fires inside
64
+ display:none — React effects are tied to commit phase, not layout visibility.
65
+ This registers navItems in NavItemsContext so InternalTabsBar can read them.
66
+ */}
67
+ <div className="hidden">{children}</div>
68
+ <InternalTabsBar />
69
+ </AppNavigationContext.Provider>
70
+ );
71
+ }
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { ElementType } from 'react';
3
+ import { ElementType, useEffect } from 'react';
4
4
  import { NavItem } from '../NavItem';
5
+ import { useNavItemsContext } from '../NavItemsContext';
5
6
 
6
7
  export interface NavItemConfig {
7
8
  label: string;
@@ -15,6 +16,12 @@ interface NavItemsProps {
15
16
  }
16
17
 
17
18
  export function NavItems({ items }: NavItemsProps) {
19
+ const { setNavItems } = useNavItemsContext();
20
+
21
+ useEffect(() => {
22
+ setNavItems(items);
23
+ }, [items, setNavItems]);
24
+
18
25
  return (
19
26
  <div className="flex flex-col">
20
27
  {items.map((item) => (
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useState, useRef, RefObject } from 'react';
3
+ import { useState, useRef, useEffect, RefObject } from 'react';
4
+ import { useMobileNavbarSheet } from '../../../store/useMobileNavbarSheet';
4
5
  import Image from 'next/image';
5
6
  import { useAppNavigationContext } from '../AppNavigationContext';
6
7
  import { ProjectSelectorPopover } from '../ProjectSelectorPopover';
@@ -39,6 +40,14 @@ export function ProjectSelector({
39
40
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
40
41
  const [activeSection, setActiveSection] = useState<'projects' | null>(null);
41
42
  const titleRef = useRef<HTMLDivElement>(null);
43
+ const { openProjectList, clearProjectList } = useMobileNavbarSheet();
44
+
45
+ useEffect(() => {
46
+ if (openProjectList && breakpoint === 'mobile') {
47
+ setActiveSection('projects');
48
+ clearProjectList();
49
+ }
50
+ }, [openProjectList, breakpoint, clearProjectList]);
42
51
 
43
52
  // Desktop and md: title + popover + users selector (same visual as current gpages sidebars)
44
53
  if (breakpoint === 'desktop' || breakpoint === 'md') {
@@ -2,14 +2,20 @@ import { create } from 'zustand';
2
2
 
3
3
  interface MobileNavbarSheetStore {
4
4
  isOpen: boolean;
5
+ openProjectList: boolean;
5
6
  open: () => void;
6
7
  close: () => void;
7
8
  toggle: () => void;
9
+ openWithProjectList: () => void;
10
+ clearProjectList: () => void;
8
11
  }
9
12
 
10
13
  export const useMobileNavbarSheet = create<MobileNavbarSheetStore>((set) => ({
11
14
  isOpen: false,
12
- open: () => set({ isOpen: true }),
13
- close: () => set({ isOpen: false }),
14
- toggle: () => set((state) => ({ isOpen: !state.isOpen })),
15
+ openProjectList: false,
16
+ open: () => set({ isOpen: true, openProjectList: false }),
17
+ close: () => set({ isOpen: false, openProjectList: false }),
18
+ toggle: () => set((state) => ({ isOpen: !state.isOpen, openProjectList: false })),
19
+ openWithProjectList: () => set({ isOpen: true, openProjectList: true }),
20
+ clearProjectList: () => set({ openProjectList: false }),
15
21
  }));