Skip to content

refactor: improve settings sidebar components #10801

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 1 commit into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
120 changes: 19 additions & 101 deletions site/src/components/DeploySettingsLayout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,135 +7,53 @@ import Globe from "@mui/icons-material/PublicOutlined";
import HubOutlinedIcon from "@mui/icons-material/HubOutlined";
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
import MonitorHeartOutlined from "@mui/icons-material/MonitorHeartOutlined";
import { GitIcon } from "components/Icons/GitIcon";
import { Stack } from "components/Stack/Stack";
import type { ElementType, FC, PropsWithChildren, ReactNode } from "react";
import { NavLink } from "react-router-dom";
import { type FC } from "react";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { css } from "@emotion/css";
import { useTheme } from "@emotion/react";

const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }>
> = ({ children, href, icon }) => {
const theme = useTheme();

const activeStyles = css`
background-color: ${theme.palette.action.hover};

&::before {
content: "";
display: block;
width: 3px;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: ${theme.palette.primary.main};
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
`;

return (
<NavLink
to={href}
className={({ isActive }) => css`
${isActive && activeStyles}

color: inherit;
display: block;
font-size: 14px;
text-decoration: none;
padding: 12px 12px 12px 16px;
border-radius: 4px;
transition: background-color 0.15s ease-in-out;
margin-bottom: 1;
position: relative;

&:hover {
background-color: ${theme.palette.action.hover};
}
`}
>
<Stack alignItems="center" spacing={1.5} direction="row">
{icon}
{children}
</Stack>
</NavLink>
);
};

const SidebarNavItemIcon: FC<{ icon: ElementType }> = ({ icon: Icon }) => {
return <Icon css={{ width: 16, height: 16 }} />;
};
import { GitIcon } from "components/Icons/GitIcon";
import {
Sidebar as BaseSidebar,
SidebarNavItem,
} from "components/Sidebar/Sidebar";

export const Sidebar: React.FC = () => {
export const Sidebar: FC = () => {
const dashboard = useDashboard();

return (
<nav css={{ width: 245 }}>
<SidebarNavItem
href="general"
icon={<SidebarNavItemIcon icon={LaunchOutlined} />}
>
<BaseSidebar>
<SidebarNavItem href="general" icon={LaunchOutlined}>
General
</SidebarNavItem>
<SidebarNavItem
href="licenses"
icon={<SidebarNavItemIcon icon={ApprovalIcon} />}
>
<SidebarNavItem href="licenses" icon={ApprovalIcon}>
Licenses
</SidebarNavItem>
<SidebarNavItem
href="appearance"
icon={<SidebarNavItemIcon icon={Brush} />}
>
<SidebarNavItem href="appearance" icon={Brush}>
Appearance
</SidebarNavItem>
<SidebarNavItem
href="userauth"
icon={<SidebarNavItemIcon icon={VpnKeyOutlined} />}
>
<SidebarNavItem href="userauth" icon={VpnKeyOutlined}>
User Authentication
</SidebarNavItem>
<SidebarNavItem
href="external-auth"
icon={<SidebarNavItemIcon icon={GitIcon} />}
>
<SidebarNavItem href="external-auth" icon={GitIcon}>
External Authentication
</SidebarNavItem>
<SidebarNavItem href="network" icon={<SidebarNavItemIcon icon={Globe} />}>
<SidebarNavItem href="network" icon={Globe}>
Network
</SidebarNavItem>
{dashboard.experiments.includes("moons") && (
<SidebarNavItem
href="workspace-proxies"
icon={<SidebarNavItemIcon icon={HubOutlinedIcon} />}
>
<SidebarNavItem href="workspace-proxies" icon={HubOutlinedIcon}>
Workspace Proxies
</SidebarNavItem>
)}
<SidebarNavItem
href="security"
icon={<SidebarNavItemIcon icon={LockRounded} />}
>
<SidebarNavItem href="security" icon={LockRounded}>
Security
</SidebarNavItem>
<SidebarNavItem
href="observability"
icon={<SidebarNavItemIcon icon={InsertChartIcon} />}
>
<SidebarNavItem href="observability" icon={InsertChartIcon}>
Observability
</SidebarNavItem>
{dashboard.experiments.includes("deployment_health_page") && (
<SidebarNavItem
href="/health"
icon={<SidebarNavItemIcon icon={MonitorHeartOutlined} />}
>
<SidebarNavItem href="/health" icon={MonitorHeartOutlined}>
Health
</SidebarNavItem>
)}
</nav>
</BaseSidebar>
);
};
6 changes: 2 additions & 4 deletions site/src/components/SettingsLayout/Section.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type FC, type ReactNode, type PropsWithChildren } from "react";
import { type FC, type ReactNode } from "react";
import { type Interpolation, type Theme } from "@emotion/react";

