Skip to content

Commit f9872f4

Browse files
committed
[TOOL-3761] Dashboard: Use new sidebar layout in project pages (#6551)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR primarily focuses on enhancing the layout and styling of the application, particularly around sidebar and project-related components. It introduces new sidebar features, refines the UI, and improves mobile responsiveness. ### Detailed summary - Removed unused files: `tabs.tsx`, `layout.tsx` in `connect`, and `settings/layout.tsx`. - Simplified layout in `DeployedContractsPage.tsx` by removing unnecessary divs. - Added `AnnouncementBanner` to multiple layouts for notifications. - Introduced `useActiveSidebarLink` for better sidebar link management. - Enhanced `Sidebar` components with new features and improved styling. - Added `Header` component in `ProjectOverviewPage` for better title display. - Updated Tailwind CSS configuration to include sidebar styles. - Improved mobile responsiveness with `useIsMobile` hook. - Cleaned up `AnnouncementBanner` logic and structure. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 473a252 commit f9872f4

File tree

22 files changed

+1200
-197
lines changed

22 files changed

+1200
-197
lines changed

apps/dashboard/src/@/components/blocks/MobileSidebar.tsx

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,7 @@ export function MobileSidebar(props: {
1919
triggerClassName?: string;
2020
}) {
2121
const [isOpen, setIsOpen] = useState(false);
22-
const pathname = usePathname();
23-
24-
const activeLink = useMemo(() => {
25-
function isActive(link: SidebarBaseLink) {
26-
if (link.exactMatch) {
27-
return link.href === pathname;
28-
}
29-
return pathname?.startsWith(link.href);
30-
}
31-
32-
for (const link of props.links) {
33-
if ("group" in link) {
34-
for (const subLink of link.links) {
35-
if (isActive(subLink)) {
36-
return subLink;
37-
}
38-
}
39-
} else {
40-
if (isActive(link)) {
41-
return link;
42-
}
43-
}
44-
}
45-
}, [props.links, pathname]);
22+
const activeLink = useActiveSidebarLink(props.links);
4623

4724
const defaultTrigger = (
4825
<Button
@@ -75,3 +52,32 @@ export function MobileSidebar(props: {
7552
</Dialog>
7653
);
7754
}
55+
56+
export function useActiveSidebarLink(links: SidebarLink[]) {
57+
const pathname = usePathname();
58+
59+
const activeLink = useMemo(() => {
60+
function isActive(link: SidebarBaseLink) {
61+
if (link.exactMatch) {
62+
return link.href === pathname;
63+
}
64+
return pathname?.startsWith(link.href);
65+
}
66+
67+
for (const link of links) {
68+
if ("group" in link) {
69+
for (const subLink of link.links) {
70+
if (isActive(subLink)) {
71+
return subLink;
72+
}
73+
}
74+
} else if ("href" in link) {
75+
if (isActive(link)) {
76+
return link;
77+
}
78+
}
79+
}
80+
}, [links, pathname]);
81+
82+
return activeLink;
83+
}

apps/dashboard/src/@/components/blocks/Sidebar.tsx

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { ExternalLinkIcon } from "lucide-react";
22
import type React from "react";
33
import { cn } from "../../lib/utils";
44
import { NavLink } from "../ui/NavLink";
5+
import { Separator } from "../ui/separator";
56

67
export type SidebarBaseLink = {
78
href: string;
89
label: React.ReactNode;
910
exactMatch?: boolean;
11+
icon?: React.FC<{ className?: string }>;
1012
tracking?: {
1113
category: string;
1214
action: string;
@@ -19,6 +21,9 @@ export type SidebarLink =
1921
| {
2022
group: string;
2123
links: SidebarBaseLink[];
24+
}
25+
| {
26+
separator: true;
2227
};
2328

2429
type SidebarContentProps = {
@@ -27,21 +32,18 @@ type SidebarContentProps = {
2732
className?: string;
2833
};
2934

30-
export function Sidebar(props: SidebarContentProps) {
35+
export function CustomSidebar(props: SidebarContentProps) {
3136
return (
32-
<aside
33-
className={cn(
34-
"sticky top-0 hidden w-[230px] flex-shrink-0 self-start lg:block",
35-
props.className,
36-
)}
37-
>
38-
<div className="py-7">
39-
{props.header}
40-
<div className="flex flex-col gap-1">
41-
<RenderSidebarLinks links={props.links} />
37+
<div className={cn("hidden w-[230px] shrink-0 lg:block", props.className)}>
38+
<aside className="sticky top-0 self-start">
39+
<div className="py-7">
40+
{props.header}
41+
<div className="flex flex-col gap-1">
42+
<RenderSidebarLinks links={props.links} />
43+
</div>
4244
</div>
43-
</div>
44-
</aside>
45+
</aside>
46+
</div>
4547
);
4648
}
4749

@@ -61,16 +63,21 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
6163
);
6264
}
6365

66+
if ("separator" in link) {
67+
return <Separator className="my-2" />;
68+
}
69+
6470
const isExternal = link.href.startsWith("http");
6571
return (
6672
<NavLink
6773
// biome-ignore lint/suspicious/noArrayIndexKey: items won't be reordered
6874
key={i}
6975
href={link.href}
7076
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-accent"
71-
activeClassName="text-foreground"
77+
activeClassName="text-foreground bg-accent"
7278
exactMatch={link.exactMatch}
7379
>
80+
{link.icon && <link.icon className="size-4" />}
7481
{link.label}
7582
{isExternal && <ExternalLinkIcon className="size-3" />}
7683
</NavLink>

apps/dashboard/src/@/components/blocks/SidebarLayout.tsx

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
import {
2+
Sidebar,
3+
SidebarContent,
4+
SidebarFooter,
5+
SidebarGroup,
6+
SidebarGroupContent,
7+
SidebarMenu,
8+
SidebarMenuButton,
9+
SidebarMenuItem,
10+
SidebarRail,
11+
SidebarSeparator,
12+
SidebarTrigger,
13+
useSidebar,
14+
} from "@/components/ui/sidebar";
115
import { cn } from "../../lib/utils";
2-
import { MobileSidebar } from "./MobileSidebar";
3-
import { Sidebar, type SidebarLink } from "./Sidebar";
16+
import { NavLink } from "../ui/NavLink";
17+
import { Separator } from "../ui/separator";
18+
import { MobileSidebar, useActiveSidebarLink } from "./MobileSidebar";
19+
import { CustomSidebar, type SidebarLink } from "./Sidebar";
420

521
export function SidebarLayout(props: {
622
sidebarLinks: SidebarLink[];
@@ -17,7 +33,10 @@ export function SidebarLayout(props: {
1733
props.className,
1834
)}
1935
>
20-
<Sidebar links={sidebarLinks} className={props.desktopSidebarClassName} />
36+
<CustomSidebar
37+
links={sidebarLinks}
38+
className={props.desktopSidebarClassName}
39+
/>
2140
<MobileSidebar
2241
links={sidebarLinks}
2342
triggerClassName={props.mobileSidebarClassName}
@@ -29,3 +48,115 @@ export function SidebarLayout(props: {
2948
</div>
3049
);
3150
}
51+
52+
export function FullWidthSidebarLayout(props: {
53+
contentSidebarLinks: SidebarLink[];
54+
footerSidebarLinks?: SidebarLink[];
55+
children: React.ReactNode;
56+
className?: string;
57+
footer?: React.ReactNode;
58+
}) {
59+
const { contentSidebarLinks, children, footerSidebarLinks } = props;
60+
return (
61+
<div
62+
className={cn("flex w-full flex-1 overflow-y-hidden", props.className)}
63+
>
64+
{/* left - sidebar */}
65+
<Sidebar collapsible="icon" side="left">
66+
<SidebarContent>
67+
<SidebarGroup>
68+
<SidebarGroupContent>
69+
<RenderSidebarGroup
70+
sidebarLinks={contentSidebarLinks}
71+
groupName={undefined}
72+
/>
73+
</SidebarGroupContent>
74+
</SidebarGroup>
75+
</SidebarContent>
76+
77+
{footerSidebarLinks && (
78+
<SidebarFooter className="pb-3">
79+
<RenderSidebarGroup
80+
sidebarLinks={footerSidebarLinks}
81+
groupName={undefined}
82+
/>
83+
</SidebarFooter>
84+
)}
85+
86+
<SidebarRail />
87+
</Sidebar>
88+
89+
{/* right - content */}
90+
<div className="flex h-full flex-grow flex-col overflow-y-auto">
91+
<MobileSidebarTrigger
92+
links={[...contentSidebarLinks, ...(footerSidebarLinks || [])]}
93+
/>
94+
95+
<main className="container z-0 flex min-w-0 max-w-[1280px] grow flex-col pb-20 max-sm:w-full lg:pt-6">
96+
{children}
97+
</main>
98+
{props.footer}
99+
</div>
100+
</div>
101+
);
102+
}
103+
104+
function RenderSidebarGroup(props: {
105+
sidebarLinks: SidebarLink[];
106+
groupName: string | undefined;
107+
}) {
108+
const { sidebarLinks } = props;
109+
const sidebar = useSidebar();
110+
111+
return (
112+
<SidebarMenu className="gap-1.5">
113+
{sidebarLinks.map((link) => {
114+
if ("href" in link) {
115+
return (
116+
<SidebarMenuItem key={link.href}>
117+
<SidebarMenuButton asChild>
118+
<NavLink
119+
href={link.href}
120+
className="flex items-center gap-2 text-muted-foreground text-sm hover:bg-accent"
121+
activeClassName="text-foreground bg-accent"
122+
exactMatch={link.exactMatch}
123+
tracking={link.tracking}
124+
onClick={() => {
125+
sidebar.setOpenMobile(false);
126+
}}
127+
>
128+
{link.icon && <link.icon className="size-4" />}
129+
<span>{link.label}</span>
130+
</NavLink>
131+
</SidebarMenuButton>
132+
</SidebarMenuItem>
133+
);
134+
}
135+
136+
if ("separator" in link) {
137+
return <SidebarSeparator className="my-1" />;
138+
}
139+
140+
return (
141+
<RenderSidebarGroup
142+
sidebarLinks={link.links}
143+
groupName={link.group}
144+
key={link.group}
145+
/>
146+
);
147+
})}
148+
</SidebarMenu>
149+
);
150+
}
151+
152+
function MobileSidebarTrigger(props: { links: SidebarLink[] }) {
153+
const activeLink = useActiveSidebarLink(props.links);
154+
155+
return (
156+
<div className="mb-4 flex items-center gap-3 border-b px-4 py-4 lg:hidden">
157+
<SidebarTrigger className="size-4" />
158+
<Separator orientation="vertical" className="h-4" />
159+
{activeLink && <span className="text-sm">{activeLink.label}</span>}
160+
</div>
161+
);
162+
}

apps/dashboard/src/@/components/ui/NavLink.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type NavButtonProps = {
1515
action: string;
1616
label: string;
1717
};
18+
onClick?: () => void;
1819
};
1920

2021
export function NavLink(props: React.PropsWithChildren<NavButtonProps>) {
@@ -30,7 +31,9 @@ export function NavLink(props: React.PropsWithChildren<NavButtonProps>) {
3031
href={props.href}
3132
className={cn(props.className, isActive && props.activeClassName)}
3233
target={props.href.startsWith("http") ? "_blank" : undefined}
34+
prefetch={false}
3335
onClick={() => {
36+
props.onClick?.();
3437
if (props.tracking) {
3538
track({
3639
category: props.tracking.category,

0 commit comments

Comments
 (0)