Skip to content

Commit c75cfe2

Browse files
authored
Merge pull request #11 from oasisprotocol/mz/layout
Migrate to new UI Library Layout component
2 parents b77c117 + 0610cdd commit c75cfe2

File tree

13 files changed

+263
-84
lines changed

13 files changed

+263
-84
lines changed

src/components/AppsList/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useEffect, type FC, type ReactNode } from 'react';
2-
import { MainLayout } from '../Layout/MainLayout';
32
import { Skeleton } from '@oasisprotocol/ui-library/src/components/ui/skeleton';
43
import { AppCard } from '../AppCard';
54
import {
@@ -60,7 +59,7 @@ export const AppsList: FC<AppsListProps> = ({ emptyState, type }) => {
6059
(isFetched && allRoflApps.length === 0);
6160

6261
return (
63-
<MainLayout>
62+
<>
6463
{isEmpty && <>{emptyState}</>}
6564
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
6665
{isLoading &&
@@ -79,6 +78,6 @@ export const AppsList: FC<AppsListProps> = ({ emptyState, type }) => {
7978
</div>
8079

8180
<div ref={ref} className="h-10 w-full" />
82-
</MainLayout>
81+
</>
8382
);
8483
};

src/components/Layout/Footer.tsx

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,26 @@ import type { FC } from 'react';
22

33
export const Footer: FC = () => {
44
return (
5-
<footer className="flex-shrink-0 sw-full border-t pb-8">
6-
<div className="flex flex-col gap-4 sm:flex-row justify-between items-center border-t border-border/40 py-3 px-6">
5+
<footer className="w-full flex items-center justify-between">
6+
<p className="text-xs text-muted-foreground">
7+
Copyright @ OASIS {new Date().getFullYear()}
8+
</p>
9+
10+
<div className="flex items-center gap-2.5">
711
<p className="text-xs text-muted-foreground">
8-
Copyright &copy; {new Date().getFullYear()} Oasis Protocol Foundation
9-
2025
10-
</p>
11-
<div className="flex gap-4">
12-
<span className="text-xs text-muted-foreground">
12+
<a href="#" rel="noopener noreferrer" target="_blank">
1313
Version {APP_VERSION}
14-
</span>
15-
<span className="text-xs text-muted-foreground">|</span>
16-
<a
17-
href="https://oasis.net/privacy-policy"
18-
target="_blank"
19-
rel="noopener noreferrer"
20-
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
21-
>
22-
Privacy Policy
2314
</a>
24-
</div>
15+
</p>
16+
<span className="text-xs text-muted-foreground">|</span>
17+
<a
18+
className="text-xs text-muted-foreground"
19+
href="https://oasis.net/privacy-policy"
20+
rel="noopener noreferrer"
21+
target="_blank"
22+
>
23+
Privacy Policy
24+
</a>
2525
</div>
2626
</footer>
2727
);

src/components/Layout/Header.tsx

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
1-
import type { FC } from 'react';
2-
import { Link } from 'react-router-dom';
1+
import { useState, type FC } from 'react';
2+
import { Link, NavLink } from 'react-router-dom';
33
import Logotype from './logo.svg';
44
import { RainbowKitConnectButton } from '../RainbowKitConnectButton';
5-
import { Plus } from 'lucide-react';
5+
import { Menu, Plus } from 'lucide-react';
66
import { Button } from '@oasisprotocol/ui-library/src/components/ui/button';
77
import { useAccount } from 'wagmi';
8+
import { Separator } from '@oasisprotocol/ui-library/src/components/ui/separator';
9+
import {
10+
Sheet,
11+
SheetContent,
12+
SheetHeader,
13+
SheetTitle,
14+
SheetTrigger,
15+
} from '@oasisprotocol/ui-library/src/components/ui/sheet';
16+
import { cn } from '@oasisprotocol/ui-library/src/lib/utils';
17+
import { useIsMobile } from '@oasisprotocol/ui-library/src/hooks/use-mobile';
18+
import { NavbarLink } from '../NavbarLink';
819

920
export const Header: FC = () => {
21+
const isMobile = useIsMobile();
1022
const { isConnected } = useAccount();
23+
const [isOpen, setIsOpen] = useState(false);
1124

1225
return (
13-
<header className="flex-shrink-0 py-3 px-5 border-b w-full border-border/40">
14-
<div className="flex justify-between items-center">
15-
<Link to="/">
16-
<img src={Logotype} alt="Oasis ROFL" />
17-
</Link>
26+
<div className="w-full flex justify-between items-center">
27+
<Link to="/">
28+
<img src={Logotype} alt="Oasis ROFL" />
29+
</Link>
30+
31+
<div className="hidden md:flex">
1832
<div className="flex items-center gap-4">
1933
{isConnected && (
2034
<Button asChild>
@@ -27,6 +41,65 @@ export const Header: FC = () => {
2741
<RainbowKitConnectButton />
2842
</div>
2943
</div>
30-
</header>
44+
45+
<div className="md:hidden">
46+
<Sheet open={isMobile && isOpen} onOpenChange={setIsOpen}>
47+
<SheetTrigger asChild>
48+
<div>
49+
<Button
50+
variant="ghost"
51+
size="icon"
52+
className="hover:cursor-pointer"
53+
>
54+
<Menu className="h-5 w-5" />
55+
<span className="sr-only">Toggle navigation menu</span>
56+
</Button>
57+
</div>
58+
</SheetTrigger>
59+
<SheetContent side="top" className={cn('w-full')}>
60+
<SheetHeader>
61+
<SheetTitle className="sr-only">Navigation Menu</SheetTitle>
62+
<div className="flex items-start px-3 py-2.5">
63+
<NavLink to="/" onClick={() => setIsOpen(false)}>
64+
<img src={Logotype} alt="Oasis ROFL" />
65+
</NavLink>
66+
</div>
67+
</SheetHeader>
68+
<nav className="flex flex-col">
69+
<div className="p-2">
70+
<NavbarLink to="/dashboard" onClick={() => setIsOpen(false)}>
71+
Dashboard
72+
</NavbarLink>
73+
<NavbarLink
74+
to="/dashboard/apps"
75+
onClick={() => setIsOpen(false)}
76+
>
77+
My Apps
78+
</NavbarLink>
79+
<NavbarLink
80+
to="/dashboard/machines"
81+
onClick={() => setIsOpen(false)}
82+
>
83+
Machines
84+
</NavbarLink>
85+
<NavbarLink to="/explore" onClick={() => setIsOpen(false)}>
86+
Explore
87+
</NavbarLink>
88+
</div>
89+
90+
<div className="space-y-2.5">
91+
<Separator className="bg-border" />
92+
</div>
93+
94+
<div className="p-2">
95+
<RainbowKitConnectButton
96+
onMobileClose={() => setIsOpen(false)}
97+
/>
98+
</div>
99+
</nav>
100+
</SheetContent>
101+
</Sheet>
102+
</div>
103+
</div>
31104
);
32105
};
Lines changed: 113 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { FC, ReactNode } from 'react';
1+
import type { FC } from 'react';
22
import { Header } from './Header';
33
import { Footer } from './Footer';
4-
import { Link } from 'react-router-dom';
4+
import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom';
55
import {
66
Sidebar,
77
SidebarContent,
@@ -11,56 +11,134 @@ import {
1111
SidebarMenuSub,
1212
SidebarMenuSubItem,
1313
SidebarMenuSubButton,
14-
SidebarProvider,
14+
SidebarGroup,
1515
} from '@oasisprotocol/ui-library/src/components/ui/sidebar';
16-
import { AppWindow, Compass } from 'lucide-react';
16+
import { Compass, LayoutDashboard } from 'lucide-react';
17+
import { Button } from '@oasisprotocol/ui-library/src/components/ui/button';
18+
import { Layout } from '@oasisprotocol/ui-library/src/components/ui/layout';
19+
import {
20+
Breadcrumb,
21+
BreadcrumbList,
22+
BreadcrumbItem,
23+
BreadcrumbLink,
24+
BreadcrumbSeparator,
25+
} from '@oasisprotocol/ui-library/src/components/ui/breadcrumb';
26+
27+
const locationListMap: Record<string, string[]> = {
28+
'/explore': ['Explore'],
29+
'/dashboard/machines': ['Dashboard', 'Machines'],
30+
'/dashboard/apps': ['Dashboard', 'My Apps'],
31+
'/': ['Dashboard'],
32+
};
1733

18-
interface MainLayoutProps {
19-
children: ReactNode;
20-
}
34+
export const MainLayout: FC = () => {
35+
const navigate = useNavigate();
36+
const location = useLocation();
37+
const locationList = Object.entries(locationListMap).find(([path]) =>
38+
location.pathname.toLowerCase().startsWith(path)
39+
)?.[1];
2140

22-
export const MainLayout: FC<MainLayoutProps> = ({ children }) => {
41+
const getBreadcrumbPath = (index: number) => {
42+
if (!locationList) return '/';
43+
if (index === 0 && locationList[0] === 'Dashboard') {
44+
return '/dashboard';
45+
}
46+
return location.pathname;
47+
};
2348
return (
24-
<div className="flex flex-col min-h-screen">
25-
<Header />
26-
<SidebarProvider className="min-h-fit flex-1">
49+
<Layout
50+
headerContent={<Header />}
51+
headerBreadcrumbsContent={
52+
!!locationList?.length && (
53+
<Breadcrumb className="flex px-2">
54+
<BreadcrumbList>
55+
{locationList.flatMap((loc, i) => {
56+
const elements = [
57+
<BreadcrumbItem key={loc + i}>
58+
<BreadcrumbLink asChild>
59+
<NavLink
60+
to={getBreadcrumbPath(i)}
61+
className="text-foreground text-sm font-normal"
62+
>
63+
{loc}
64+
</NavLink>
65+
</BreadcrumbLink>
66+
</BreadcrumbItem>,
67+
];
68+
69+
if (i + 1 < locationList.length) {
70+
elements.push(
71+
<BreadcrumbSeparator key={`sep-${loc}-${i}`} />
72+
);
73+
}
74+
75+
return elements;
76+
})}
77+
</BreadcrumbList>
78+
</Breadcrumb>
79+
)
80+
}
81+
footerContent={<Footer />}
82+
sidebar={
2783
<Sidebar collapsible="icon" className="border-r !static !h-full">
28-
<SidebarContent>
29-
<SidebarMenu>
30-
<SidebarMenuItem>
31-
<SidebarMenuButton asChild tooltip="Dashboard">
32-
<Link to="/dashboard">
33-
<AppWindow />
34-
<span>Dashboard</span>
35-
</Link>
36-
</SidebarMenuButton>
84+
<SidebarContent className="bg-sidebar-background">
85+
<SidebarGroup>
86+
<SidebarMenu>
87+
<SidebarMenuItem>
88+
<SidebarMenuButton asChild>
89+
<Button
90+
onClick={() => navigate('/dashboard')}
91+
variant="ghost"
92+
className="w-full justify-start p-2 h-8 rounded-md cursor-pointer"
93+
>
94+
<LayoutDashboard className="h-4 w-4 text-sidebar-foreground" />
95+
Dashboard
96+
</Button>
97+
</SidebarMenuButton>
98+
</SidebarMenuItem>
3799
<SidebarMenuSub>
38100
<SidebarMenuSubItem>
39101
<SidebarMenuSubButton asChild>
40-
<Link to="/dashboard/apps">My Apps</Link>
102+
<Button
103+
onClick={() => navigate('/dashboard/apps')}
104+
variant="ghost"
105+
className="w-full justify-start p-2 h-8 rounded-md cursor-pointer"
106+
>
107+
My Apps
108+
</Button>
41109
</SidebarMenuSubButton>
42110
</SidebarMenuSubItem>
43111
<SidebarMenuSubItem>
44112
<SidebarMenuSubButton asChild>
45-
<Link to="/dashboard/machines">Machines</Link>
113+
<Button
114+
onClick={() => navigate('/dashboard/machines')}
115+
variant="ghost"
116+
className="w-full justify-start p-2 h-8 rounded-md cursor-pointer"
117+
>
118+
Machines
119+
</Button>
46120
</SidebarMenuSubButton>
47121
</SidebarMenuSubItem>
48122
</SidebarMenuSub>
49-
</SidebarMenuItem>
50-
<SidebarMenuItem>
51-
<SidebarMenuButton asChild tooltip="Explore">
52-
<Link to="/explore">
53-
<Compass />
54-
<span>Explore</span>
55-
</Link>
123+
<SidebarMenuButton asChild>
124+
<Button
125+
onClick={() => navigate('/explore')}
126+
variant="ghost"
127+
className="w-full justify-start p-2 h-8 rounded-md cursor-pointer"
128+
>
129+
<Compass className="h-4 w-4 text-sidebar-foreground" />
130+
Explore
131+
</Button>
56132
</SidebarMenuButton>
57-
</SidebarMenuItem>
58-
</SidebarMenu>
133+
</SidebarMenu>
134+
</SidebarGroup>
59135
</SidebarContent>
60136
</Sidebar>
61-
<main className="flex-1 p-5">{children}</main>
62-
</SidebarProvider>
63-
<Footer />
64-
</div>
137+
}
138+
>
139+
<div className="flex-1 p-5 h-5/6">
140+
<Outlet />
141+
</div>
142+
</Layout>
65143
);
66144
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Button } from '@oasisprotocol/ui-library/src/components/ui/button'
2+
import { FC, PropsWithChildren } from 'react'
3+
import { NavLink } from 'react-router-dom'
4+
5+
interface NavbarLinkProps extends PropsWithChildren {
6+
to?: string
7+
className?: string
8+
onClick?: () => void
9+
}
10+
11+
export const NavbarLink: FC<NavbarLinkProps> = ({ children, to, className, onClick }) => {
12+
return (
13+
<Button
14+
variant="ghost"
15+
className={`${className} flex items-center justify-start px-2 py-2.5 rounded-md text-base font-medium`}
16+
asChild={!!to}
17+
onClick={onClick}
18+
>
19+
{to ? (
20+
<NavLink to={to}>
21+
{({ isActive }) => (
22+
<span className={isActive ? 'text-foreground' : 'text-muted-foreground'}>{children}</span>
23+
)}
24+
</NavLink>
25+
) : (
26+
children
27+
)}
28+
</Button>
29+
)
30+
}

0 commit comments

Comments
 (0)