Skip to content

feat: implement new menu design #152

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 12 commits into from
Jan 21, 2025
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,16 @@ Run the production build command:
```bash
npm run preview
```

## Custom SVG icons

In order to generate custom SVG icons based on the Figma design, download the icon from Figma and place it
in the `icons/` folder.

Then run:

```bash
npm run generate-icons
```


5 changes: 5 additions & 0 deletions icons/Continue.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions icons/Copilot.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions icons/Discord.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions icons/Github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions icons/Youtube.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"preview": "vite preview",
"test": "vitest",
"test:coverage": "vitest run --coverage",
"type-check": "tsc --noEmit -p ./tsconfig.app.json"
"type-check": "tsc --noEmit -p ./tsconfig.app.json",
"generate-icons": "npx @svgr/cli --typescript --no-dimensions --jsx-runtime automatic --out-dir ./src/components/icons/ -- icons"
},
"dependencies": {
"@hey-api/client-fetch": "^0.6.0",
Expand Down
80 changes: 59 additions & 21 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,79 @@ describe("App", () => {
render(<App />);
expect(screen.getByText(/toggle sidebar/i)).toBeVisible();
expect(screen.getByText("Certificates")).toBeVisible();
expect(screen.getByText("Setup")).toBeVisible();
expect(screen.getByText("Help")).toBeVisible();
expect(screen.getByRole("banner", { name: "App header" })).toBeVisible();
expect(
screen.getByRole("heading", { name: /codeGate dashboard/i })
screen.getByRole("heading", { name: /codeGate dashboard/i }),
).toBeVisible();

await userEvent.click(screen.getByText("Certificates"));

expect(
screen.getByRole("link", {
screen.getByRole("menuitem", {
name: /certificate security/i,
})
}),
).toBeVisible();
expect(
screen.getByRole("link", {
name: /set up in continue/i,
})
screen.getByRole("menuitem", {
name: /download/i,
}),
).toBeVisible();

await userEvent.click(screen.getByText("Certificates"));
await userEvent.click(screen.getByText("Help"));

expect(
screen.getByRole("link", {
name: /set up in copilot/i,
})
screen.getByRole("menuitem", {
name: /set up in continue/i,
}),
).toBeVisible();

expect(
screen.getByRole("link", {
name: /download/i,
})
screen.getByRole("menuitem", {
name: /set up in copilot/i,
}),
).toBeVisible();

expect(
screen.getByRole("link", {
screen.getByRole("menuitem", {
name: /documentation/i,
})
}),
).toBeVisible();

const discordMenuItem = screen.getByRole("menuitem", {
name: /discord/i,
});
expect(discordMenuItem).toBeVisible();
expect(discordMenuItem).toHaveAttribute(
"href",
"https://discord.gg/stacklok",
);

const githubMenuItem = screen.getByRole("menuitem", {
name: /github/i,
});
expect(githubMenuItem).toBeVisible();
expect(githubMenuItem).toHaveAttribute(
"href",
"https://github.com/stacklok/codegate",
);

const youtubeMenuItem = screen.getByRole("menuitem", {
name: /youtube/i,
});
expect(youtubeMenuItem).toBeVisible();
expect(youtubeMenuItem).toHaveAttribute(
"href",
"https://www.youtube.com/@Stacklok",
);

await userEvent.click(screen.getByText("Help"));

await waitFor(() =>
expect(
screen.getByRole("link", { name: /codeGate dashboard/i })
).toBeVisible()
screen.getByRole("link", { name: /codeGate dashboard/i }),
).toBeVisible(),
);
});

Expand All @@ -63,8 +101,8 @@ describe("App", () => {

await waitFor(() =>
expect(
screen.getByRole("link", { name: "CodeGate Dashboard" })
).toBeVisible()
screen.getByRole("link", { name: "CodeGate Dashboard" }),
).toBeVisible(),
);

const workspaceSelectionButton = screen.getByRole("button", {
Expand All @@ -78,8 +116,8 @@ describe("App", () => {
expect(
screen.getByRole("option", {
name: /anotherworkspae/i,
})
).toBeVisible()
}),
).toBeVisible(),
);
});
});
23 changes: 9 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,24 @@ import { usePromptsData } from "./hooks/usePromptsData";
import { Sidebar } from "./components/Sidebar";
import { useSse } from "./hooks/useSse";
import Page from "./Page";
import { useHref, useNavigate } from "react-router-dom";
import { RouterProvider } from "@stacklok/ui-kit";

function App() {
const { data: prompts, isLoading } = usePromptsData();
const navigate = useNavigate();
useSse();

return (
<RouterProvider navigate={navigate} useHref={useHref}>
<div className="flex w-screen h-screen">
<Sidebar loading={isLoading}>
<PromptList prompts={prompts ?? []} />
</Sidebar>
<div className="flex-1 flex flex-col overflow-hidden">
<Header />
<div className="flex w-screen h-screen">
<Sidebar loading={isLoading}>
<PromptList prompts={prompts ?? []} />
</Sidebar>
<div className="flex-1 flex flex-col overflow-hidden">
<Header />

<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-3">
<Page />
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-3">
<Page />
</div>
</div>
</RouterProvider>
</div>
);
}

Expand Down
82 changes: 47 additions & 35 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Link } from "react-router-dom";
import { SidebarTrigger } from "./ui/sidebar";
import { HoverPopover } from "./HoverPopover";
import { Separator, ButtonDarkMode } from "@stacklok/ui-kit";
import { Separator, ButtonDarkMode, MenuItem } from "@stacklok/ui-kit";
import { WorkspacesSelection } from "@/features/workspace/components/workspaces-selection";
import { BookOpenText, Download, ShieldCheck } from "lucide-react";
import { Continue, Copilot, Discord, Github, Youtube } from "./icons";

