mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): fix tabs in the template layout (#10334)
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { NavLink, NavLinkProps } from "react-router-dom";
|
||||||
|
import { combineClasses } from "utils/combineClasses";
|
||||||
|
import { Margins } from "components/Margins/Margins";
|
||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
|
||||||
|
export const Tabs = ({ children }: { children: ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={(theme) => ({
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
marginBottom: theme.spacing(5),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Margins
|
||||||
|
css={(theme) => ({
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing(0.25),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Margins>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TabLink = (props: NavLinkProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const baseTabLink = css`
|
||||||
|
text-decoration: none;
|
||||||
|
color: ${theme.palette.text.secondary};
|
||||||
|
font-size: 14px;
|
||||||
|
display: block;
|
||||||
|
padding: ${theme.spacing(0, 2, 2)};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${theme.palette.text.primary};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const activeTabLink = css`
|
||||||
|
color: ${theme.palette.text.primary};
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
background: ${theme.palette.secondary.dark};
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
className={({ isActive }) =>
|
||||||
|
combineClasses([
|
||||||
|
baseTabLink,
|
||||||
|
isActive ? activeTabLink : undefined,
|
||||||
|
props.className as string,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Link from "@mui/material/Link";
|
import Link from "@mui/material/Link";
|
||||||
import { makeStyles } from "@mui/styles";
|
|
||||||
import GroupAdd from "@mui/icons-material/GroupAddOutlined";
|
import GroupAdd from "@mui/icons-material/GroupAddOutlined";
|
||||||
import PersonAdd from "@mui/icons-material/PersonAddOutlined";
|
import PersonAdd from "@mui/icons-material/PersonAddOutlined";
|
||||||
import { USERS_LINK } from "components/Dashboard/Navbar/NavbarView";
|
import { USERS_LINK } from "components/Dashboard/Navbar/NavbarView";
|
||||||
@@ -8,18 +7,11 @@ import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
|
|||||||
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
|
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
|
||||||
import { usePermissions } from "hooks/usePermissions";
|
import { usePermissions } from "hooks/usePermissions";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import {
|
import { Link as RouterLink, Outlet, useNavigate } from "react-router-dom";
|
||||||
Link as RouterLink,
|
|
||||||
NavLink,
|
|
||||||
Outlet,
|
|
||||||
useNavigate,
|
|
||||||
} from "react-router-dom";
|
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
import { Margins } from "components/Margins/Margins";
|
import { Margins } from "components/Margins/Margins";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { TabLink, Tabs } from "components/Tabs/Tabs";
|
||||||
|
|
||||||
export const UsersLayout: FC = () => {
|
export const UsersLayout: FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
const { createUser: canCreateUser, createGroup: canCreateGroup } =
|
const { createUser: canCreateUser, createGroup: canCreateGroup } =
|
||||||
usePermissions();
|
usePermissions();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -53,35 +45,10 @@ export const UsersLayout: FC = () => {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
</Margins>
|
</Margins>
|
||||||
|
|
||||||
<div className={styles.tabs}>
|
<Tabs>
|
||||||
<Margins>
|
<TabLink to={USERS_LINK}>Users</TabLink>
|
||||||
<Stack direction="row" spacing={0.25}>
|
<TabLink to="/groups">Groups</TabLink>
|
||||||
<NavLink
|
</Tabs>
|
||||||
end
|
|
||||||
to={USERS_LINK}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
combineClasses([
|
|
||||||
styles.tabItem,
|
|
||||||
isActive ? styles.tabItemActive : undefined,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Users
|
|
||||||
</NavLink>
|
|
||||||
<NavLink
|
|
||||||
to="/groups"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
combineClasses([
|
|
||||||
styles.tabItem,
|
|
||||||
isActive ? styles.tabItemActive : undefined,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Groups
|
|
||||||
</NavLink>
|
|
||||||
</Stack>
|
|
||||||
</Margins>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Margins>
|
<Margins>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
@@ -89,39 +56,3 @@ export const UsersLayout: FC = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStyles = makeStyles((theme) => {
|
|
||||||
return {
|
|
||||||
tabs: {
|
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
||||||
marginBottom: theme.spacing(5),
|
|
||||||
},
|
|
||||||
|
|
||||||
tabItem: {
|
|
||||||
textDecoration: "none",
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
fontSize: 14,
|
|
||||||
display: "block",
|
|
||||||
padding: theme.spacing(0, 2, 2),
|
|
||||||
|
|
||||||
"&:hover": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
tabItemActive: {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
position: "relative",
|
|
||||||
|
|
||||||
"&:before": {
|
|
||||||
content: `""`,
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
height: 2,
|
|
||||||
width: "100%",
|
|
||||||
background: theme.palette.secondary.dark,
|
|
||||||
position: "absolute",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { css } from "@emotion/css";
|
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { createContext, type FC, Suspense, useContext } from "react";
|
import { createContext, type FC, Suspense, useContext } from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { NavLink, Outlet, useNavigate, useParams } from "react-router-dom";
|
import { Outlet, useNavigate, useParams } from "react-router-dom";
|
||||||
import type { AuthorizationRequest } from "api/typesGenerated";
|
import type { AuthorizationRequest } from "api/typesGenerated";
|
||||||
import {
|
import {
|
||||||
checkAuthorization,
|
checkAuthorization,
|
||||||
@@ -11,10 +10,10 @@ import {
|
|||||||
} from "api/api";
|
} from "api/api";
|
||||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||||
import { Margins } from "components/Margins/Margins";
|
import { Margins } from "components/Margins/Margins";
|
||||||
import { Stack } from "components/Stack/Stack";
|
|
||||||
import { Loader } from "components/Loader/Loader";
|
import { Loader } from "components/Loader/Loader";
|
||||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||||
import { TemplatePageHeader } from "./TemplatePageHeader";
|
import { TemplatePageHeader } from "./TemplatePageHeader";
|
||||||
|
import { TabLink, Tabs } from "components/Tabs/Tabs";
|
||||||
|
|
||||||
const templatePermissions = (
|
const templatePermissions = (
|
||||||
templateId: string,
|
templateId: string,
|
||||||
@@ -85,34 +84,6 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
|||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemStyles = css`
|
|
||||||
text-decoration: none;
|
|
||||||
color: ${theme.palette.text.secondary};
|
|
||||||
font-size: 14;
|
|
||||||
display: block;
|
|
||||||
padding: ${theme.spacing(0, 2, 2)};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: ${theme.palette.text.primary};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const activeItemStyles = css`
|
|
||||||
${itemStyles}
|
|
||||||
color: ${theme.palette.text.primary};
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: "";
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
height: 2;
|
|
||||||
width: 100%;
|
|
||||||
background: ${theme.palette.secondary.dark};
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TemplatePageHeader
|
<TemplatePageHeader
|
||||||
@@ -124,71 +95,20 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<Tabs>
|
||||||
css={{
|
<TabLink end to={`/templates/${templateName}`}>
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
Summary
|
||||||
marginBottom: theme.spacing(5),
|
</TabLink>
|
||||||
}}
|
<TabLink to={`/templates/${templateName}/docs`}>Docs</TabLink>
|
||||||
>
|
{data.permissions.canUpdateTemplate && (
|
||||||
<Margins>
|
<TabLink to={`/templates/${templateName}/files`}>Source Code</TabLink>
|
||||||
<Stack direction="row" spacing={0.25}>
|
)}
|
||||||
<NavLink
|
<TabLink to={`/templates/${templateName}/versions`}>Versions</TabLink>
|
||||||
end
|
<TabLink to={`/templates/${templateName}/embed`}>Embed</TabLink>
|
||||||
to={`/templates/${templateName}`}
|
{shouldShowInsights && (
|
||||||
className={({ isActive }) =>
|
<TabLink to={`/templates/${templateName}/insights`}>Insights</TabLink>
|
||||||
isActive ? activeItemStyles : itemStyles
|
)}
|
||||||
}
|
</Tabs>
|
||||||
>
|
|
||||||
Summary
|
|
||||||
</NavLink>
|
|
||||||
<NavLink
|
|
||||||
end
|
|
||||||
to={`/templates/${templateName}/docs`}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
isActive ? activeItemStyles : itemStyles
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Docs
|
|
||||||
</NavLink>
|
|
||||||
{data.permissions.canUpdateTemplate && (
|
|
||||||
<NavLink
|
|
||||||
to={`/templates/${templateName}/files`}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
isActive ? activeItemStyles : itemStyles
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Source Code
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
<NavLink
|
|
||||||
to={`/templates/${templateName}/versions`}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
isActive ? activeItemStyles : itemStyles
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Versions
|
|
||||||
</NavLink>
|
|
||||||
<NavLink
|
|
||||||
to={`/templates/${templateName}/embed`}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
isActive ? activeItemStyles : itemStyles
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Embed
|
|
||||||
</NavLink>
|
|
||||||
{shouldShowInsights && (
|
|
||||||
<NavLink
|
|
||||||
to={`/templates/${templateName}/insights`}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
isActive ? activeItemStyles : itemStyles
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Insights
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Margins>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Margins>
|
<Margins>
|
||||||
<TemplateLayoutContext.Provider value={data}>
|
<TemplateLayoutContext.Provider value={data}>
|
||||||
|
|||||||
Reference in New Issue
Block a user