Thanks to visit codestin.com
Credit goes to github.com

Skip to content

refactor: update the navbar to match the new designs #15964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add basic structure for mobile menu
  • Loading branch information
BrunoQuaresma committed Dec 26, 2024
commit 8837484d2db092cbac36e4b9dc4fdf2dcadf0f77
2 changes: 2 additions & 0 deletions site/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ export const buttonVariants = cva(
"border-none bg-transparent text-content-secondary hover:text-content-primary",
warning:
"border border-border-error text-content-primary bg-surface-error hover:bg-transparent",
ghost: "bg-transparent border-0 hover:bg-surface-secondary",
},

size: {
lg: "h-10",
default: "h-9",
sm: "h-8 px-2 py-1.5 text-xs",
icon: "h-10 w-10",
},
},
defaultVariants: {
Expand Down
5 changes: 2 additions & 3 deletions site/src/modules/dashboard/Navbar/Navbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
MockMemberPermissions,
} from "testHelpers/entities";
import { server } from "testHelpers/server";
import { Language } from "./NavbarView";

/**
* The LicenseBanner, mounted above the AppRouter, fetches entitlements. Thus, to test their
Expand All @@ -26,7 +25,7 @@ describe("Navbar", () => {
await userEvent.click(deploymentMenu);
await waitFor(
() => {
const link = screen.getByText(Language.audit);
const link = screen.getByText("Audit Logs");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we update these to be role selectors, too?

expect(link).toBeDefined();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check needed since getByText will throw if a node can't be found?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re right, it’s not needed.

},
{ timeout: 2000 },
Expand All @@ -41,7 +40,7 @@ describe("Navbar", () => {
await userEvent.click(deploymentMenu);
await waitFor(
() => {
const link = screen.queryByText(Language.audit);
const link = screen.queryByText("Audit Logs");
expect(link).toBe(null);
},
{ timeout: 2000 },
Expand Down
12 changes: 5 additions & 7 deletions site/src/modules/dashboard/Navbar/NavbarView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
import type { ProxyContextValue } from "contexts/ProxyContext";
import { MockPrimaryWorkspaceProxy, MockUser } from "testHelpers/entities";
import { renderWithAuth } from "testHelpers/renderHelpers";
import { NavbarView, Language as navLanguage } from "./NavbarView";
import { NavbarView } from "./NavbarView";

const proxyContextValue: ProxyContextValue = {
proxy: {
Expand Down Expand Up @@ -36,7 +36,7 @@ describe("NavbarView", () => {
canViewAuditLog
/>,
);
const workspacesLink = await screen.findByText(navLanguage.workspaces);
const workspacesLink = await screen.findByText("Workspaces");
expect((workspacesLink as HTMLAnchorElement).href).toContain("/workspaces");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be swapped for a findByRole call?

const workspacesLink = await screen.findByRole<HTMLAnchorElement>("link", {
  name: /Workspaces/i
});
expect(workspacesLink.href).toContain("/workspaces");

Same goes for the other links

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m going to stick with findByText because the element’s role is menuitem, and for some reason, Testing Library doesn’t work with menuitem.

});

Expand All @@ -54,7 +54,7 @@ describe("NavbarView", () => {
canViewAuditLog
/>,
);
const templatesLink = await screen.findByText(navLanguage.templates);
const templatesLink = await screen.findByText("Templates");
expect((templatesLink as HTMLAnchorElement).href).toContain("/templates");
});

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

Expand All @@ -94,9 +94,7 @@ describe("NavbarView", () => {
);
const deploymentMenu = await screen.findByText("Admin settings");
await userEvent.click(deploymentMenu);
const deploymentSettingsLink = await screen.findByText(
navLanguage.deployment,
);
const deploymentSettingsLink = await screen.findByText("Deployment");
expect((deploymentSettingsLink as HTMLAnchorElement).href).toContain(
"/deployment/general",
);
Expand Down
144 changes: 78 additions & 66 deletions site/src/modules/dashboard/Navbar/NavbarView.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { type Interpolation, type Theme, css, useTheme } from "@emotion/react";
import MenuIcon from "@mui/icons-material/Menu";
import Drawer from "@mui/material/Drawer";
import IconButton from "@mui/material/IconButton";
import type * as TypesGen from "api/typesGenerated";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { CoderIcon } from "components/Icons/CoderIcon";
import type { ProxyContextValue } from "contexts/ProxyContext";
import { type FC, useState } from "react";
import { Link, NavLink, useLocation } from "react-router-dom";
import type { FC } from "react";
import { NavLink, useLocation } from "react-router-dom";
import { DeploymentDropdown } from "./DeploymentDropdown";
import { ProxyMenu } from "./ProxyMenu";
import { UserDropdown } from "./UserDropdown/UserDropdown";
import { cn } from "utils/cn";
import { Button } from "components/Button/Button";
import { ChevronRightIcon, MenuIcon } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "components/DropdownMenu/DropdownMenu";
import { Avatar } from "components/Avatar/Avatar";
import { Latency } from "components/Latency/Latency";

export const Language = {
workspaces: "Workspaces",
templates: "Templates",
users: "Users",
audit: "Audit Logs",
deployment: "Deployment",
};
export interface NavbarViewProps {
logo_url?: string;
user?: TypesGen.User;
Expand All @@ -41,6 +40,9 @@ const linkClassNames = {
active: "text-content-primary",
};

const mobileDropdownItemClassName =
"px-9 h-[60px] border-0 border-b border-solid";

export const NavbarView: FC<NavbarViewProps> = ({
user,
logo_url,
Expand All @@ -55,40 +57,8 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewAuditLog,
proxyContextValue,
}) => {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);

return (
<div className="border-0 border-b border-solid h-[72px] flex items-center leading-none px-6">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does border-b border-solid do anything if we have no border width?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They shouldn’t, but since we’re not using Tailwind’s preflight and are still relying on MUI’s reset, the border doesn’t apply correctly without those classes.

<IconButton
aria-label="Open menu"
css={styles.mobileMenuButton}
onClick={() => {
setIsDrawerOpen(true);
}}
size="large"
>
<MenuIcon />
</IconButton>

<Drawer
anchor="left"
open={isDrawerOpen}
onClose={() => setIsDrawerOpen(false)}
>
<div css={{ width: 250 }}>
<div css={styles.drawerHeader}>
<div css={["h-7", styles.drawerLogo]}>
{logo_url ? (
<ExternalImage src={logo_url} alt="Custom Logo" />
) : (
<CoderIcon />
)}
</div>
</div>
<NavItems />
</div>
</Drawer>

<NavLink to="/workspaces">
{logo_url ? (
<ExternalImage className="h-7" src={logo_url} alt="Custom Logo" />
Expand All @@ -99,7 +69,7 @@ export const NavbarView: FC<NavbarViewProps> = ({

<NavItems className="ml-4" />

<div className="flex items-center gap-3 ml-auto">
<div className=" hidden md:flex items-center gap-3 ml-auto">
{proxyContextValue && (
<ProxyMenu proxyContextValue={proxyContextValue} />
)}
Expand Down Expand Up @@ -130,10 +100,70 @@ export const NavbarView: FC<NavbarViewProps> = ({
/>
)}
</div>

<MobileMenu proxyContextValue={proxyContextValue} user={user} />
</div>
);
};

type MobileMenuProps = {
proxyContextValue?: ProxyContextValue;
user?: TypesGen.User;
};

const MobileMenu: FC<MobileMenuProps> = ({ proxyContextValue, user }) => {
const selectedProxy = proxyContextValue?.proxy.proxy;
const latency = selectedProxy
? proxyContextValue?.proxyLatencies[selectedProxy?.id]
: undefined;

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
aria-label="Open Menu"
size="icon"
variant="ghost"
className="ml-auto md:hidden"
>
<MenuIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-screen border-0 p-0" sideOffset={17}>
{selectedProxy && (
<DropdownMenuItem className={mobileDropdownItemClassName}>
Workspace proxy settings:
<span className="leading-[0px] flex items-center gap-1">
<img
className="w-4 h-4"
src={selectedProxy.icon_url}
alt={selectedProxy.name}
/>
{latency && <Latency latency={latency.latencyMS} />}
</span>
<ChevronRightIcon className="ml-auto" />
</DropdownMenuItem>
)}
<DropdownMenuItem className={mobileDropdownItemClassName}>
Admin settings
<ChevronRightIcon className="ml-auto" />
</DropdownMenuItem>
<DropdownMenuItem className={mobileDropdownItemClassName}>
Docs
</DropdownMenuItem>
<DropdownMenuItem className={mobileDropdownItemClassName}>
<Avatar
src={user?.avatar_url}
fallback={user?.name || user?.username}
/>
User settings
<ChevronRightIcon className="ml-auto" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

interface NavItemsProps {
className?: string;
}
Expand All @@ -155,7 +185,7 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
}}
to="/workspaces"
>
{Language.workspaces}
Workspaces
</NavLink>
<NavLink
className={({ isActive }) => {
Expand All @@ -166,26 +196,8 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
}}
to="/templates"
>
{Language.templates}
Templates
</NavLink>
</nav>
);
};

const styles = {
mobileMenuButton: (theme) => css`
${theme.breakpoints.up("md")} {
display: none;
}
`,

drawerHeader: {
padding: 16,
paddingTop: 32,
paddingBottom: 32,
},
drawerLogo: {
padding: 0,
maxHeight: 40,
},
} satisfies Record<string, Interpolation<Theme>>;