export function Header({ hasError }: { hasError?: boolean }) {
return (
Expand Down Expand Up @@ -30,47 +32,57 @@ export function Header({ hasError }: { hasError?: boolean }) {
</div>
<div className="flex items-center gap-4 mr-16">
<HoverPopover title="Certificates">
<Link
to="/certificates"
className="block px-5 py-3 text-secondary hover:bg-brand-50"
<MenuItem href="/certificates/security" icon={<ShieldCheck />}>
About certificate security
</MenuItem>
<MenuItem icon={<Download />} href="/certificates">
Download certificates
</MenuItem>
</HoverPopover>

<HoverPopover title="Help">
<MenuItem href="/help/continue-setup" icon={<Continue />}>
Set up in <span className="font-bold">Continue</span>
</MenuItem>
<MenuItem icon={<Copilot />} href="/help/copilot-setup">
Set up in <span className="font-bold">Copilot</span>
</MenuItem>

<MenuItem
href="https://docs.codegate.ai/"
target="_blank"
icon={<BookOpenText />}
>
Download
</Link>
<Link
to="/certificates/security"
className="block px-5 py-3 text-secondary hover:bg-brand-50"
Documentation
</MenuItem>

<Separator />

<MenuItem
href="https://discord.gg/stacklok"
target="_blank"
icon={<Discord />}
>
Certificate Security
</Link>
</HoverPopover>
Discord
</MenuItem>

<HoverPopover title="Setup">
<Link
to="/help/continue-setup"
className="block px-5 py-3 text-secondary hover:bg-brand-50"
<MenuItem
href="https://github.com/stacklok/codegate"
target="_blank"
icon={<Github />}
>
Set up in <span className="font-bold">Continue</span>
</Link>
<Link
to="/help/copilot-setup"
className="block px-5 py-3 text-secondary hover:bg-brand-50"
GitHub
</MenuItem>

<MenuItem
href="https://www.youtube.com/@Stacklok"
target="_blank"
icon={<Youtube />}
>
Set up in <span className="font-bold">Copilot</span>
</Link>
YouTube
</MenuItem>
</HoverPopover>

<div className="flex items-center relative group">
<div className="text-primary hover:text-secondary font-semibold cursor-pointer text-base px-2 py-1 rounded-md transition-colors">
<a
href="https://docs.codegate.ai/"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</div>

<ButtonDarkMode />
</div>
</header>
Expand Down
32 changes: 19 additions & 13 deletions src/components/HoverPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { ReactNode } from "react";
import { twMerge } from "tailwind-merge";
import { Button, DropdownMenu, MenuTrigger, Popover } from "@stacklok/ui-kit";
import { OverlayTriggerStateContext } from "react-aria-components";
import { ReactNode, useContext } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";

function PopoverIcon() {
const { isOpen = false } = useContext(OverlayTriggerStateContext) ?? {};

return isOpen ? <ChevronUp /> : <ChevronDown />;
}

export function HoverPopover({
children,
title,
className
}: {
title: ReactNode;
children: ReactNode;
className?: string
className?: string;
}) {
return (
<div className={twMerge("flex items-center relative group/hoverPopover", className)}>
<div className="text-primary hover:text-secondary font-semibold cursor-pointer text-base px-2 py-1 rounded-md transition-colors">
<MenuTrigger>
<Button variant="tertiary">
{title}
</div>
<div className="absolute right-0 top-full mt-2 w-56 bg-base rounded-lg shadow-lg opacity-0 invisible group-hover/hoverPopover:opacity-100 group-hover/hoverPopover:visible transition-all duration-200 z-50 border border-gray-200">
<div className="py-1">
{children}
</div>
</div>
</div>
<PopoverIcon />
</Button>
<Popover>
<DropdownMenu>{children}</DropdownMenu>
</Popover>
</MenuTrigger>
);
}
15 changes: 15 additions & 0 deletions src/components/icons/Continue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { SVGProps } from "react";
const SvgContinue = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 18"
{...props}
>
<path
fill="#2E323A"
d="m16.114 2.483-1.081 1.815 2.733 4.58c.02.035.032.078.032.116a.24.24 0 0 1-.032.116l-2.733 4.584 1.081 1.815L20 8.994 16.114 2.48zm-1.5 1.58 1.081-1.815h-2.162l-1.081 1.815h2.166zm-2.166.47 2.525 4.23h2.162l-2.521-4.23zm2.166 8.93 2.521-4.233h-2.162l-2.525 4.232zm-2.166.47 1.08 1.808h2.163l-1.081-1.807h-2.166zm-7.33 2.256A.25.25 0 0 1 5 16.158a.23.23 0 0 1-.088-.085l-2.737-4.584H.012L3.898 18h7.768l-1.082-1.811H5.12m5.885-.236 1.082 1.812 1.08-1.816-1.08-1.815-1.082 1.815zm.663-2.05H6.623l-1.081 1.815h5.042zM6.2 13.67 3.674 9.438l-1.08 1.815 2.525 4.233zM.008 11.018H2.17l1.082-1.815H1.093zM4.899 1.93a.23.23 0 0 1 .088-.085c.036-.02.08-.03.12-.03h5.47L11.657 0H3.887L0 6.515h2.162l2.73-4.58zM3.252 8.797 2.17 6.982H.008l1.081 1.815zm1.859-6.28-2.522 4.23L3.67 8.562l2.522-4.229zm5.47-.235H5.53l1.08 1.815h5.052zm1.504 1.58 1.077-1.811L12.085.236l-1.082 1.81z"
/>
</svg>
);
export default SvgContinue;
Loading
Loading