type SectionLayout = "fixed" | "fluid";
Expand All @@ -15,9 +15,7 @@ export interface SectionProps {
children?: ReactNode;
}

type SectionFC = FC<PropsWithChildren<SectionProps>>;

export const Section: SectionFC = ({
export const Section: FC<SectionProps> = ({
id,
title,
description,
Expand Down
154 changes: 20 additions & 134 deletions site/src/components/SettingsLayout/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,163 +1,49 @@
import { css } from "@emotion/css";
import {
type CSSObject,
type Interpolation,
type Theme,
useTheme,
} from "@emotion/react";
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
import FingerprintOutlinedIcon from "@mui/icons-material/FingerprintOutlined";
import {
type FC,
type ComponentType,
type PropsWithChildren,
type ReactNode,
} from "react";
import { NavLink } from "react-router-dom";
import AccountIcon from "@mui/icons-material/Person";
import ScheduleIcon from "@mui/icons-material/EditCalendarOutlined";
import SecurityIcon from "@mui/icons-material/LockOutlined";
import type { User } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { combineClasses } from "utils/combineClasses";

const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }>
> = ({ children, href, icon }) => {
const theme = useTheme();

const sidebarNavItemStyles = css`
color: inherit;
display: block;
font-size: 14px;
text-decoration: none;
padding: 12px 12px 12px 16px;
border-radius: 4px;
transition: background-color 0.15s ease-in-out;
margin-bottom: 1px;
position: relative;

&:hover {
background-color: theme.palette.action.hover;
}
`;

const sidebarNavItemActiveStyles = css`
background-color: ${theme.palette.action.hover};

&:before {
content: "";
display: block;
width: 3px;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: ${theme.palette.primary.main};
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
`;

return (
<NavLink
to={href}
className={({ isActive }) =>
combineClasses([
sidebarNavItemStyles,
isActive ? sidebarNavItemActiveStyles : undefined,
])
}
>
<Stack alignItems="center" spacing={1.5} direction="row">
{icon}
{children}
</Stack>
</NavLink>
);
};

const SidebarNavItemIcon: React.FC<{
icon: ComponentType<{ className?: string }>;
}> = ({ icon: Icon }) => {
return <Icon css={{ width: 16, height: 16 }} />;
};
import {
Sidebar as BaseSidebar,
SidebarHeader,
SidebarNavItem,
} from "components/Sidebar/Sidebar";

export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
const { entitlements } = useDashboard();
const allowAutostopRequirement =
entitlements.features.template_autostop_requirement.enabled;

return (
<nav css={styles.sidebar}>
<Stack direction="row" alignItems="center" css={styles.userInfo}>
<UserAvatar username={user.username} avatarURL={user.avatar_url} />
<Stack spacing={0} css={styles.userData}>
<span css={styles.username}>{user.username}</span>
<span css={styles.email}>{user.email}</span>
</Stack>
</Stack>

<SidebarNavItem
href="account"
icon={<SidebarNavItemIcon icon={AccountIcon} />}
>
<BaseSidebar>
<SidebarHeader
avatar={
<UserAvatar username={user.username} avatarURL={user.avatar_url} />
}
title={user.username}
subtitle={user.email}
/>
;
<SidebarNavItem href="account" icon={AccountIcon}>
Account
</SidebarNavItem>
{allowAutostopRequirement && (
<SidebarNavItem
href="schedule"
icon={<SidebarNavItemIcon icon={ScheduleIcon} />}
>
<SidebarNavItem href="schedule" icon={ScheduleIcon}>
Schedule
</SidebarNavItem>
)}
<SidebarNavItem
href="security"
icon={<SidebarNavItemIcon icon={SecurityIcon} />}
>
<SidebarNavItem href="security" icon={SecurityIcon}>
Security
</SidebarNavItem>
<SidebarNavItem
href="ssh-keys"
icon={<SidebarNavItemIcon icon={FingerprintOutlinedIcon} />}
>
<SidebarNavItem href="ssh-keys" icon={FingerprintOutlinedIcon}>
SSH Keys
</SidebarNavItem>
<SidebarNavItem
href="tokens"
icon={<SidebarNavItemIcon icon={VpnKeyOutlined} />}
>
<SidebarNavItem href="tokens" icon={VpnKeyOutlined}>
Tokens
</SidebarNavItem>
</nav>
</BaseSidebar>
);
};

const styles = {
sidebar: {
width: 245,
flexShrink: 0,
},
userInfo: (theme) => ({
...(theme.typography.body2 as CSSObject),
marginBottom: 16,
}),
userData: {
overflow: "hidden",
},
username: {
fontWeight: 600,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
email: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 12,
overflow: "hidden",
textOverflow: "ellipsis",
}),
} satisfies Record<string, Interpolation<Theme>>;
Loading