Skip to content

Commit 8837484

Browse files
committed
Add basic structure for mobile menu
1 parent 916c0a7 commit 8837484

File tree

4 files changed

+87
-76
lines changed

4 files changed

+87
-76
lines changed

site/src/components/Button/Button.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ export const buttonVariants = cva(
2626
"border-none bg-transparent text-content-secondary hover:text-content-primary",
2727
warning:
2828
"border border-border-error text-content-primary bg-surface-error hover:bg-transparent",
29+
ghost: "bg-transparent border-0 hover:bg-surface-secondary",
2930
},
3031

3132
size: {
3233
lg: "h-10",
3334
default: "h-9",
3435
sm: "h-8 px-2 py-1.5 text-xs",
36+
icon: "h-10 w-10",
3537
},
3638
},
3739
defaultVariants: {

site/src/modules/dashboard/Navbar/Navbar.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
MockMemberPermissions,
88
} from "testHelpers/entities";
99
import { server } from "testHelpers/server";
10-
import { Language } from "./NavbarView";
1110

1211
/**
1312
* The LicenseBanner, mounted above the AppRouter, fetches entitlements. Thus, to test their
@@ -26,7 +25,7 @@ describe("Navbar", () => {
2625
await userEvent.click(deploymentMenu);
2726
await waitFor(
2827
() => {
29-
const link = screen.getByText(Language.audit);
28+
const link = screen.getByText("Audit Logs");
3029
expect(link).toBeDefined();
3130
},
3231
{ timeout: 2000 },
@@ -41,7 +40,7 @@ describe("Navbar", () => {
4140
await userEvent.click(deploymentMenu);
4241
await waitFor(
4342
() => {
44-
const link = screen.queryByText(Language.audit);
43+
const link = screen.queryByText("Audit Logs");
4544
expect(link).toBe(null);
4645
},
4746
{ timeout: 2000 },

site/src/modules/dashboard/Navbar/NavbarView.test.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
33
import type { ProxyContextValue } from "contexts/ProxyContext";
44
import { MockPrimaryWorkspaceProxy, MockUser } from "testHelpers/entities";
55
import { renderWithAuth } from "testHelpers/renderHelpers";
6-
import { NavbarView, Language as navLanguage } from "./NavbarView";
6+
import { NavbarView } from "./NavbarView";
77

88
const proxyContextValue: ProxyContextValue = {
99
proxy: {
@@ -36,7 +36,7 @@ describe("NavbarView", () => {
3636
canViewAuditLog
3737
/>,
3838
);
39-
const workspacesLink = await screen.findByText(navLanguage.workspaces);
39+
const workspacesLink = await screen.findByText("Workspaces");
4040
expect((workspacesLink as HTMLAnchorElement).href).toContain("/workspaces");
4141
});
4242

@@ -54,7 +54,7 @@ describe("NavbarView", () => {
5454
canViewAuditLog
5555
/>,
5656
);
57-
const templatesLink = await screen.findByText(navLanguage.templates);
57+
const templatesLink = await screen.findByText("Templates");
5858
expect((templatesLink as HTMLAnchorElement).href).toContain("/templates");
5959
});
6060

@@ -74,7 +74,7 @@ describe("NavbarView", () => {
7474
);
7575
const deploymentMenu = await screen.findByText("Admin settings");
7676
await userEvent.click(deploymentMenu);
77-
const auditLink = await screen.findByText(navLanguage.audit);
77+
const auditLink = await screen.findByText("Audit Logs");
7878
expect((auditLink as HTMLAnchorElement).href).toContain("/audit");
7979
});
8080

@@ -94,9 +94,7 @@ describe("NavbarView", () => {
9494
);
9595
const deploymentMenu = await screen.findByText("Admin settings");
9696
await userEvent.click(deploymentMenu);
97-
const deploymentSettingsLink = await screen.findByText(
98-
navLanguage.deployment,
99-
);
97+
const deploymentSettingsLink = await screen.findByText("Deployment");
10098
expect((deploymentSettingsLink as HTMLAnchorElement).href).toContain(
10199
"/deployment/general",
102100
);

site/src/modules/dashboard/Navbar/NavbarView.tsx

Lines changed: 78 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
1-
import { type Interpolation, type Theme, css, useTheme } from "@emotion/react";
2-
import MenuIcon from "@mui/icons-material/Menu";
3-
import Drawer from "@mui/material/Drawer";
4-
import IconButton from "@mui/material/IconButton";
51
import type * as TypesGen from "api/typesGenerated";
62
import { ExternalImage } from "components/ExternalImage/ExternalImage";
73
import { CoderIcon } from "components/Icons/CoderIcon";
84
import type { ProxyContextValue } from "contexts/ProxyContext";
9-
import { type FC, useState } from "react";
10-
import { Link, NavLink, useLocation } from "react-router-dom";
5+
import type { FC } from "react";
6+
import { NavLink, useLocation } from "react-router-dom";
117
import { DeploymentDropdown } from "./DeploymentDropdown";
128
import { ProxyMenu } from "./ProxyMenu";
139
import { UserDropdown } from "./UserDropdown/UserDropdown";
1410
import { cn } from "utils/cn";
11+
import { Button } from "components/Button/Button";
12+
import { ChevronRightIcon, MenuIcon } from "lucide-react";
13+
import {
14+
DropdownMenu,
15+
DropdownMenuContent,
16+
DropdownMenuItem,
17+
DropdownMenuTrigger,
18+
} from "components/DropdownMenu/DropdownMenu";
19+
import { Avatar } from "components/Avatar/Avatar";
20+
import { Latency } from "components/Latency/Latency";
1521

16-
export const Language = {
17-
workspaces: "Workspaces",
18-
templates: "Templates",
19-
users: "Users",
20-
audit: "Audit Logs",
21-
deployment: "Deployment",
22-
};
2322
export interface NavbarViewProps {
2423
logo_url?: string;
2524
user?: TypesGen.User;
@@ -41,6 +40,9 @@ const linkClassNames = {
4140
active: "text-content-primary",
4241
};
4342

43+
const mobileDropdownItemClassName =
44+
"px-9 h-[60px] border-0 border-b border-solid";
45+
4446
export const NavbarView: FC<NavbarViewProps> = ({
4547
user,
4648
logo_url,
@@ -55,40 +57,8 @@ export const NavbarView: FC<NavbarViewProps> = ({
5557
canViewAuditLog,
5658
proxyContextValue,
5759
}) => {
58-
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
59-
6060
return (
6161
<div className="border-0 border-b border-solid h-[72px] flex items-center leading-none px-6">
62-
<IconButton
63-
aria-label="Open menu"
64-
css={styles.mobileMenuButton}
65-
onClick={() => {
66-
setIsDrawerOpen(true);
67-
}}
68-
size="large"
69-
>
70-
<MenuIcon />
71-
</IconButton>
72-
73-
<Drawer
74-
anchor="left"
75-
open={isDrawerOpen}
76-
onClose={() => setIsDrawerOpen(false)}
77-
>
78-
<div css={{ width: 250 }}>
79-
<div css={styles.drawerHeader}>
80-
<div css={["h-7", styles.drawerLogo]}>
81-
{logo_url ? (
82-
<ExternalImage src={logo_url} alt="Custom Logo" />
83-
) : (
84-
<CoderIcon />
85-
)}
86-
</div>
87-
</div>
88-
<NavItems />
89-
</div>
90-
</Drawer>
91-
9262
<NavLink to="/workspaces">
9363
{logo_url ? (
9464
<ExternalImage className="h-7" src={logo_url} alt="Custom Logo" />
@@ -99,7 +69,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
9969

10070
<NavItems className="ml-4" />
10171

102-
<div className="flex items-center gap-3 ml-auto">
72+
<div className=" hidden md:flex items-center gap-3 ml-auto">
10373
{proxyContextValue && (
10474
<ProxyMenu proxyContextValue={proxyContextValue} />
10575
)}
@@ -130,10 +100,70 @@ export const NavbarView: FC<NavbarViewProps> = ({
130100
/>
131101
)}
132102
</div>
103+
104+
<MobileMenu proxyContextValue={proxyContextValue} user={user} />
133105
</div>
134106
);
135107
};
136108

109+
type MobileMenuProps = {
110+
proxyContextValue?: ProxyContextValue;
111+
user?: TypesGen.User;
112+
};
113+
114+
const MobileMenu: FC<MobileMenuProps> = ({ proxyContextValue, user }) => {
115+
const selectedProxy = proxyContextValue?.proxy.proxy;
116+
const latency = selectedProxy
117+
? proxyContextValue?.proxyLatencies[selectedProxy?.id]
118+
: undefined;
119+
120+
return (
121+
<DropdownMenu>
122+
<DropdownMenuTrigger asChild>
123+
<Button
124+
aria-label="Open Menu"
125+
size="icon"
126+
variant="ghost"
127+
className="ml-auto md:hidden"
128+
>
129+
<MenuIcon />
130+
</Button>
131+
</DropdownMenuTrigger>
132+
<DropdownMenuContent className="w-screen border-0 p-0" sideOffset={17}>
133+
{selectedProxy && (
134+
<DropdownMenuItem className={mobileDropdownItemClassName}>
135+
Workspace proxy settings:
136+
<span className="leading-[0px] flex items-center gap-1">
137+
<img
138+
className="w-4 h-4"
139+
src={selectedProxy.icon_url}
140+
alt={selectedProxy.name}
141+
/>
142+
{latency && <Latency latency={latency.latencyMS} />}
143+
</span>
144+
<ChevronRightIcon className="ml-auto" />
145+
</DropdownMenuItem>
146+
)}
147+
<DropdownMenuItem className={mobileDropdownItemClassName}>
148+
Admin settings
149+
<ChevronRightIcon className="ml-auto" />
150+
</DropdownMenuItem>
151+
<DropdownMenuItem className={mobileDropdownItemClassName}>
152+
Docs
153+
</DropdownMenuItem>
154+
<DropdownMenuItem className={mobileDropdownItemClassName}>
155+
<Avatar
156+
src={user?.avatar_url}
157+
fallback={user?.name || user?.username}
158+
/>
159+
User settings
160+
<ChevronRightIcon className="ml-auto" />
161+
</DropdownMenuItem>
162+
</DropdownMenuContent>
163+
</DropdownMenu>
164+
);
165+
};
166+
137167
interface NavItemsProps {
138168
className?: string;
139169
}
@@ -155,7 +185,7 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
155185
}}
156186
to="/workspaces"
157187
>
158-
{Language.workspaces}
188+
Workspaces
159189
</NavLink>
160190
<NavLink
161191
className={({ isActive }) => {
@@ -166,26 +196,8 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
166196
}}
167197
to="/templates"
168198
>
169-
{Language.templates}
199+
Templates
170200
</NavLink>
171201
</nav>
172202
);
173203
};
174-
175-
const styles = {
176-
mobileMenuButton: (theme) => css`
177-
${theme.breakpoints.up("md")} {
178-
display: none;
179-
}
180-
`,
181-
182-
drawerHeader: {
183-
padding: 16,
184-
paddingTop: 32,
185-
paddingBottom: 32,
186-
},
187-
drawerLogo: {
188-
padding: 0,
189-
maxHeight: 40,
190-
},
191-
} satisfies Record<string, Interpolation<Theme>>;

0 commit comments

Comments
 (0)