diff --git a/site/.eslintrc.yaml b/site/.eslintrc.yaml index 0991916c76064..e2d919bcd85e9 100644 --- a/site/.eslintrc.yaml +++ b/site/.eslintrc.yaml @@ -142,6 +142,8 @@ rules: - name: "@mui/material/Typography" message: "You should use the native HTML elements as span, p, h1, h2, h3..." + - name: "@mui/material/Box" + message: "You should use a
instead" no-unused-vars: "off" "object-curly-spacing": "off" react-hooks/exhaustive-deps: warn diff --git a/site/package.json b/site/package.json index 8b6c55622472c..8943a338eac6b 100644 --- a/site/package.json +++ b/site/package.json @@ -40,7 +40,6 @@ "@mui/icons-material": "5.14.0", "@mui/lab": "5.0.0-alpha.129", "@mui/material": "5.14.0", - "@mui/styles": "5.14.0", "@mui/system": "5.14.0", "@mui/utils": "5.14.11", "@vitejs/plugin-react": "4.1.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 53e16f6e0ca91..a923fcc90458f 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -45,9 +45,6 @@ dependencies: '@mui/material': specifier: 5.14.0 version: 5.14.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@mui/styles': - specifier: 5.14.0 - version: 5.14.0(@types/react@18.2.6)(react@18.2.0) '@mui/system': specifier: 5.14.0 version: 5.14.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) @@ -3096,37 +3093,6 @@ packages: react: 18.2.0 dev: false - /@mui/styles@5.14.0(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-+nXPk/7qhlJn2QGSBlB42gM9G4shLDEAfkTqjUoCDsS3qPo7ZlpM2X5SgnCNoYhXCn820R0YxaJYd19rmC3FSA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.22.6 - '@emotion/hash': 0.9.1 - '@mui/private-theming': 5.13.7(@types/react@18.2.6)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) - '@types/react': 18.2.6 - clsx: 1.2.1 - csstype: 3.1.2 - hoist-non-react-statics: 3.3.2 - jss: 10.10.0 - jss-plugin-camel-case: 10.10.0 - jss-plugin-default-unit: 10.10.0 - jss-plugin-global: 10.10.0 - jss-plugin-nested: 10.10.0 - jss-plugin-props-sort: 10.10.0 - jss-plugin-rule-value-function: 10.10.0 - jss-plugin-vendor-prefixer: 10.10.0 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - /@mui/system@5.14.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0): resolution: {integrity: sha512-0HZGkX8miJbiNw+rjlZ9l0Cfkz1bSqfSHQH0EH9J+nx0aAm5cBleg9piOlLdCNIWGgecCqsw4x62erGrGjjcJg==} engines: {node: '>=12.0.0'} @@ -7218,13 +7184,6 @@ packages: source-map: 0.6.1 dev: false - /css-vendor@2.0.8: - resolution: {integrity: sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==} - dependencies: - '@babel/runtime': 7.23.2 - is-in-browser: 1.1.3 - dev: false - /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} dev: true @@ -9252,10 +9211,6 @@ packages: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} dev: false - /is-in-browser@1.1.3: - resolution: {integrity: sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==} - dev: false - /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -10174,68 +10129,6 @@ packages: graceful-fs: 4.2.11 dev: true - /jss-plugin-camel-case@10.10.0: - resolution: {integrity: sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==} - dependencies: - '@babel/runtime': 7.23.2 - hyphenate-style-name: 1.0.4 - jss: 10.10.0 - dev: false - - /jss-plugin-default-unit@10.10.0: - resolution: {integrity: sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - dev: false - - /jss-plugin-global@10.10.0: - resolution: {integrity: sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - dev: false - - /jss-plugin-nested@10.10.0: - resolution: {integrity: sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - tiny-warning: 1.0.3 - dev: false - - /jss-plugin-props-sort@10.10.0: - resolution: {integrity: sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - dev: false - - /jss-plugin-rule-value-function@10.10.0: - resolution: {integrity: sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==} - dependencies: - '@babel/runtime': 7.23.2 - jss: 10.10.0 - tiny-warning: 1.0.3 - dev: false - - /jss-plugin-vendor-prefixer@10.10.0: - resolution: {integrity: sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==} - dependencies: - '@babel/runtime': 7.23.2 - css-vendor: 2.0.8 - jss: 10.10.0 - dev: false - - /jss@10.10.0: - resolution: {integrity: sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==} - dependencies: - '@babel/runtime': 7.23.2 - csstype: 3.1.2 - is-in-browser: 1.1.3 - tiny-warning: 1.0.3 - dev: false - /jsx-ast-utils@3.3.4: resolution: {integrity: sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==} engines: {node: '>=4.0'} @@ -14244,7 +14137,7 @@ packages: peerDependencies: eslint: '>=7' meow: ^9.0.0 - optionator: 0.9.3 + optionator: ^0.9.1 stylelint: '>=13' typescript: '*' vite: '>=2.0.0' diff --git a/site/src/@types/emotion.d.ts b/site/src/@types/emotion.d.ts index 1bb431a7e2e41..3e6adf9cd846a 100644 --- a/site/src/@types/emotion.d.ts +++ b/site/src/@types/emotion.d.ts @@ -1,8 +1,5 @@ -import type { DefaultTheme as MuiTheme } from "@mui/system"; -import type { NewTheme } from "theme/experimental"; +import type { Theme as MuiTheme } from "@mui/material/styles"; declare module "@emotion/react" { - interface Theme extends MuiTheme { - experimental: NewTheme; - } + interface Theme extends MuiTheme {} } diff --git a/site/src/@types/mui.d.ts b/site/src/@types/mui.d.ts index 50ca165c18ce2..17abc0aa2db65 100644 --- a/site/src/@types/mui.d.ts +++ b/site/src/@types/mui.d.ts @@ -1,14 +1,6 @@ -import type { - PaletteColor, - PaletteColorOptions, - Theme, -} from "@mui/material/styles"; +import type { PaletteColor, PaletteColorOptions } from "@mui/material/styles"; import type { NewTheme } from "theme/experimental"; -declare module "@mui/styles/defaultTheme" { - interface DefaultTheme extends Theme {} -} - declare module "@mui/material/styles" { interface Theme { experimental: NewTheme; diff --git a/site/src/App.tsx b/site/src/App.tsx index da700660d77ed..7fa09d271d194 100644 --- a/site/src/App.tsx +++ b/site/src/App.tsx @@ -48,12 +48,14 @@ export const ThemeProviders: FC = ({ children }) => { ); }; -export const AppProviders = ({ - children, - queryClient = defaultQueryClient, -}: { +interface AppProvidersProps { children: ReactNode; queryClient?: QueryClient; +} + +export const AppProviders: FC = ({ + children, + queryClient = defaultQueryClient, }) => { return ( diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index 15ff04864ccb1..8541297819b99 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -1,6 +1,3 @@ -import Box from "@mui/material/Box"; -import { Theme } from "@mui/material/styles"; -import useTheme from "@mui/styles/useTheme"; import { CategoryScale, Chart as ChartJS, @@ -22,7 +19,8 @@ import { HelpTooltipText, } from "components/HelpTooltip/HelpTooltip"; import dayjs from "dayjs"; -import { FC } from "react"; +import { useTheme } from "@emotion/react"; +import { type FC } from "react"; import { Line } from "react-chartjs-2"; import annotationPlugin from "chartjs-plugin-annotation"; @@ -42,7 +40,7 @@ ChartJS.register( const USER_LIMIT_DISPLAY_THRESHOLD = 60; export interface ActiveUserChartProps { - data: { date: string; amount: number }[]; + data: Array<{ date: string; amount: number }>; interval: "day" | "week"; userLimit: number | undefined; } @@ -52,7 +50,7 @@ export const ActiveUserChart: FC = ({ interval, userLimit, }) => { - const theme: Theme = useTheme(); + const theme = useTheme(); const labels = data.map((val) => dayjs(val.date).format("YYYY-MM-DD")); const chartData = data.map((val) => val.amount); @@ -137,9 +135,9 @@ export const ActiveUserChart: FC = ({ ); }; -export const ActiveUsersTitle = () => { +export const ActiveUsersTitle: FC = () => { return ( - +
Active Users How do we calculate active users? @@ -148,7 +146,7 @@ export const ActiveUsersTitle = () => { considered an active user. e.g. apps, web terminal, SSH - +
); }; diff --git a/site/src/components/Alert/Alert.tsx b/site/src/components/Alert/Alert.tsx index bba4044c4ce86..6191de2c55592 100644 --- a/site/src/components/Alert/Alert.tsx +++ b/site/src/components/Alert/Alert.tsx @@ -1,9 +1,15 @@ -import { useState, FC, ReactNode } from "react"; +import { + useState, + type FC, + type ReactNode, + type PropsWithChildren, +} from "react"; import Collapse from "@mui/material/Collapse"; // eslint-disable-next-line no-restricted-imports -- It is the base component -import MuiAlert, { AlertProps as MuiAlertProps } from "@mui/material/Alert"; +import MuiAlert, { + type AlertProps as MuiAlertProps, +} from "@mui/material/Alert"; import Button from "@mui/material/Button"; -import Box from "@mui/material/Box"; export type AlertProps = MuiAlertProps & { actions?: ReactNode; @@ -32,7 +38,7 @@ export const Alert: FC = ({ @@ -62,15 +68,13 @@ export const Alert: FC = ({ ); }; -export const AlertDetail = ({ children }: { children: ReactNode }) => { +export const AlertDetail: FC = ({ children }) => { return ( - theme.palette.text.secondary} - fontSize={13} + ({ color: theme.palette.text.secondary, fontSize: 13 })} data-chromatic="ignore" > {children} - + ); }; diff --git a/site/src/components/Avatar/Avatar.tsx b/site/src/components/Avatar/Avatar.tsx index f47f85e32657d..ebc234e8480a3 100644 --- a/site/src/components/Avatar/Avatar.tsx +++ b/site/src/components/Avatar/Avatar.tsx @@ -1,9 +1,10 @@ // This is the only place MuiAvatar can be used // eslint-disable-next-line no-restricted-imports -- Read above -import MuiAvatar, { AvatarProps as MuiAvatarProps } from "@mui/material/Avatar"; -import { FC, useId } from "react"; +import MuiAvatar, { + type AvatarProps as MuiAvatarProps, +} from "@mui/material/Avatar"; +import { type FC, useId } from "react"; import { css, type Interpolation, type Theme } from "@emotion/react"; -import { Box } from "@mui/system"; import { visuallyHidden } from "@mui/utils"; export type AvatarProps = MuiAvatarProps & { @@ -78,6 +79,8 @@ export const AvatarIcon: FC = ({ src, alt }) => { const hookId = useId(); const avatarId = `${hookId}-avatar`; + // We use a `visuallyHidden` element instead of setting `alt` to avoid + // splatting the text out on the screen if the image fails to load. return ( <> = ({ src, alt }) => { css={{ maxWidth: "50%" }} aria-labelledby={avatarId} /> - +
{alt} - +
); }; diff --git a/site/src/components/Dashboard/DashboardLayout.tsx b/site/src/components/Dashboard/DashboardLayout.tsx index e06888c6b3776..35cc12611e59d 100644 --- a/site/src/components/Dashboard/DashboardLayout.tsx +++ b/site/src/components/Dashboard/DashboardLayout.tsx @@ -1,18 +1,17 @@ -import { DeploymentBanner } from "./DeploymentBanner/DeploymentBanner"; +import Snackbar from "@mui/material/Snackbar"; +import Link from "@mui/material/Link"; +import Button from "@mui/material/Button"; +import InfoOutlined from "@mui/icons-material/InfoOutlined"; +import { type FC, type HTMLAttributes, Suspense } from "react"; +import { Outlet } from "react-router-dom"; import { LicenseBanner } from "components/Dashboard/LicenseBanner/LicenseBanner"; import { Loader } from "components/Loader/Loader"; import { ServiceBanner } from "components/Dashboard/ServiceBanner/ServiceBanner"; import { usePermissions } from "hooks/usePermissions"; -import { FC, Suspense } from "react"; -import { Outlet } from "react-router-dom"; import { dashboardContentBottomPadding } from "theme/constants"; -import { Navbar } from "./Navbar/Navbar"; -import Snackbar from "@mui/material/Snackbar"; -import Link from "@mui/material/Link"; -import Box, { BoxProps } from "@mui/material/Box"; -import InfoOutlined from "@mui/icons-material/InfoOutlined"; -import Button from "@mui/material/Button"; import { docs } from "utils/docs"; +import { Navbar } from "./Navbar/Navbar"; +import { DeploymentBanner } from "./DeploymentBanner/DeploymentBanner"; import { useUpdateCheck } from "./useUpdateCheck"; export const DashboardLayout: FC = () => { @@ -74,21 +73,21 @@ export const DashboardLayout: FC = () => { }), }} message={ - +
({ + css={(theme) => ({ fontSize: 16, height: 20, // 20 is the height of the text line so we can align them color: theme.palette.info.light, })} /> - +

Coder {updateCheck.data?.version} is now available. View the{" "} release notes and{" "} upgrade instructions{" "} for more information. - - +

+
} action={
); }; diff --git a/site/src/components/Dashboard/Navbar/NavbarView.tsx b/site/src/components/Dashboard/Navbar/NavbarView.tsx index f23963bfbbba7..2ef95770a8f56 100644 --- a/site/src/components/Dashboard/Navbar/NavbarView.tsx +++ b/site/src/components/Dashboard/Navbar/NavbarView.tsx @@ -2,7 +2,6 @@ import Drawer from "@mui/material/Drawer"; import IconButton from "@mui/material/IconButton"; import Divider from "@mui/material/Divider"; import Skeleton from "@mui/material/Skeleton"; -import Box from "@mui/material/Box"; import Menu from "@mui/material/Menu"; import Button from "@mui/material/Button"; import MenuItem from "@mui/material/MenuItem"; @@ -45,81 +44,6 @@ export const Language = { deployment: "Deployment", }; -const styles = { - desktopNavItems: (theme) => css` - display: none; - - ${theme.breakpoints.up("md")} { - display: flex; - } - `, - mobileMenuButton: (theme) => css` - ${theme.breakpoints.up("md")} { - display: none; - } - `, - wrapper: (theme) => css` - position: relative; - display: flex; - justify-content: space-between; - align-items: center; - - ${theme.breakpoints.up("md")} { - justify-content: flex-start; - } - `, - drawerHeader: { - padding: 16, - paddingTop: 32, - paddingBottom: 32, - }, - logo: (theme) => css` - align-items: center; - display: flex; - height: ${navHeight}px; - color: ${theme.palette.text.primary}; - padding: 16px; - - // svg is for the Coder logo, img is for custom images - & svg, - & img { - height: 100%; - object-fit: contain; - } - `, - drawerLogo: { - padding: 0, - maxHeight: 40, - }, - item: { - padding: 0, - }, - link: (theme) => css` - align-items: center; - color: ${colors.gray[6]}; - display: flex; - flex: 1; - font-size: 16px; - padding: 12px 16px; - text-decoration: none; - transition: background-color 0.15s ease-in-out; - - &.active { - color: ${theme.palette.text.primary}; - font-weight: 500; - } - - &:hover { - background-color: ${theme.palette.action.hover}; - } - - ${theme.breakpoints.up("md")} { - height: ${navHeight}px; - padding: 0 24px; - } - `, -} satisfies Record>; - interface NavItemsProps { children?: ReactNode; className?: string; @@ -128,9 +52,12 @@ interface NavItemsProps { canViewAllUsers: boolean; } -const NavItems: React.FC = (props) => { - const { className, canViewAuditLog, canViewDeployment, canViewAllUsers } = - props; +const NavItems: FC = ({ + className, + canViewAuditLog, + canViewDeployment, + canViewAllUsers, +}) => { const location = useLocation(); const theme = useTheme(); @@ -181,15 +108,16 @@ export const NavbarView: FC = ({ canViewAllUsers, proxyContextValue, }) => { + const theme = useTheme(); const [isDrawerOpen, setIsDrawerOpen] = useState(false); return ( ); }; -const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ - proxyContextValue, -}) => { +interface ProxyMenuProps { + proxyContextValue: ProxyContextValue; +} + +const ProxyMenu: FC = ({ proxyContextValue }) => { + const theme = useTheme(); const buttonRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [refetchDate, setRefetchDate] = useState(); @@ -305,7 +230,7 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ ); } @@ -317,28 +242,29 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ onClick={() => setIsOpen(true)} size="small" endIcon={} - sx={{ + css={{ borderRadius: "999px", "& .MuiSvgIcon-root": { fontSize: 14 }, }} > {selectedProxy ? ( - - - +
+ - +
-
+ ) : ( "Select Proxy" )} @@ -348,13 +274,13 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ anchorEl={buttonRef.current} onClick={closeMenu} onClose={closeMenu} - sx={{ "& .MuiMenu-paper": { py: 1 } }} + css={{ "& .MuiMenu-paper": { paddingTop: 8, paddingBottom: 8 } }} > - = ({ manually selected, otherwise the default primary region will be used.

-
- theme.palette.divider }} /> + + {proxyContextValue.proxies ?.sort((a, b) => { const latencyA = latencies?.[a.id]?.latencyMS ?? Infinity; @@ -405,33 +331,39 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ }} key={proxy.id} selected={proxy.id === selectedProxy?.id} - sx={{ - fontSize: 14, - }} + css={{ fontSize: 14 }} > - - - +
+ - +
{proxy.display_name} -
+ ))} - theme.palette.divider }} /> + {Boolean(permissions.editWorkspaceProxies) && ( { navigate("deployment/workspace-proxies"); }} @@ -440,7 +372,7 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ )} { // Stop the menu from closing e.stopPropagation(); @@ -455,3 +387,85 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({ ); }; + +const styles = { + desktopNavItems: (theme) => css` + display: none; + + ${theme.breakpoints.up("md")} { + display: flex; + } + `, + mobileMenuButton: (theme) => css` + ${theme.breakpoints.up("md")} { + display: none; + } + `, + navMenus: (theme) => ({ + display: "flex", + gap: 16, + alignItems: "center", + paddingRight: 16, + + [theme.breakpoints.up("md")]: { + marginLeft: "auto", + }, + }), + wrapper: (theme) => css` + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + + ${theme.breakpoints.up("md")} { + justify-content: flex-start; + } + `, + drawerHeader: { + padding: 16, + paddingTop: 32, + paddingBottom: 32, + }, + logo: (theme) => css` + align-items: center; + display: flex; + height: ${navHeight}px; + color: ${theme.palette.text.primary}; + padding: 16px; + + // svg is for the Coder logo, img is for custom images + & svg, + & img { + height: 100%; + object-fit: contain; + } + `, + drawerLogo: { + padding: 0, + maxHeight: 40, + }, + link: (theme) => css` + align-items: center; + color: ${colors.gray[6]}; + display: flex; + flex: 1; + font-size: 16px; + padding: 12px 16px; + text-decoration: none; + transition: background-color 0.15s ease-in-out; + + &.active { + color: ${theme.palette.text.primary}; + font-weight: 500; + } + + &:hover { + background-color: ${theme.palette.action.hover}; + } + + ${theme.breakpoints.up("md")} { + height: ${navHeight}px; + padding: 0 24px; + } + `, +} satisfies Record>; diff --git a/site/src/components/Dashboard/Navbar/UserDropdown/UserDropdown.tsx b/site/src/components/Dashboard/Navbar/UserDropdown/UserDropdown.tsx index a1c5cba6ad687..b7a025d2ff559 100644 --- a/site/src/components/Dashboard/Navbar/UserDropdown/UserDropdown.tsx +++ b/site/src/components/Dashboard/Navbar/UserDropdown/UserDropdown.tsx @@ -57,7 +57,7 @@ export const UserDropdown: FC> = ({ > { +const createWrapper = (): FC => { const queryClient = new QueryClient(); - return ({ children }: { children: ReactNode }) => ( + return ({ children }) => ( {children} ); }; diff --git a/site/src/components/DeploySettingsLayout/Option.tsx b/site/src/components/DeploySettingsLayout/Option.tsx index 978233accebde..6db042e7f0870 100644 --- a/site/src/components/DeploySettingsLayout/Option.tsx +++ b/site/src/components/DeploySettingsLayout/Option.tsx @@ -1,18 +1,14 @@ -import Box, { type BoxProps } from "@mui/material/Box"; import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined"; import { css, useTheme } from "@emotion/react"; -import type { PropsWithChildren, FC } from "react"; +import type { HTMLAttributes, PropsWithChildren, FC } from "react"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; import { DisabledBadge, EnabledBadge } from "../Badges/Badges"; -export const OptionName: FC = (props) => { - const { children } = props; - +export const OptionName: FC = ({ children }) => { return {children}; }; -export const OptionDescription: FC = (props) => { - const { children } = props; +export const OptionDescription: FC = ({ children }) => { const theme = useTheme(); return ( @@ -37,31 +33,20 @@ export const OptionValue: FC = (props) => { const { children: value } = props; const theme = useTheme(); - const optionStyles = css` - font-size: 14px; - font-family: ${MONOSPACE_FONT_FAMILY}; - overflow-wrap: anywhere; - user-select: all; - - & ul { - padding: 16px; - } - `; - if (typeof value === "boolean") { return value ? : ; } if (typeof value === "number") { - return {value}; + return {value}; } if (!value || value.length === 0) { - return Not set; + return Not set; } if (typeof value === "string") { - return {value}; + return {value}; } if (typeof value === "object" && !Array.isArray(value)) { @@ -73,31 +58,31 @@ export const OptionValue: FC = (props) => {
  • - {isEnabled && ( ({ width: 16, height: 16, - color: (theme) => theme.palette.success.light, + color: theme.palette.success.light, margin: "0 8px", - }} + })} /> )} {option} - +
  • ))} @@ -108,7 +93,7 @@ export const OptionValue: FC = (props) => { return (
      {value.map((item) => ( -
    • +
    • {item}
    • ))} @@ -116,63 +101,83 @@ export const OptionValue: FC = (props) => { ); } - return {JSON.stringify(value)}; + return {JSON.stringify(value)}; }; -interface OptionConfigProps extends BoxProps { +interface OptionConfigProps extends HTMLAttributes { source?: boolean; } -// OptionalConfig takes a source bool to indicate if the Option is the source of the configured value. -export const OptionConfig = (props: OptionConfigProps) => { - const { source, sx, ...attrs } = props; +// OptionConfig takes a source bool to indicate if the Option is the source of the configured value. +export const OptionConfig: FC = ({ + children, + source, + ...attrs +}) => { const theme = useTheme(); const borderColor = source ? undefined : theme.palette.divider; return ( - - source ? theme.palette.primary.dark : theme.palette.background.paper, + backgroundColor: source + ? theme.palette.primary.dark + : theme.palette.background.paper, display: "inline-flex", alignItems: "center", - borderRadius: 0.25, + borderRadius: 2, padding: "0 8px", border: `1px solid ${borderColor}`, - ...sx, }} - /> + > + {children} + ); }; -interface OptionConfigFlagProps extends BoxProps { +interface OptionConfigFlagProps extends HTMLAttributes { source?: boolean; } -export const OptionConfigFlag = (props: OptionConfigFlagProps) => { - const { children, source, sx, ...attrs } = props; +export const OptionConfigFlag: FC = ({ + children, + source, + ...attrs +}) => { + const theme = useTheme(); return ( - - source ? "rgba(0, 0, 0, 0.7)" : theme.palette.divider, + backgroundColor: source ? "rgba(0, 0, 0, 0.7)" : theme.palette.divider, lineHeight: 1, padding: "2px 4px", - borderRadius: 0.25, - ...sx, + borderRadius: 2, }} > {children} - + ); }; + +const styles = { + option: css` + font-size: 14px; + font-family: ${MONOSPACE_FONT_FAMILY}; + overflow-wrap: anywhere; + user-select: all; + + & ul { + padding: 16px; + } + `, +}; diff --git a/site/src/components/DeploySettingsLayout/OptionsTable.tsx b/site/src/components/DeploySettingsLayout/OptionsTable.tsx index c36f97b9b56c9..57ae0729a39d7 100644 --- a/site/src/components/DeploySettingsLayout/OptionsTable.tsx +++ b/site/src/components/DeploySettingsLayout/OptionsTable.tsx @@ -5,7 +5,6 @@ import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import { type FC } from "react"; -import Box from "@mui/material/Box"; import { css } from "@emotion/react"; import type { ClibaseOption } from "api/typesGenerated"; import { @@ -17,10 +16,12 @@ import { } from "components/DeploySettingsLayout/Option"; import { optionValue } from "./optionValue"; -const OptionsTable: FC<{ +interface OptionsTableProps { options: ClibaseOption[]; additionalValues?: string[]; -}> = ({ options, additionalValues }) => { +} + +const OptionsTable: FC = ({ options, additionalValues }) => { if (options.length === 0) { return

      No options to configure

      ; } @@ -60,12 +61,12 @@ const OptionsTable: FC<{ {option.name} {option.description} - {option.flag && ( @@ -92,7 +93,7 @@ const OptionsTable: FC<{ {option.yaml} )} - + diff --git a/site/src/components/EmptyState/EmptyState.tsx b/site/src/components/EmptyState/EmptyState.tsx index 228b7c06e9260..a36069db4ce4a 100644 --- a/site/src/components/EmptyState/EmptyState.tsx +++ b/site/src/components/EmptyState/EmptyState.tsx @@ -1,11 +1,10 @@ -import Box from "@mui/material/Box"; import type { FC, ReactNode } from "react"; export interface EmptyStateProps { /** Text Message to display, placed inside Typography component */ message: string; /** Longer optional description to display below the message */ - description?: string | React.ReactNode; + description?: string | ReactNode; cta?: ReactNode; className?: string; image?: ReactNode; @@ -15,17 +14,16 @@ export interface EmptyStateProps { * Component to place on screens or in lists that have no content. Optionally * provide a button that would allow the user to return from where they were, * or to add an item that they currently have none of. - * - * EmptyState's props extend the [Material UI Box component](https://material-ui.com/components/box/) - * that you can directly pass props through to to customize the shape and layout of it. */ -export const EmptyState: FC> = ( - props, -) => { - const { message, description, cta, image, ...boxProps } = props; - +export const EmptyState: FC = ({ + message, + description, + cta, + image, + ...attrs +}) => { return ( - > = ( padding: "80px 40px", position: "relative", }} - {...boxProps} + {...attrs} > -
      {message}
      +
      {message}
      {description && (

      ({ @@ -55,6 +53,6 @@ export const EmptyState: FC> = ( )} {cta &&

      {cta}
      } {image} -
      + ); }; diff --git a/site/src/components/Filter/UserFilter.tsx b/site/src/components/Filter/UserFilter.tsx index 61b7754e0f78a..14bab75b52828 100644 --- a/site/src/components/Filter/UserFilter.tsx +++ b/site/src/components/Filter/UserFilter.tsx @@ -1,3 +1,4 @@ +import { type FC } from "react"; import { useMe } from "hooks"; import { BaseOption } from "./options"; import { getUsers } from "api/api"; @@ -67,7 +68,11 @@ export const useUserFilterMenu = ({ export type UserFilterMenu = ReturnType; -export const UserMenu = (menu: UserFilterMenu) => { +interface UserMenuProps { + menu: UserFilterMenu; +} + +export const UserMenu: FC = ({ menu }) => { return ( { ); }; -const UserOptionItem = ({ - option, - isSelected, -}: { +interface UserOptionItemProps { option: UserOption; isSelected?: boolean; -}) => { +} + +const UserOptionItem: FC = ({ option, isSelected }) => { return ( } /> diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index e08f76a1db22d..2a4f9ba721f9b 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -1,31 +1,37 @@ -import { ReactNode, forwardRef, useEffect, useRef, useState } from "react"; -import Box from "@mui/material/Box"; import TextField from "@mui/material/TextField"; -import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown"; -import Button, { ButtonProps } from "@mui/material/Button"; -import Menu, { MenuProps } from "@mui/material/Menu"; +import Button, { type ButtonProps } from "@mui/material/Button"; +import Menu, { type MenuProps } from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import SearchOutlined from "@mui/icons-material/SearchOutlined"; import InputAdornment from "@mui/material/InputAdornment"; import IconButton from "@mui/material/IconButton"; import Tooltip from "@mui/material/Tooltip"; +import Skeleton, { type SkeletonProps } from "@mui/material/Skeleton"; +import MenuList from "@mui/material/MenuList"; +import Divider from "@mui/material/Divider"; +import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined"; +import CheckOutlined from "@mui/icons-material/CheckOutlined"; import CloseOutlined from "@mui/icons-material/CloseOutlined"; +import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown"; +import SearchOutlined from "@mui/icons-material/SearchOutlined"; +import { useTheme } from "@emotion/react"; +import { + type FC, + type ReactNode, + forwardRef, + useEffect, + useRef, + useState, +} from "react"; import { useSearchParams } from "react-router-dom"; -import Skeleton, { SkeletonProps } from "@mui/material/Skeleton"; -import CheckOutlined from "@mui/icons-material/CheckOutlined"; import { getValidationErrorMessage, hasError, isApiValidationError, } from "api/errors"; -import { useFilterMenu } from "./menu"; -import { BaseOption } from "./options"; -import MenuList from "@mui/material/MenuList"; import { Loader } from "components/Loader/Loader"; -import Divider from "@mui/material/Divider"; -import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined"; - import { useDebouncedFunction } from "hooks/debounce"; +import { useFilterMenu } from "./menu"; +import type { BaseOption } from "./options"; export type PresetFilter = { name: string; @@ -118,25 +124,29 @@ const stringifyFilter = (filterValue: FilterValues): string => { return result.trim(); }; -const BaseSkeleton = (props: SkeletonProps) => { +const BaseSkeleton: FC = ({ children, ...skeletonProps }) => { return ( theme.palette.background.paper, + {...skeletonProps} + css={(theme) => ({ + backgroundColor: theme.palette.background.paper, borderRadius: "6px", - ...props.sx, - }} - /> + })} + > + {children} + ); }; -export const SearchFieldSkeleton = () => ; -export const MenuSkeleton = () => ( - -); +export const SearchFieldSkeleton: FC = () => { + return ; +}; + +export const MenuSkeleton: FC = () => { + return ; +}; type FilterProps = { filter: ReturnType; @@ -150,7 +160,7 @@ type FilterProps = { presets: PresetFilter[]; }; -export const Filter = ({ +export const Filter: FC = ({ filter, isLoading, error, @@ -160,7 +170,8 @@ export const Filter = ({ learnMoreLabel2, learnMoreLink2, presets, -}: FilterProps) => { +}) => { + const theme = useTheme(); // Storing local copy of the filter query so that it can be updated more // aggressively without re-renders rippling out to the rest of the app every // single time. Exists for performance reasons - not really a good way to @@ -185,19 +196,23 @@ export const Filter = ({ const hasFilterQuery = filter.query !== ""; return ( - {isLoading ? ( skeleton ) : ( <> - +
      filter.update(query)} presets={presets} @@ -238,7 +253,7 @@ export const Filter = ({ zIndex: 2, }, "& input::placeholder": { - color: (theme) => theme.palette.text.secondary, + color: theme.palette.text.secondary, }, "& .MuiInputAdornment-root": { marginLeft: 0, @@ -250,9 +265,9 @@ export const Filter = ({ startAdornment: ( theme.palette.text.secondary, + color: theme.palette.text.secondary, }} /> @@ -266,43 +281,46 @@ export const Filter = ({ filter.update(""); }} > - + ), }} /> - +
      {options} )} -
      + ); }; -const PresetMenu = ({ - presets, - learnMoreLink, - learnMoreLabel2, - learnMoreLink2, - onSelect, -}: { +interface PresetMenuProps { presets: PresetFilter[]; learnMoreLink: string; learnMoreLabel2?: string; learnMoreLink2?: string; onSelect: (query: string) => void; +} + +const PresetMenu: FC = ({ + presets, + learnMoreLink, + learnMoreLabel2, + learnMoreLink2, + onSelect, }) => { const [isOpen, setIsOpen] = useState(false); const anchorRef = useRef(null); + const theme = useTheme(); return ( <> ); }); -function SearchMenu({ +interface SearchMenuProps + extends Pick { + options?: TOption[]; + renderOption: (option: TOption) => ReactNode; + query: string; + onQueryChange: (query: string) => void; +} + +function SearchMenu({ options, renderOption, query, onQueryChange, ...menuProps -}: Pick & { - options?: TOption[]; - renderOption: (option: TOption) => ReactNode; - query: string; - onQueryChange: (query: string) => void; -}) { +}: SearchMenuProps) { const menuListRef = useRef(null); const searchInputRef = useRef(null); + const theme = useTheme(); return ( ({ menuProps.onClose && menuProps.onClose(event, reason); onQueryChange(""); }} - sx={{ + css={{ "& .MuiPaper-root": { width: 320, paddingY: 0, @@ -565,33 +598,31 @@ function SearchMenu({ enter: 250, exit: 0, }} + onKeyDown={(e) => { + e.stopPropagation(); + if (e.key === "ArrowDown" && menuListRef.current) { + const firstItem = menuListRef.current.firstChild as HTMLElement; + firstItem.focus(); + } + }} > - `1px solid ${theme.palette.divider}`, - }} - onKeyDown={(e) => { - e.stopPropagation(); - if (e.key === "ArrowDown" && menuListRef.current) { - const firstItem = menuListRef.current.firstChild as HTMLElement; - firstItem.focus(); - } + borderBottom: `1px solid ${theme.palette.divider}`, }} > theme.palette.text.secondary, + color: theme.palette.text.secondary, }} /> - ({ onChange={(e) => { onQueryChange(e.target.value); }} - sx={{ + css={{ height: "100%", border: 0, background: "none", width: "100%", - marginLeft: 2, + marginLeft: 16, outline: 0, "&::placeholder": { - color: (theme) => theme.palette.text.secondary, + color: theme.palette.text.secondary, }, }} /> - + - +
    • { @@ -629,22 +660,23 @@ function SearchMenu({ options.length > 0 ? ( options.map(renderOption) ) : ( - theme.palette.text.secondary, + color: theme.palette.text.secondary, textAlign: "center", - py: 1, + paddingTop: 8, + paddingBottom: 8, }} > No results - + ) ) : ( )} - +
    • ); } diff --git a/site/src/components/GlobalSnackbar/GlobalSnackbar.tsx b/site/src/components/GlobalSnackbar/GlobalSnackbar.tsx index 9b97c9be021e5..a39e9ad880d5f 100644 --- a/site/src/components/GlobalSnackbar/GlobalSnackbar.tsx +++ b/site/src/components/GlobalSnackbar/GlobalSnackbar.tsx @@ -45,12 +45,12 @@ export const GlobalSnackbar: FC = () => { autoHideDuration={notification.msgType === MsgType.Error ? 22000 : 6000} anchorOrigin={{ vertical: "bottom", horizontal: "right" }} message={ -
      +
      {notification.msgType === MsgType.Error && ( )} -
      +
      {notification.msg} {notification.additionalMsgs && @@ -64,30 +64,13 @@ export const GlobalSnackbar: FC = () => { ); }; -const styles = { - list: { - paddingLeft: 0, - }, - messageWrapper: { - display: "flex", - }, - message: { - maxWidth: 670, - }, - messageTitle: { - fontSize: 14, - fontWeight: 600, - }, - messageSubtitle: { - marginTop: 12, - }, - errorIcon: (theme) => ({ - color: theme.palette.error.contrastText, - marginRight: 16, - }), -} satisfies Record>; +interface AdditionalMessageDisplayProps { + message: AdditionalMessage; +} -function AdditionalMessageDisplay({ message }: { message: AdditionalMessage }) { +const AdditionalMessageDisplay: FC = ({ + message, +}) => { if (isNotificationText(message)) { return {message}; } @@ -102,7 +85,7 @@ function AdditionalMessageDisplay({ message }: { message: AdditionalMessage }) { if (isNotificationList(message)) { return ( -
        +
          {message.map((item, idx) => (
        • {item} @@ -113,4 +96,18 @@ function AdditionalMessageDisplay({ message }: { message: AdditionalMessage }) { } return null; -} +}; + +const styles = { + messageTitle: { + fontSize: 14, + fontWeight: 600, + }, + messageSubtitle: { + marginTop: 12, + }, + errorIcon: (theme) => ({ + color: theme.palette.error.contrastText, + marginRight: 16, + }), +} satisfies Record>; diff --git a/site/src/components/LastSeen/LastSeen.tsx b/site/src/components/LastSeen/LastSeen.tsx index df9c05210e5ea..d7510c5f763e1 100644 --- a/site/src/components/LastSeen/LastSeen.tsx +++ b/site/src/components/LastSeen/LastSeen.tsx @@ -1,11 +1,12 @@ -import Box, { type BoxProps } from "@mui/material/Box"; import { useTheme } from "@emotion/react"; import dayjs from "dayjs"; +import { type FC, type HTMLAttributes } from "react"; -export const LastSeen = ({ - value, - ...boxProps -}: { value: string } & BoxProps) => { +interface LastSeenProps extends HTMLAttributes { + value: string; +} + +export const LastSeen: FC = ({ value, ...attrs }) => { const theme = useTheme(); const t = dayjs(value); const now = dayjs(); @@ -29,13 +30,8 @@ export const LastSeen = ({ } return ( - + {message} - + ); }; diff --git a/site/src/components/Loader/Loader.tsx b/site/src/components/Loader/Loader.tsx index 0a5f487f33615..eaa002a660c1d 100644 --- a/site/src/components/Loader/Loader.tsx +++ b/site/src/components/Loader/Loader.tsx @@ -1,22 +1,24 @@ -import Box, { BoxProps } from "@mui/material/Box"; import CircularProgress from "@mui/material/CircularProgress"; -import { FC } from "react"; +import { type FC, type HTMLAttributes } from "react"; -export const Loader: FC<{ size?: number } & BoxProps> = ({ - size = 26, - ...boxProps -}) => { +interface LoaderProps extends HTMLAttributes { + size?: number; +} + +export const Loader: FC = ({ size = 26, ...attrs }) => { return ( - - +
      ); }; diff --git a/site/src/components/OverflowY/OverflowY.tsx b/site/src/components/OverflowY/OverflowY.tsx index 1252287a94f97..fe055df8285b3 100644 --- a/site/src/components/OverflowY/OverflowY.tsx +++ b/site/src/components/OverflowY/OverflowY.tsx @@ -1,18 +1,21 @@ /** * @file Provides reusable vertical overflow behavior. */ -import { type ReactNode } from "react"; -import { type SystemStyleObject } from "@mui/system"; -import Box from "@mui/system/Box"; +import { type FC, type ReactNode } from "react"; -type Props = { - children: ReactNode; +type OverflowYProps = { + children?: ReactNode; + className?: string; height?: number; maxHeight?: number; - sx?: SystemStyleObject; }; -export function OverflowY({ children, height, maxHeight, sx }: Props) { +export const OverflowY: FC = ({ + children, + height, + maxHeight, + ...attrs +}) => { const computedHeight = height === undefined ? "100%" : `${height}px`; // Doing Math.max check to catch cases where height is accidentally larger @@ -23,17 +26,17 @@ export function OverflowY({ children, height, maxHeight, sx }: Props) { : `${Math.max(height ?? 0, maxHeight)}px`; return ( - {children} - +
      ); -} +}; diff --git a/site/src/components/Paywall/Paywall.tsx b/site/src/components/Paywall/Paywall.tsx index 2f9d9ba860b88..b24acfff07e39 100644 --- a/site/src/components/Paywall/Paywall.tsx +++ b/site/src/components/Paywall/Paywall.tsx @@ -1,21 +1,19 @@ -import Box from "@mui/material/Box"; import { type FC, type ReactNode } from "react"; import { type Interpolation, type Theme } from "@emotion/react"; import { EnterpriseBadge } from "components/Badges/Badges"; import { Stack } from "components/Stack/Stack"; export interface PaywallProps { + children?: ReactNode; message: string; - description?: string | React.ReactNode; + description?: string | ReactNode; cta?: ReactNode; } -export const Paywall: FC> = (props) => { - const { message, description, cta } = props; - +export const Paywall: FC = ({ message, description, cta }) => { return ( - -
      +
      +
      {message}
      @@ -24,7 +22,7 @@ export const Paywall: FC> = (props) => { {description &&

      {description}

      }
      {cta} - +
      ); }; @@ -41,9 +39,6 @@ const styles = { border: `1px solid ${theme.palette.divider}`, borderRadius: 8, }), - header: { - marginBottom: 24, - }, title: { fontWeight: 600, fontFamily: "inherit", @@ -58,10 +53,4 @@ const styles = { color: theme.palette.text.secondary, fontSize: 14, }), - enterpriseChip: (theme) => ({ - background: theme.palette.success.dark, - color: theme.palette.success.contrastText, - border: `1px solid ${theme.palette.success.light}`, - fontSize: 13, - }), } satisfies Record>; diff --git a/site/src/components/Popover/Popover.tsx b/site/src/components/Popover/Popover.tsx index bab14ff805598..489d3d85dcb8c 100644 --- a/site/src/components/Popover/Popover.tsx +++ b/site/src/components/Popover/Popover.tsx @@ -1,6 +1,7 @@ import { - ReactElement, - ReactNode, + type FC, + type ReactElement, + type ReactNode, cloneElement, createContext, useContext, @@ -38,13 +39,19 @@ const PopoverContext = createContext( undefined, ); -export const Popover = (props: { +interface PopoverProps { children: ReactNode | ((popover: PopoverContextValue) => ReactNode); // Allows inline usage mode?: TriggerMode; isDefaultOpen?: boolean; +} + +export const Popover: FC = ({ + children, + mode, + isDefaultOpen, }) => { const hookId = useId(); - const [isOpen, setIsOpen] = useState(props.isDefaultOpen ?? false); + const [isOpen, setIsOpen] = useState(isDefaultOpen ?? false); const triggerRef = useRef(null); const value: PopoverContextValue = { @@ -52,14 +59,12 @@ export const Popover = (props: { setIsOpen, triggerRef, id: `${hookId}-popover`, - mode: props.mode ?? "click", + mode: mode ?? "click", }; return ( - {typeof props.children === "function" - ? props.children(value) - : props.children} + {typeof children === "function" ? children(value) : children} ); }; @@ -102,14 +107,19 @@ export const PopoverTrigger = (props: { children: TriggerElement }) => { type Horizontal = "left" | "right"; -export const PopoverContent = ( - props: Omit & { - horizontal?: Horizontal; - }, -) => { +type PopoverContentProps = Omit< + MuiPopoverProps, + "open" | "onClose" | "anchorEl" +> & { + horizontal?: Horizontal; +}; + +export const PopoverContent: FC = ({ + horizontal = "left", + ...popoverProps +}) => { const popover = usePopover(); const [isReady, setIsReady] = useState(false); - const horizontal = props.horizontal ?? "left"; const hoverMode = popover.mode === "hover"; // This is a hack to make sure the popover is not rendered until the trigger @@ -143,7 +153,7 @@ export const PopoverContent = ( }} {...horizontalProps(horizontal)} {...modeProps(popover)} - {...props} + {...popoverProps} id={popover.id} open={popover.isOpen} onClose={() => popover.setIsOpen(false)} diff --git a/site/src/components/ProxyStatusLatency/ProxyStatusLatency.tsx b/site/src/components/ProxyStatusLatency/ProxyStatusLatency.tsx index 8886652ac2ad3..330aaa542eeb5 100644 --- a/site/src/components/ProxyStatusLatency/ProxyStatusLatency.tsx +++ b/site/src/components/ProxyStatusLatency/ProxyStatusLatency.tsx @@ -1,15 +1,19 @@ import { useTheme } from "@mui/material/styles"; import HelpOutline from "@mui/icons-material/HelpOutline"; -import Box from "@mui/material/Box"; import Tooltip from "@mui/material/Tooltip"; -import { FC } from "react"; +import { type FC } from "react"; import { getLatencyColor } from "utils/latency"; import CircularProgress from "@mui/material/CircularProgress"; -export const ProxyStatusLatency: FC<{ +interface ProxyStatusLatencyProps { latency?: number; isLoading?: boolean; -}> = ({ latency, isLoading }) => { +} + +export const ProxyStatusLatency: FC = ({ + latency, + isLoading, +}) => { const theme = useTheme(); const color = getLatencyColor(theme, latency); @@ -18,7 +22,7 @@ export const ProxyStatusLatency: FC<{ +
      {latency.toFixed(0)}ms - +
      ); }; diff --git a/site/src/components/Resources/AgentButton.tsx b/site/src/components/Resources/AgentButton.tsx index c0ab32c58f313..58f0e533b8095 100644 --- a/site/src/components/Resources/AgentButton.tsx +++ b/site/src/components/Resources/AgentButton.tsx @@ -1,28 +1,35 @@ -import Button, { ButtonProps } from "@mui/material/Button"; +import Button, { type ButtonProps } from "@mui/material/Button"; +import { useTheme } from "@emotion/react"; import { forwardRef } from "react"; // eslint-disable-next-line react/display-name -- Name is inferred from variable name export const AgentButton = forwardRef( (props, ref) => { + const { children, ...buttonProps } = props; + const theme = useTheme(); + return ( ); }, ); diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx index 4145843b03c96..13ef4dc5478e8 100644 --- a/site/src/components/Resources/AgentMetadata.tsx +++ b/site/src/components/Resources/AgentMetadata.tsx @@ -1,29 +1,34 @@ -import { watchAgentMetadata } from "api/api"; -import type { - WorkspaceAgent, - WorkspaceAgentMetadata, -} from "api/typesGenerated"; -import { Stack } from "components/Stack/Stack"; import dayjs from "dayjs"; +import Skeleton from "@mui/material/Skeleton"; +import Tooltip from "@mui/material/Tooltip"; +import { type Interpolation, type Theme } from "@emotion/react"; import { createContext, - FC, + type FC, + type HTMLAttributes, useContext, useEffect, + useLayoutEffect, useRef, useState, } from "react"; -import Skeleton from "@mui/material/Skeleton"; +import { watchAgentMetadata } from "api/api"; +import type { + WorkspaceAgent, + WorkspaceAgentMetadata, +} from "api/typesGenerated"; +import { Stack } from "components/Stack/Stack"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; -import Tooltip from "@mui/material/Tooltip"; -import Box, { BoxProps } from "@mui/material/Box"; -import { type Interpolation, type Theme } from "@emotion/react"; type ItemStatus = "stale" | "valid" | "loading"; export const WatchAgentMetadataContext = createContext(watchAgentMetadata); -const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { +interface MetadataItemProps { + item: WorkspaceAgentMetadata; +} + +const MetadataItem: FC = ({ item }) => { if (item.result === undefined) { throw new Error("Metadata item result is undefined"); } @@ -79,7 +84,7 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { return (
      {item.description.display_name}
      - {value} +
      {value}
      ); }; @@ -106,10 +111,15 @@ export const AgentMetadataView: FC = ({ metadata }) => { ); }; -export const AgentMetadata: FC<{ +interface AgentMetadataProps { agent: WorkspaceAgent; storybookMetadata?: WorkspaceAgentMetadata[]; -}> = ({ agent, storybookMetadata }) => { +} + +export const AgentMetadata: FC = ({ + agent, + storybookMetadata, +}) => { const [metadata, setMetadata] = useState< WorkspaceAgentMetadata[] | undefined >(undefined); @@ -182,10 +192,13 @@ export const AgentMetadataSkeleton: FC = () => { ); }; -const StaticWidth = (props: BoxProps) => { +const StaticWidth: FC> = ({ + children, + ...attrs +}) => { const ref = useRef(null); - useEffect(() => { + useLayoutEffect(() => { // Ignore this in storybook if (!ref.current || process.env.STORYBOOK === "true") { return; @@ -196,9 +209,13 @@ const StaticWidth = (props: BoxProps) => { const autoWidth = ref.current.getBoundingClientRect().width; ref.current.style.width = autoWidth > currentWidth ? `${autoWidth}px` : `${currentWidth}px`; - }, [props.children]); + }, [children]); - return ; + return ( +
      + {children} +
      + ); }; // These are more or less copied from diff --git a/site/src/components/Resources/PortForwardButton.stories.tsx b/site/src/components/Resources/PortForwardButton.stories.tsx index 910ab65909c01..1f4c710e711a1 100644 --- a/site/src/components/Resources/PortForwardButton.stories.tsx +++ b/site/src/components/Resources/PortForwardButton.stories.tsx @@ -1,4 +1,3 @@ -import Box from "@mui/material/Box"; import { PortForwardPopoverView } from "./PortForwardButton"; import type { Meta, StoryObj } from "@storybook/react"; import { @@ -11,16 +10,16 @@ const meta: Meta = { component: PortForwardPopoverView, decorators: [ (Story) => ( - ({ width: 304, - border: (theme) => `1px solid ${theme.palette.divider}`, - borderRadius: 1, - backgroundColor: (theme) => theme.palette.background.paper, - }} + border: `1px solid ${theme.palette.divider}`, + borderRadius: 8, + backgroundColor: theme.palette.background.paper, + })} > - +
      ), ], args: { diff --git a/site/src/components/Resources/PortForwardButton.tsx b/site/src/components/Resources/PortForwardButton.tsx index 6d1747f32a723..c4c2cbf22667a 100644 --- a/site/src/components/Resources/PortForwardButton.tsx +++ b/site/src/components/Resources/PortForwardButton.tsx @@ -155,13 +155,13 @@ export const PortForwardPopoverView: FC = ({ />
      - {/* 62px - navbar and 36px - the bottom bar */} - +
      - - +
      +
      ); -} +}; + +const styles = { + sectionLink: (theme) => ({ + border: "none", + fontSize: 14, + width: "100%", + display: "flex", + alignItems: "center", + gap: 12, + textAlign: "left", + height: 36, + padding: "0 24px", + cursor: "pointer", + background: "none", + color: theme.palette.text.secondary, + + "&:hover": { + background: theme.palette.action.hover, + color: theme.palette.text.primary, + }, + }), + + activeSectionLink: (theme) => ({ + background: theme.palette.action.hover, + pointerEvents: "none", + color: theme.palette.text.primary, + }), +} satisfies Record>; diff --git a/site/src/pages/IconsPage/IconsPage.tsx b/site/src/pages/IconsPage/IconsPage.tsx index 06199bdf9b11e..4122a828ba770 100644 --- a/site/src/pages/IconsPage/IconsPage.tsx +++ b/site/src/pages/IconsPage/IconsPage.tsx @@ -2,7 +2,6 @@ import TextField from "@mui/material/TextField"; import InputAdornment from "@mui/material/InputAdornment"; import Tooltip from "@mui/material/Tooltip"; import IconButton from "@mui/material/IconButton"; -import Box from "@mui/material/Box"; import Link from "@mui/material/Link"; import SearchIcon from "@mui/icons-material/SearchOutlined"; import ClearIcon from "@mui/icons-material/CloseOutlined"; @@ -80,7 +79,7 @@ export const IconsPage: FC = () => { { public GitHub repository. Just keep in mind that it should be relevant to many Coder users, and redistributable under a permissive license. -
      +

      } > @@ -126,7 +125,7 @@ export const IconsPage: FC = () => { startAdornment: ( { size="small" onClick={() => setSearchInputText("")} > - + diff --git a/site/src/pages/LoginPage/OAuthSignInForm.tsx b/site/src/pages/LoginPage/OAuthSignInForm.tsx index e8f289f122bcf..299040b768a4f 100644 --- a/site/src/pages/LoginPage/OAuthSignInForm.tsx +++ b/site/src/pages/LoginPage/OAuthSignInForm.tsx @@ -1,30 +1,29 @@ import Button from "@mui/material/Button"; import GitHubIcon from "@mui/icons-material/GitHub"; import KeyIcon from "@mui/icons-material/VpnKey"; -import Box from "@mui/material/Box"; -import { useId, type FC } from "react"; +import { type FC, useId } from "react"; import { Language } from "./SignInForm"; -import { type AuthMethods } from "api/typesGenerated"; +import type { AuthMethods } from "api/typesGenerated"; import { visuallyHidden } from "@mui/utils"; +const iconStyles = { + width: 16, + height: 16, +}; + type OAuthSignInFormProps = { isSigningIn: boolean; redirectTo: string; authMethods?: AuthMethods; }; -const iconStyles = { - width: 16, - height: 16, -}; - export const OAuthSignInForm: FC = ({ isSigningIn, redirectTo, authMethods, }) => { return ( - +
      {authMethods?.github.enabled && ( )} - +
      ); }; @@ -72,7 +71,7 @@ type OidcIconProps = { iconUrl: string; }; -function OidcIcon({ iconUrl }: OidcIconProps) { +const OidcIcon: FC = ({ iconUrl }) => { const hookId = useId(); const oidcId = `${hookId}-oidc`; @@ -87,4 +86,4 @@ function OidcIcon({ iconUrl }: OidcIconProps) { ); -} +}; diff --git a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx index f040033f2abeb..14593a3dc84fd 100644 --- a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx +++ b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx @@ -1,6 +1,5 @@ import CheckOutlined from "@mui/icons-material/CheckOutlined"; import FileCopyOutlined from "@mui/icons-material/FileCopyOutlined"; -import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import FormControlLabel from "@mui/material/FormControlLabel"; import Radio from "@mui/material/Radio"; @@ -101,8 +100,8 @@ export const TemplateEmbedPageView: FC<{ {!buttonValues || !templateParameters ? ( ) : ( - - +
      +
      )} - - + +
      ({ // 80px for padding, 36px is for the status bar. We want to use `vh` // so that it will be relative to the screen and not the parent layout. height: "calc(100vh - (80px + 36px))", top: 40, position: "sticky", - }} - p={8} - flex={1} - alignItems="center" - justifyContent="center" - borderRadius={1} - bgcolor="background.paper" - border={(theme) => `1px solid ${theme.palette.divider}`} + display: "flex", + padding: 64, + flex: 1, + alignItems: "center", + justifyContent: "center", + borderRadius: 8, + backgroundColor: theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, + })} > Open in Coder button - - - - +
      +
      +
      )} ); diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx index 797e1b4995f10..29127893d9a58 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx @@ -1,10 +1,9 @@ import CheckOutlined from "@mui/icons-material/CheckOutlined"; import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; -import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import { useState, useRef } from "react"; +import { type FC, useState, useRef } from "react"; export const insightsIntervals = { day: { @@ -17,13 +16,12 @@ export const insightsIntervals = { export type InsightsInterval = keyof typeof insightsIntervals; -export const IntervalMenu = ({ - value, - onChange, -}: { +interface IntervalMenuProps { value: InsightsInterval; onChange: (value: InsightsInterval) => void; -}) => { +} + +export const IntervalMenu: FC = ({ value, onChange }) => { const anchorRef = useRef(null); const [open, setOpen] = useState(false); @@ -72,11 +70,11 @@ export const IntervalMenu = ({ }} > {label} - +
      {value === interval && ( )} - +
      ); })} diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx index 73ad35db9d02e..6b4525566eb3b 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx @@ -1,10 +1,9 @@ import CheckOutlined from "@mui/icons-material/CheckOutlined"; import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; -import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import { useState, useRef } from "react"; +import { type FC, useState, useRef } from "react"; import { DateRangeValue } from "./DateRange"; import { differenceInWeeks } from "date-fns"; import { lastWeeks } from "./utils"; @@ -13,13 +12,12 @@ import { lastWeeks } from "./utils"; // 6 months. export const numberOfWeeksOptions = [4, 12, 24] as const; -export const WeekPicker = ({ - value, - onChange, -}: { +interface WeekPickerProps { value: DateRangeValue; onChange: (value: DateRangeValue) => void; -}) => { +} + +export const WeekPicker: FC = ({ value, onChange }) => { const anchorRef = useRef(null); const [open, setOpen] = useState(false); const numberOfWeeks = differenceInWeeks(value.endDate, value.startDate); @@ -71,11 +69,11 @@ export const WeekPicker = ({ }} > Last {option} weeks - +
      {numberOfWeeks === option && ( )} - +
      ); })} diff --git a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionsTable.tsx b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionsTable.tsx index cbeb3e208a897..91a724a5d81cd 100644 --- a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionsTable.tsx +++ b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionsTable.tsx @@ -1,4 +1,3 @@ -import Box from "@mui/material/Box"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; @@ -6,7 +5,7 @@ import TableContainer from "@mui/material/TableContainer"; import TableRow from "@mui/material/TableRow"; import { Timeline } from "components/Timeline/Timeline"; import { type FC } from "react"; -import * as TypesGen from "api/typesGenerated"; +import type * as TypesGen from "api/typesGenerated"; import { EmptyState } from "components/EmptyState/EmptyState"; import { TableLoader } from "components/TableLoader/TableLoader"; import { VersionRow } from "./VersionRow"; @@ -20,15 +19,17 @@ export const Language = { export interface VersionsTableProps { activeVersionId: string; + versions?: TypesGen.TemplateVersion[]; onPromoteClick?: (templateVersionId: string) => void; onArchiveClick?: (templateVersionId: string) => void; - - versions?: TypesGen.TemplateVersion[]; } -export const VersionsTable: FC = (props) => { - const { versions, onArchiveClick, onPromoteClick, activeVersionId } = props; - +export const VersionsTable: FC = ({ + activeVersionId, + versions, + onArchiveClick, + onPromoteClick, +}) => { const latestVersionId = versions?.reduce( (latestSoFar, against) => { if (against.job.status !== "succeeded") { @@ -73,9 +74,9 @@ export const VersionsTable: FC = (props) => { {versions && versions.length === 0 && ( - +
      - +
      )} diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx index 70a3001d73b5f..dac80188afa0f 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx @@ -1,8 +1,7 @@ import CircularProgress from "@mui/material/CircularProgress"; import TextField from "@mui/material/TextField"; import Autocomplete from "@mui/material/Autocomplete"; -import Box from "@mui/material/Box"; -import { type ChangeEvent, useState } from "react"; +import { type ChangeEvent, type FC, useState } from "react"; import { css } from "@emotion/react"; import type { Group, User } from "api/typesGenerated"; import { AvatarData } from "components/AvatarData/AvatarData"; @@ -21,13 +20,13 @@ export type UserOrGroupAutocompleteProps = { exclude: UserOrGroupAutocompleteValue[]; }; -export const UserOrGroupAutocomplete: React.FC< - UserOrGroupAutocompleteProps -> = ({ value, onChange, templateID, exclude }) => { - const [autoComplete, setAutoComplete] = useState<{ - value: string; - open: boolean; - }>({ +export const UserOrGroupAutocomplete: FC = ({ + value, + onChange, + templateID, + exclude, +}) => { + const [autoComplete, setAutoComplete] = useState({ value: "", open: false, }); @@ -90,7 +89,7 @@ export const UserOrGroupAutocomplete: React.FC< const isOptionGroup = isGroup(option); return ( - +
    • - +
    • ); }} options={options} diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText.tsx index fff5cf23c55fd..2ae948bcfcc9e 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText.tsx @@ -1,5 +1,6 @@ -import { Template } from "api/typesGenerated"; -import { TemplateAutostopRequirementDaysValue } from "utils/schedule"; +import { type FC } from "react"; +import type { Template } from "api/typesGenerated"; +import type { TemplateAutostopRequirementDaysValue } from "utils/schedule"; const autostopRequirementDescriptions = { off: "Workspaces are not required to stop periodically.", @@ -26,21 +27,24 @@ export const convertAutostopRequirementDaysValue = ( return "off"; }; -export const AutostopRequirementDaysHelperText = ({ - days = "off", -}: { +interface AutostopRequirementDaysHelperTextProps { days: TemplateAutostopRequirementDaysValue; -}) => { +} + +export const AutostopRequirementDaysHelperText: FC< + AutostopRequirementDaysHelperTextProps +> = ({ days = "off" }) => { return {autostopRequirementDescriptions[days]}; }; -export const AutostopRequirementWeeksHelperText = ({ - days, - weeks, -}: { +interface AutostopRequirementWeeksHelperTextProps { days: TemplateAutostopRequirementDaysValue; weeks: number; -}) => { +} + +export const AutostopRequirementWeeksHelperText: FC< + AutostopRequirementWeeksHelperTextProps +> = ({ days, weeks }) => { // Disabled if (days !== "saturday" && days !== "sunday") { return ( diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx index 2203e1a1658f7..4eeb687a0d8b1 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx @@ -71,9 +71,7 @@ export const ScheduleDialog: FC> = ({
      {` This change will result in ${inactiveWorkspacesToGoDormant} workspaces being immediately transitioned to the dormant state and ${inactiveWorkspacesToGoDormantInWeek} over the next seven days. To prevent this, do you want to reset the inactivity period for all template workspaces?`}
      > = ({ {showDeletionWarning && ( <> -

      {"Dormancy Auto-Deletion"}

      +

      Dormancy Auto-Deletion

      -
      {`This change will result in ${dormantWorkspacesToBeDeleted} workspaces being immediately deleted and ${dormantWorkspacesToBeDeletedInWeek} over the next 7 days. To prevent this, do you want to reset the dormancy period for all template workspaces?`}
      +

      + This change will result in {dormantWorkspacesToBeDeleted}{" "} + workspaces being immediately deleted and{" "} + {dormantWorkspacesToBeDeletedInWeek} over the next 7 days. To + prevent this, do you want to reset the dormancy period for all + template workspaces? +

      = ({
      - `1px solid ${theme.palette.divider}`, - borderLeft: (theme) => - `2px solid ${theme.palette.error.main}`, + borderBottom: `1px solid ${theme.palette.divider}`, + borderLeft: `2px solid ${theme.palette.error.main}`, }} > Error during the build @@ -645,17 +644,19 @@ export const TemplateVersionEditor: FC = ({ ); }; -const TopbarButton = (props: ButtonProps) => { +const TopbarButton: FC = ({ children, ...buttonProps }) => { return ( ); }; diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index 085def04a896c..6a9d4849c352f 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -42,7 +42,6 @@ import { Avatar } from "components/Avatar/Avatar"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { docs } from "utils/docs"; import Skeleton from "@mui/material/Skeleton"; -import { Box } from "@mui/system"; import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"; import { Pill } from "components/Pill/Pill"; @@ -62,7 +61,7 @@ export const Language = { templateTooltipLink: "Manage templates", }; -const TemplateHelpTooltip: React.FC = () => { +const TemplateHelpTooltip: FC = () => { return ( {Language.templateTooltipTitle} @@ -76,7 +75,11 @@ const TemplateHelpTooltip: React.FC = () => { ); }; -const TemplateRow: FC<{ template: Template }> = ({ template }) => { +interface TemplateRowProps { + template: Template; +} + +const TemplateRow: FC = ({ template }) => { const templatePageLink = `/templates/${template.name}`; const hasIcon = template.icon && template.icon !== ""; const navigate = useNavigate(); @@ -226,14 +229,14 @@ export const TemplatesPageView: FC = ({ ); }; -const TableLoader = () => { +const TableLoader: FC = () => { return ( - +
      - +
      diff --git a/site/src/pages/TerminalPage/TerminalAlerts.tsx b/site/src/pages/TerminalPage/TerminalAlerts.tsx index 05706a5bc440a..ba4f8a8470b27 100644 --- a/site/src/pages/TerminalPage/TerminalAlerts.tsx +++ b/site/src/pages/TerminalPage/TerminalAlerts.tsx @@ -1,10 +1,10 @@ import Button from "@mui/material/Button"; import Link from "@mui/material/Link"; import { Alert, AlertProps } from "components/Alert/Alert"; -import { useState } from "react"; +import { type FC, useState } from "react"; import { docs } from "utils/docs"; -export const ErrorScriptAlert = () => { +export const ErrorScriptAlert: FC = () => { return ( { ); }; -export const LoadingScriptsAlert = () => { +export const LoadingScriptsAlert: FC = () => { return ( { ); }; -export const LoadedScriptsAlert = () => { +export const LoadedScriptsAlert: FC = () => { return ( { ); }; -const TerminalAlert = (props: AlertProps) => { +const TerminalAlert: FC = (props) => { return ( ({ borderRadius: 0, borderWidth: 0, borderBottomWidth: 1, - borderBottomColor: (theme) => theme.palette.divider, - backgroundColor: (theme) => theme.palette.background.paper, - borderLeft: (theme) => - `3px solid ${theme.palette[props.severity!].light}`, + borderBottomColor: theme.palette.divider, + backgroundColor: theme.palette.background.paper, + borderLeft: `3px solid ${theme.palette[props.severity!].light}`, marginBottom: 1, - }} + })} /> ); }; -export const DisconnectedAlert = (props: AlertProps) => { +export const DisconnectedAlert: FC = (props) => { return ( { ); }; -const RefreshSessionButton = () => { +const RefreshSessionButton: FC = () => { const [isRefreshing, setIsRefreshing] = useState(false); return ( diff --git a/site/src/pages/TerminalPage/TerminalPage.tsx b/site/src/pages/TerminalPage/TerminalPage.tsx index c27286864e510..156e41daeab6e 100644 --- a/site/src/pages/TerminalPage/TerminalPage.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.tsx @@ -14,7 +14,6 @@ import "xterm/css/xterm.css"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; import { pageTitle } from "utils/page"; import { useProxy } from "contexts/ProxyContext"; -import Box from "@mui/material/Box"; import { useDashboard } from "components/Dashboard/DashboardProvider"; import type { Region } from "api/typesGenerated"; import { getLatencyColor } from "utils/latency"; @@ -309,7 +308,7 @@ const TerminalPage: FC = () => { : ""} - +
      {lifecycleState === "start_error" && } {lifecycleState === "starting" && } {lifecycleState === "ready" && @@ -321,31 +320,35 @@ const TerminalPage: FC = () => { latency && ( )} - +
      ); }; -const BottomBar = ({ proxy, latency }: { proxy: Region; latency?: number }) => { +interface BottomBarProps { + proxy: Region; + latency?: number; +} + +const BottomBar: FC = ({ proxy, latency }) => { const theme = useTheme(); const color = getLatencyColor(theme, latency); return ( - theme.palette.background.paper, + background: theme.palette.background.paper, display: "flex", alignItems: "center", justifyContent: "flex-end", fontSize: 12, - borderTop: (theme) => `1px solid ${theme.palette.divider}`, + borderTop: `1px solid ${theme.palette.divider}`, }} > - { padding: 8, }} > - - + { horizontal: "right", }} > - theme.palette.text.secondary, + color: theme.palette.text.secondary, fontWeight: 500, }} > Selected proxy - - +
      - - - +
      + - +
      {proxy.display_name} -
      +
      -
      +
      -
      + ); }; diff --git a/site/src/pages/TerminalPage/TerminalPageAlert.tsx b/site/src/pages/TerminalPage/TerminalPageAlert.tsx deleted file mode 100644 index ab7f69d21fd7a..0000000000000 --- a/site/src/pages/TerminalPage/TerminalPageAlert.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { AlertColor } from "@mui/material/Alert/Alert"; -import Button from "@mui/material/Button"; -import Link from "@mui/material/Link"; -import { Alert } from "components/Alert/Alert"; -import { ReactNode } from "react"; -import { docs } from "utils/docs"; - -export type TerminalPageAlertType = "error" | "starting" | "success"; - -type MapAlertTypeToComponent = { - [key in TerminalPageAlertType]: { - severity: AlertColor; - children: ReactNode | undefined; - }; -}; - -const mapAlertTypeToText: MapAlertTypeToComponent = { - error: { - severity: "warning", - children: ( - <> - The workspace{" "} - - startup script has exited with an error - - , we recommend reloading this session and{" "} - - debugging the startup script - {" "} - because{" "} - - your workspace may be incomplete. - {" "} - - ), - }, - starting: { - severity: "info", - children: ( - <> - Startup script is still running. You can continue using this terminal, - but{" "} - - {" "} - your workspace may be incomplete. - - - ), - }, - success: { - severity: "success", - children: ( - <> - Startup script has completed successfully. The workspace is ready but - this{" "} - - session was started before the startup script finished. - {" "} - To ensure your shell environment is up-to-date, we recommend reloading - this session. - - ), - }, -}; - -export default ({ - alertType, - onDismiss, -}: { - alertType: TerminalPageAlertType; - onDismiss: () => void; -}) => { - const severity = mapAlertTypeToText[alertType].severity; - return ( - theme.palette.divider, - backgroundColor: (theme) => theme.palette.background.paper, - borderLeft: (theme) => `3px solid ${theme.palette[severity].light}`, - marginBottom: 1, - }} - onDismiss={onDismiss} - dismissible - actions={[ - , - ]} - > - {mapAlertTypeToText[alertType].children} - - ); -}; diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index e7b46df07d86f..a9d785917fe23 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -1,16 +1,15 @@ +import { Stack } from "@mui/system"; import { type FC } from "react"; +import { useQuery } from "react-query"; +import { useOrganizationId } from "hooks"; import { useMe } from "hooks/useMe"; import { usePermissions } from "hooks/usePermissions"; -import { useQuery } from "react-query"; import { groupsForUser } from "api/queries/groups"; -import { useOrganizationId } from "hooks"; import { useAuth } from "components/AuthProvider/AuthProvider"; +import { Section } from "components/SettingsLayout/Section"; import { useDashboard } from "components/Dashboard/DashboardProvider"; - -import { Stack } from "@mui/system"; import { AccountUserGroups } from "./AccountUserGroups"; import { AccountForm } from "./AccountForm"; -import { Section } from "components/SettingsLayout/Section"; export const AccountPage: FC = () => { const me = useMe(); @@ -38,7 +37,6 @@ export const AccountPage: FC = () => { /> - {/* Has
      embedded inside because its description is dynamic */} {hasGroupsFeature && ( = ({ groups, error, loading, -}: AccountGroupsProps) { +}) => { const theme = useTheme(); return ( @@ -71,4 +71,4 @@ export function AccountUserGroups({
      ); -} +}; diff --git a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPageView.tsx b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPageView.tsx index a371d487a453b..c3ac6a1c47e4d 100644 --- a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPageView.tsx +++ b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPageView.tsx @@ -1,4 +1,3 @@ -import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import CircularProgress from "@mui/material/CircularProgress"; import { type FC, type PropsWithChildren } from "react"; @@ -25,9 +24,9 @@ export const SSHKeysPageView: FC> = ({ if (isLoading) { return ( - +
      - +
      ); } diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx index 5d06b74f13d43..124b7350f8071 100644 --- a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx +++ b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx @@ -57,16 +57,18 @@ export const SecurityPage: FC = () => { ); }; -export const SecurityPageView = ({ - security, - oidc, -}: { +interface SecurityPageViewProps { security: { form: ComponentProps; }; oidc?: { section: ComponentProps; }; +} + +export const SecurityPageView: FC = ({ + security, + oidc, }) => { return ( diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx index 3aff1d7881c58..87f8f61ba3621 100644 --- a/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx +++ b/site/src/pages/UserSettingsPage/SecurityPage/SingleSignOnSection.tsx @@ -1,7 +1,7 @@ -import { useState } from "react"; +import { useTheme } from "@emotion/react"; +import { type FC, useState } from "react"; import { Section } from "components/SettingsLayout/Section"; import TextField from "@mui/material/TextField"; -import Box from "@mui/material/Box"; import GitHubIcon from "@mui/icons-material/GitHub"; import KeyIcon from "@mui/icons-material/VpnKey"; import Button from "@mui/material/Button"; @@ -99,7 +99,7 @@ export const useSingleSignOnSection = () => { }; }; -function SSOEmptyState() { +const SSOEmptyState: FC = () => { return ( ({ @@ -117,14 +117,14 @@ function SSOEmptyState() { } /> ); -} +}; type SingleSignOnSectionProps = ReturnType & { authMethods: AuthMethods; userLoginType: UserLoginType; }; -export const SingleSignOnSection = ({ +export const SingleSignOnSection: FC = ({ authMethods, userLoginType, openConfirmation, @@ -133,11 +133,12 @@ export const SingleSignOnSection = ({ isUpdating, isConfirming, error, -}: SingleSignOnSectionProps) => { +}) => { + const theme = useTheme(); + const authList = Object.values( authMethods, ) as (typeof authMethods)[keyof typeof authMethods][]; - const noSsoEnabled = !authList.some((method) => method.enabled); return ( @@ -147,7 +148,7 @@ export const SingleSignOnSection = ({ title="Single Sign On" description="Authenticate in Coder using one-click" > - +
      {userLoginType.login_type === "password" ? ( <> {authMethods.github.enabled && ( @@ -155,7 +156,7 @@ export const SingleSignOnSection = ({ size="large" fullWidth disabled={isUpdating} - startIcon={} + startIcon={} onClick={() => openConfirmation("github")} > GitHub @@ -177,21 +178,21 @@ export const SingleSignOnSection = ({ {noSsoEnabled && } ) : ( - theme.palette.background.paper, - borderRadius: 1, - border: (theme) => `1px solid ${theme.palette.divider}`, - padding: 2, +
      theme.palette.success.light, + css={{ + color: theme.palette.success.light, fontSize: 16, }} /> @@ -203,16 +204,16 @@ export const SingleSignOnSection = ({ : getOIDCLabel(authMethods.oidc)} - +
      {userLoginType.login_type === "github" ? ( - + ) : ( )} - - +
      +
      )} -
      +
      { +interface OIDCIconProps { + oidcAuth: OIDCAuthMethod; +} + +const OIDCIcon: FC = ({ oidcAuth }) => { if (!oidcAuth.iconUrl) { - return ; + return ; } return ( - ); }; @@ -245,18 +249,20 @@ const getOIDCLabel = (oidcAuth: OIDCAuthMethod) => { return oidcAuth.signInText || "OpenID Connect"; }; -const ConfirmLoginTypeChangeModal = ({ - open, - loading, - error, - onClose, - onConfirm, -}: { +interface ConfirmLoginTypeChangeModalProps { open: boolean; loading: boolean; error: unknown; onClose: () => void; onConfirm: (password: string) => void; +} + +const ConfirmLoginTypeChangeModal: FC = ({ + open, + loading, + error, + onClose, + onConfirm, }) => { const [password, setPassword] = useState(""); diff --git a/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx b/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx index 73a0560145f89..f370dc846ea1f 100644 --- a/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx +++ b/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx @@ -1,4 +1,3 @@ -import { useTheme } from "@mui/styles"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; @@ -11,9 +10,10 @@ import { TableEmpty } from "components/TableEmpty/TableEmpty"; import { TableLoader } from "components/TableLoader/TableLoader"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import dayjs from "dayjs"; -import { FC } from "react"; +import { useTheme } from "@emotion/react"; +import { type FC, type ReactNode } from "react"; import IconButton from "@mui/material/IconButton/IconButton"; -import { APIKeyWithOwner } from "api/typesGenerated"; +import type { APIKeyWithOwner } from "api/typesGenerated"; import relativeTime from "dayjs/plugin/relativeTime"; import { ErrorAlert } from "components/Alert/ErrorAlert"; @@ -32,11 +32,10 @@ export interface TokensPageViewProps { hasLoaded: boolean; onDelete: (token: APIKeyWithOwner) => void; deleteTokenError?: unknown; + children?: ReactNode; } -export const TokensPageView: FC< - React.PropsWithChildren -> = ({ +export const TokensPageView: FC = ({ tokens, getTokensError, isLoading, diff --git a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx index 25cce2e9f12fd..8aad4ef234410 100644 --- a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx +++ b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx @@ -3,6 +3,7 @@ import { AvatarData } from "components/AvatarData/AvatarData"; import { Avatar } from "components/Avatar/Avatar"; import TableCell from "@mui/material/TableCell"; import TableRow from "@mui/material/TableRow"; +import { useTheme } from "@emotion/react"; import type { FC, ReactNode } from "react"; import { HealthyBadge, @@ -12,12 +13,15 @@ import { } from "components/Badges/Badges"; import type { ProxyLatencyReport } from "contexts/useProxyLatency"; import { getLatencyColor } from "utils/latency"; -import Box from "@mui/material/Box"; -export const ProxyRow: FC<{ +interface ProxyRowProps { latency?: ProxyLatencyReport; proxy: Region; -}> = ({ proxy, latency }) => { +} + +export const ProxyRow: FC = ({ proxy, latency }) => { + const theme = useTheme(); + // If we have a more specific proxy status, use that. // All users can see healthy/unhealthy, some can see more. let statusBadge = ; @@ -56,16 +60,15 @@ export const ProxyRow: FC<{ />
      - {proxy.path_app_url} - {statusBadge} + {proxy.path_app_url} + {statusBadge} - latency - ? getLatencyColor(theme, latency.latencyMS) - : theme.palette.text.secondary, + color: latency + ? getLatencyColor(theme, latency.latencyMS) + : theme.palette.text.secondary, }} > {latency ? `${latency.latencyMS.toFixed(0)} ms` : "Not available"} @@ -75,7 +78,7 @@ export const ProxyRow: FC<{ @@ -85,30 +88,22 @@ export const ProxyRow: FC<{ ); }; -const ProxyMessagesRow: FC<{ +interface ProxyMessagesRowProps { proxy: WorkspaceProxy; -}> = ({ proxy }) => { +} + +const ProxyMessagesRow: FC = ({ proxy }) => { + const theme = useTheme(); + return ( <> theme.palette.error.light }} - > - Errors -
      - } + title={Errors} messages={proxy.status?.report?.errors} /> theme.palette.warning.light }} - > - Warnings - + Warnings } messages={proxy.status?.report?.warnings} /> @@ -116,53 +111,58 @@ const ProxyMessagesRow: FC<{ ); }; -const ProxyMessagesList: FC<{ +interface ProxyMessagesListProps { title: ReactNode; messages?: string[]; -}> = ({ title, messages }) => { +} + +const ProxyMessagesList: FC = ({ title, messages }) => { + const theme = useTheme(); + if (!messages) { return <>; } return ( - `1px solid ${theme.palette.divider}`, - backgroundColor: (theme) => theme.palette.background.default, - p: "16px 24px", +
      - {title} - +
      {messages.map((error, index) => ( - {error} - + ))} -
      + ); }; -// DetailedProxyStatus allows a more precise status to be displayed. -const DetailedProxyStatus: FC<{ +interface DetailedProxyStatusProps { proxy: WorkspaceProxy; -}> = ({ proxy }) => { +} + +// DetailedProxyStatus allows a more precise status to be displayed. +const DetailedProxyStatus: FC = ({ proxy }) => { if (!proxy.status) { // If the status is null/undefined/not provided, just go with the boolean "healthy" value. return ; @@ -187,10 +187,12 @@ const DetailedProxyStatus: FC<{ } }; -// ProxyStatus will only show "healthy" or "not healthy" status. -const ProxyStatus: FC<{ +interface ProxyStatusProps { proxy: Region; -}> = ({ proxy }) => { +} + +// ProxyStatus will only show "healthy" or "not healthy" status. +const ProxyStatus: FC = ({ proxy }) => { let icon = ; if (proxy.healthy) { icon = ; diff --git a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx index 21037635de1a5..0832ef8dcffd0 100644 --- a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx +++ b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyView.tsx @@ -47,7 +47,7 @@ export const WorkspaceProxyView: FC< Proxy URL Status - + Latency diff --git a/site/src/pages/UsersPage/UsersFilter.tsx b/site/src/pages/UsersPage/UsersFilter.tsx index 366900690b804..e01191346d1d1 100644 --- a/site/src/pages/UsersPage/UsersFilter.tsx +++ b/site/src/pages/UsersPage/UsersFilter.tsx @@ -1,6 +1,5 @@ -import { FC } from "react"; -import Box from "@mui/material/Box"; -import { Palette, PaletteColor } from "@mui/material/styles"; +import { useTheme } from "@emotion/react"; +import { type FC } from "react"; import { Filter, FilterMenu, @@ -9,8 +8,12 @@ import { SearchFieldSkeleton, useFilter, } from "components/Filter/filter"; -import { BaseOption } from "components/Filter/options"; -import { UseFilterMenuOptions, useFilterMenu } from "components/Filter/menu"; +import type { BaseOption } from "components/Filter/options"; +import { + type UseFilterMenuOptions, + useFilterMenu, +} from "components/Filter/menu"; +import type { ThemeRole } from "theme/experimental"; import { docs } from "utils/docs"; const userFilterQuery = { @@ -19,7 +22,7 @@ const userFilterQuery = { }; type StatusOption = BaseOption & { - color: string; + color: ThemeRole; }; export const useStatusFilterMenu = ({ @@ -28,7 +31,7 @@ export const useStatusFilterMenu = ({ }: Pick, "value" | "onChange">) => { const statusOptions: StatusOption[] = [ { value: "active", label: "Active", color: "success" }, - { value: "dormant", label: "Dormant", color: "secondary" }, + { value: "dormant", label: "Dormant", color: "notice" }, { value: "suspended", label: "Suspended", color: "warning" }, ]; return useFilterMenu({ @@ -48,17 +51,15 @@ const PRESET_FILTERS = [ { query: userFilterQuery.all, name: "All users" }, ]; -export const UsersFilter = ({ - filter, - error, - menus, -}: { +interface UsersFilterProps { filter: ReturnType; error?: unknown; menus: { status: StatusFilterMenu; }; -}) => { +} + +export const UsersFilter: FC = ({ filter, error, menus }) => { return ( { ); }; -const StatusOptionItem = ({ - option, - isSelected, -}: { +interface StatusOptionItemProps { option: StatusOption; isSelected?: boolean; +} + +const StatusOptionItem: FC = ({ + option, + isSelected, }) => { return ( = ({ option }) => { +interface StatusIndicatorProps { + option: StatusOption; +} + +const StatusIndicator: FC = ({ option }) => { + const theme = useTheme(); + return ( - - (theme.palette[option.color as keyof Palette] as PaletteColor).light, +
      ); diff --git a/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx b/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx index e3deb15f4808a..695f3e344005f 100644 --- a/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx +++ b/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx @@ -51,7 +51,7 @@ export function UserGroupsCell({ userGroups }: GroupsCellProps) { css={{ columnGap: 8, alignItems: "center" }} > 0 ? 0.8 : 0.5, diff --git a/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx b/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx index 17cdf3226e55d..af7e0ea3cc4f2 100644 --- a/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx +++ b/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx @@ -1,12 +1,18 @@ -import Box from "@mui/material/Box"; import TableCell from "@mui/material/TableCell"; import TableRow from "@mui/material/TableRow"; import Skeleton from "@mui/material/Skeleton"; +import Divider from "@mui/material/Divider"; +import HideSourceOutlined from "@mui/icons-material/HideSourceOutlined"; +import KeyOutlined from "@mui/icons-material/KeyOutlined"; +import GitHub from "@mui/icons-material/GitHub"; +import PasswordOutlined from "@mui/icons-material/PasswordOutlined"; +import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; import { type Interpolation, type Theme } from "@emotion/react"; import { type FC } from "react"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import type * as TypesGen from "api/typesGenerated"; +import { type GroupsByUserId } from "api/queries/groups"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; import { AvatarData } from "components/AvatarData/AvatarData"; import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"; @@ -16,15 +22,7 @@ import { TableRowSkeleton, } from "components/TableLoader/TableLoader"; import { EnterpriseBadge } from "components/Badges/Badges"; -import HideSourceOutlined from "@mui/icons-material/HideSourceOutlined"; -import KeyOutlined from "@mui/icons-material/KeyOutlined"; -import GitHub from "@mui/icons-material/GitHub"; -import PasswordOutlined from "@mui/icons-material/PasswordOutlined"; -import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; import { LastSeen } from "components/LastSeen/LastSeen"; -import { UserRoleCell } from "./UserRoleCell"; -import { type GroupsByUserId } from "api/queries/groups"; -import { UserGroupsCell } from "./UserGroupsCell"; import { MoreMenu, MoreMenuTrigger, @@ -32,7 +30,8 @@ import { MoreMenuItem, ThreeDotsButton, } from "components/MoreMenu/MoreMenu"; -import Divider from "@mui/material/Divider"; +import { UserRoleCell } from "./UserRoleCell"; +import { UserGroupsCell } from "./UserGroupsCell"; dayjs.extend(relativeTime); @@ -91,9 +90,9 @@ export const UsersTableBody: FC< - +
      - +
      @@ -126,9 +125,9 @@ export const UsersTableBody: FC< - +
      - +
      @@ -136,9 +135,9 @@ export const UsersTableBody: FC< - +
      - +
      @@ -177,8 +176,8 @@ export const UsersTableBody: FC< user.status === "suspended" && styles.suspended, ]} > - {user.status} - +
      {user.status}
      +
      {canEditUsers && ( @@ -237,57 +236,60 @@ export const UsersTableBody: FC< ); }; -const LoginType = ({ - authMethods, - value, -}: { +interface LoginTypeProps { authMethods: TypesGen.AuthMethods; value: TypesGen.LoginType; -}) => { +} + +const LoginType: FC = ({ authMethods, value }) => { let displayName: string = value; let icon = <>; - const iconStyles = { width: 14, height: 14 }; if (value === "password") { displayName = "Password"; - icon = ; + icon = ; } else if (value === "none") { displayName = "None"; - icon = ; + icon = ; } else if (value === "github") { displayName = "GitHub"; - icon = ; + icon = ; } else if (value === "token") { displayName = "Token"; - icon = ; + icon = ; } else if (value === "oidc") { displayName = authMethods.oidc.signInText === "" ? "OIDC" : authMethods.oidc.signInText; icon = authMethods.oidc.iconUrl === "" ? ( - + ) : ( - ); } return ( - +
      {icon} {displayName} - +
      ); }; const styles = { + icon: { + width: 14, + height: 14, + }, + status: { textTransform: "capitalize", }, + suspended: (theme) => ({ color: theme.palette.text.secondary, }), diff --git a/site/src/pages/WorkspacePage/BuildsTable.tsx b/site/src/pages/WorkspacePage/BuildsTable.tsx index 2cf4dd5524d34..30637967911f8 100644 --- a/site/src/pages/WorkspacePage/BuildsTable.tsx +++ b/site/src/pages/WorkspacePage/BuildsTable.tsx @@ -1,31 +1,31 @@ -import Box from "@mui/material/Box"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableRow from "@mui/material/TableRow"; -import { Timeline } from "components/Timeline/Timeline"; -import { FC } from "react"; -import * as TypesGen from "api/typesGenerated"; +import LoadingButton from "@mui/lab/LoadingButton"; +import ArrowDownwardOutlined from "@mui/icons-material/ArrowDownwardOutlined"; +import { type FC, type ReactNode } from "react"; +import type * as TypesGen from "api/typesGenerated"; import { EmptyState } from "components/EmptyState/EmptyState"; import { TableLoader } from "components/TableLoader/TableLoader"; -import { BuildRow } from "./BuildRow"; +import { Timeline } from "components/Timeline/Timeline"; import { Stack } from "components/Stack/Stack"; -import LoadingButton from "@mui/lab/LoadingButton"; -import ArrowDownwardOutlined from "@mui/icons-material/ArrowDownwardOutlined"; +import { BuildRow } from "./BuildRow"; export const Language = { emptyMessage: "No builds found", }; export interface BuildsTableProps { + children?: ReactNode; builds: TypesGen.WorkspaceBuild[] | undefined; onLoadMoreBuilds: () => void; isLoadingMoreBuilds: boolean; hasMoreBuilds: boolean; } -export const BuildsTable: FC> = ({ +export const BuildsTable: FC = ({ builds, onLoadMoreBuilds, isLoadingMoreBuilds, @@ -49,9 +49,9 @@ export const BuildsTable: FC> = ({ {builds && builds.length === 0 && ( - +
      - +
      )} diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx index fcda04c5c070a..f7756a2c8cb52 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx @@ -1,9 +1,10 @@ import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; -import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; +import { useTheme } from "@emotion/react"; +import { type FC } from "react"; import { useQuery } from "react-query"; import { getWorkspaceParameters } from "api/api"; -import { +import type { TemplateVersionParameter, Workspace, WorkspaceBuildParameter, @@ -28,14 +29,16 @@ import { usePopover, } from "components/Popover/Popover"; -export const BuildParametersPopover = ({ - workspace, - disabled, - onSubmit, -}: { +interface BuildParametersPopoverProps { workspace: Workspace; disabled?: boolean; onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void; +} + +export const BuildParametersPopover: FC = ({ + workspace, + disabled, + onSubmit, }) => { const { data: parameters } = useQuery({ queryKey: ["workspace", workspace.id, "parameters"], @@ -52,9 +55,9 @@ export const BuildParametersPopover = ({ data-testid="build-parameters-button" disabled={disabled} color="neutral" - sx={{ px: 0 }} + css={{ paddingLeft: 0, paddingRight: 0 }} > - + void; +interface BuildParametersPopoverContentProps { ephemeralParameters?: TemplateVersionParameter[]; buildParameters?: WorkspaceBuildParameter[]; + onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void; +} + +const BuildParametersPopoverContent: FC = ({ + ephemeralParameters, + buildParameters, + onSubmit, }) => { + const theme = useTheme(); const popover = usePopover(); return ( @@ -87,19 +93,19 @@ const BuildParametersPopoverContent = ({ {buildParameters && ephemeralParameters ? ( ephemeralParameters.length > 0 ? ( <> - theme.palette.text.secondary, - p: 2.5, - borderBottom: (theme) => `1px solid ${theme.palette.divider}`, +
      Build Options These parameters only apply for a single workspace start. - - +
      +
      { onSubmit(buildParameters); @@ -108,14 +114,14 @@ const BuildParametersPopoverContent = ({ ephemeralParameters={ephemeralParameters} buildParameters={buildParameters} /> - +
      ) : ( - theme.palette.text.secondary, - p: 2.5, - borderBottom: (theme) => `1px solid ${theme.palette.divider}`, +
      Build Options @@ -129,7 +135,7 @@ const BuildParametersPopoverContent = ({ Read the docs - +
      ) ) : ( @@ -138,14 +144,16 @@ const BuildParametersPopoverContent = ({ ); }; -const Form = ({ - ephemeralParameters, - buildParameters, - onSubmit, -}: { +interface FormProps { ephemeralParameters: TemplateVersionParameter[]; buildParameters: WorkspaceBuildParameter[]; onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void; +} + +const Form: FC = ({ + ephemeralParameters, + buildParameters, + onSubmit, }) => { const form = useFormik({ initialValues: { @@ -180,17 +188,17 @@ const Form = ({ ); })} - +
      - +
      ); }; diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx index 33456d6bf2897..0e95000a85d0c 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx @@ -1,7 +1,3 @@ -import { type FC } from "react"; -import type { Workspace, WorkspaceBuildParameter } from "api/typesGenerated"; -import { BuildParametersPopover } from "./BuildParametersPopover"; - import Tooltip from "@mui/material/Tooltip"; import Button from "@mui/material/Button"; import LoadingButton from "@mui/lab/LoadingButton"; @@ -15,15 +11,18 @@ import OutlinedBlockIcon from "@mui/icons-material/BlockOutlined"; import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew"; import RetryIcon from "@mui/icons-material/BuildOutlined"; import RetryDebugIcon from "@mui/icons-material/BugReportOutlined"; +import { type FC } from "react"; +import type { Workspace, WorkspaceBuildParameter } from "api/typesGenerated"; +import { BuildParametersPopover } from "./BuildParametersPopover"; -interface WorkspaceActionProps { +interface ActionButtonProps { loading?: boolean; - handleAction: () => void; + handleAction: (buildParameters?: WorkspaceBuildParameter[]) => void; disabled?: boolean; tooltipText?: string; } -export const UpdateButton: FC = ({ +export const UpdateButton: FC = ({ handleAction, loading, }) => { @@ -33,14 +32,14 @@ export const UpdateButton: FC = ({ loadingPosition="start" data-testid="workspace-update-button" startIcon={} - onClick={handleAction} + onClick={() => handleAction()} > {loading ? <>Updating… : <>Update…} ); }; -export const ActivateButton: FC = ({ +export const ActivateButton: FC = ({ handleAction, loading, }) => { @@ -49,19 +48,24 @@ export const ActivateButton: FC = ({ loading={loading} loadingPosition="start" startIcon={} - onClick={handleAction} + onClick={() => handleAction()} > {loading ? <>Activating… : "Activate"} ); }; -export const StartButton: FC< - Omit & { - workspace: Workspace; - handleAction: (buildParameters?: WorkspaceBuildParameter[]) => void; - } -> = ({ handleAction, workspace, loading, disabled, tooltipText }) => { +interface ActionButtonPropsWithWorkspace extends ActionButtonProps { + workspace: Workspace; +} + +export const StartButton: FC = ({ + handleAction, + workspace, + loading, + disabled, + tooltipText, +}) => { const buttonContent = ( = ({ +export const StopButton: FC = ({ handleAction, loading, }) => { @@ -106,7 +110,7 @@ export const StopButton: FC = ({ loading={loading} loadingPosition="start" startIcon={} - onClick={handleAction} + onClick={() => handleAction()} data-testid="workspace-stop-button" > {loading ? <>Stopping… : "Stop"} @@ -114,16 +118,17 @@ export const StopButton: FC = ({ ); }; -export const RestartButton: FC< - Omit & { - workspace: Workspace; - handleAction: (buildParameters?: WorkspaceBuildParameter[]) => void; - } -> = ({ handleAction, loading, workspace, disabled, tooltipText }) => { +export const RestartButton: FC = ({ + handleAction, + loading, + workspace, + disabled, + tooltipText, +}) => { const buttonContent = ( button:hover + button": { borderLeft: "1px solid #FFF", @@ -156,19 +161,19 @@ export const RestartButton: FC< ); }; -export const CancelButton: FC = ({ handleAction }) => { +export const CancelButton: FC = ({ handleAction }) => { return ( - ); }; -interface DisabledProps { +interface DisabledButtonProps { label: string; } -export const DisabledButton: FC = ({ label }) => { +export const DisabledButton: FC = ({ label }) => { return ( diff --git a/site/src/pages/WorkspacePage/WorkspaceBuildLogsSection.tsx b/site/src/pages/WorkspacePage/WorkspaceBuildLogsSection.tsx index a45f01c7935df..ceb491277a00d 100644 --- a/site/src/pages/WorkspacePage/WorkspaceBuildLogsSection.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceBuildLogsSection.tsx @@ -1,15 +1,18 @@ -import Box from "@mui/material/Box"; -import { ProvisionerJobLog } from "api/typesGenerated"; +import { useTheme } from "@emotion/react"; +import { type FC, useRef, useEffect } from "react"; +import type { ProvisionerJobLog } from "api/typesGenerated"; import { Loader } from "components/Loader/Loader"; import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs"; -import { useRef, useEffect } from "react"; -export const WorkspaceBuildLogsSection = ({ +interface WorkspaceBuildLogsSectionProps { + logs?: ProvisionerJobLog[]; +} + +export const WorkspaceBuildLogsSection: FC = ({ logs, -}: { - logs: ProvisionerJobLog[] | undefined; }) => { const scrollRef = useRef(null); + const theme = useTheme(); useEffect(() => { // Auto scrolling makes hard to snapshot test using Chromatic @@ -24,14 +27,15 @@ export const WorkspaceBuildLogsSection = ({ }, [logs]); return ( - ({ - borderRadius: 1, +
      - ({ +
      Build logs - - ({ - height: "400px", - overflowY: "auto", - })} - > +
      +
      {logs ? ( ) : ( - - +
      )} -
      - +
      +
      ); }; diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index b11cfb1a1a59c..dfc124d2509a0 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -103,7 +103,10 @@ export const WorkspacePage: FC = () => { if (pageError) { return ( - + ); } diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index caf2d3bb88bf2..10968243188d6 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -10,7 +10,7 @@ import { } from "./WorkspaceParametersForm"; import { useNavigate } from "react-router-dom"; import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; -import { FC } from "react"; +import { type FC } from "react"; import { isApiValidationError } from "api/errors"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { WorkspaceBuildParameter } from "api/typesGenerated"; @@ -19,7 +19,7 @@ import Button from "@mui/material/Button"; import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined"; import { docs } from "utils/docs"; -const WorkspaceParametersPage = () => { +const WorkspaceParametersPage: FC = () => { const workspace = useWorkspaceSettings(); const parameters = useQuery({ queryKey: ["workspace", workspace.id, "parameters"], @@ -79,16 +79,12 @@ export const WorkspaceParametersPageView: FC< > = ({ data, submitError, isSubmitting, onSubmit, onCancel }) => { return ( <> - + Workspace parameters {submitError && !isApiValidationError(submitError) && ( - + )} {data ? ( diff --git a/site/src/pages/WorkspacesPage/BatchActions.tsx b/site/src/pages/WorkspacesPage/BatchActions.tsx index 6209b088f8bfd..d1c9bc43a5347 100644 --- a/site/src/pages/WorkspacesPage/BatchActions.tsx +++ b/site/src/pages/WorkspacesPage/BatchActions.tsx @@ -1,16 +1,18 @@ +import { useTheme } from "@emotion/react"; import TextField from "@mui/material/TextField"; -import { Box } from "@mui/system"; import { deleteWorkspace, startWorkspace, stopWorkspace } from "api/api"; -import { Workspace } from "api/typesGenerated"; +import type { Workspace } from "api/typesGenerated"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { displayError } from "components/GlobalSnackbar/utils"; -import { useState } from "react"; +import { type FC, useState } from "react"; import { useMutation } from "react-query"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; -export const useBatchActions = (options: { +interface UseBatchActionsProps { onSuccess: () => Promise; -}) => { +} + +export function useBatchActions(options: UseBatchActionsProps) { const { onSuccess } = options; const startAllMutation = useMutation({ @@ -56,7 +58,7 @@ export const useBatchActions = (options: { stopAllMutation.isLoading || deleteAllMutation.isLoading, }; -}; +} type BatchDeleteConfirmationProps = { checkedWorkspaces: Workspace[]; @@ -66,10 +68,11 @@ type BatchDeleteConfirmationProps = { onConfirm: () => void; }; -export const BatchDeleteConfirmation = ( - props: BatchDeleteConfirmationProps, +export const BatchDeleteConfirmation: FC = ( + props, ) => { const { checkedWorkspaces, open, onClose, onConfirm, isLoading } = props; + const theme = useTheme(); const [confirmation, setConfirmation] = useState({ value: "", error: false }); const confirmDeletion = () => { @@ -103,21 +106,20 @@ export const BatchDeleteConfirmation = ( confirmDeletion(); }} > - +
      Deleting these workspaces is irreversible! Are you sure you want to proceed? Type{" "} - theme.palette.text.primary, + color: theme.palette.text.primary, fontWeight: 600, }} > `DELETE` - {" "} + {" "} to confirm. - +
      { const value = e.currentTarget?.value; setConfirmation((c) => ({ ...c, value })); diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index cbbd8e63fa537..a50510a0ccaa7 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -1,11 +1,10 @@ -import { type PropsWithChildren, type ReactNode, useState } from "react"; +import { type FC, type ReactNode, useState } from "react"; import { type Template } from "api/typesGenerated"; import { type UseQueryResult } from "react-query"; import { Link as RouterLink, LinkProps as RouterLinkProps, } from "react-router-dom"; -import Box from "@mui/system/Box"; import Button from "@mui/material/Button"; import Link from "@mui/material/Link"; import AddIcon from "@mui/icons-material/AddOutlined"; @@ -25,16 +24,17 @@ const ICON_SIZE = 18; type TemplatesQuery = UseQueryResult; -type WorkspacesButtonProps = PropsWithChildren<{ +interface WorkspacesButtonProps { + children?: ReactNode; templatesFetchStatus: TemplatesQuery["status"]; templates: TemplatesQuery["data"]; -}>; +} -export function WorkspacesButton({ +export const WorkspacesButton: FC = ({ children, templatesFetchStatus, templates, -}: WorkspacesButtonProps) { +}) => { // Dataset should always be small enough that client-side filtering should be // good enough. Can swap out down the line if it becomes an issue const [searchTerm, setSearchTerm] = useState(""); @@ -69,15 +69,16 @@ export function WorkspacesButton({ onValueChange={(newValue) => setSearchTerm(newValue)} placeholder="Type/select a workspace template" label="Template select for workspace" - sx={{ flexShrink: 0, columnGap: 1.5 }} + css={{ flexShrink: 0, columnGap: 12 }} /> {templatesFetchStatus === "loading" ? ( @@ -93,7 +94,7 @@ export function WorkspacesButton({ )} - ({ padding: "8px 0", borderTop: `1px solid ${theme.palette.divider}`, @@ -105,20 +106,23 @@ export function WorkspacesButton({ display: "flex", alignItems: "center", columnGap: 12, - color: theme.palette.primary.main, })} > See all templates - + ); +}; + +interface WorkspaceResultsRowProps { + template: Template; } -function WorkspaceResultsRow({ template }: { template: Template }) { +const WorkspaceResultsRow: FC = ({ template }) => { return ( - ({ color: theme.palette.text.primary, display: "flex", @@ -170,15 +174,15 @@ function WorkspaceResultsRow({ template }: { template: Template }) { developer {template.active_user_count === 1 ? "" : "s"} - + ); -} +}; -function PopoverLink(props: RouterLinkProps) { +const PopoverLink: FC = ({ children, ...linkProps }) => { return ( ({ color: theme.palette.text.primary, padding: "8px 16px", @@ -193,9 +197,11 @@ function PopoverLink(props: RouterLinkProps) { backgroundColor: theme.palette.action.hover, }, })} - /> + > + {children} + ); -} +}; function sortTemplatesByUsersDesc( templates: readonly Template[], diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index fe096ef9c065d..c68e1cdfbcae6 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -12,7 +12,6 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { WorkspacesFilter } from "./filter/filter"; import { hasError, isApiValidationError } from "api/errors"; import { TableToolbar } from "components/TableToolbar/TableToolbar"; -import Box from "@mui/material/Box"; import DeleteOutlined from "@mui/icons-material/DeleteOutlined"; import { WorkspacesButton } from "./WorkspacesButton"; import { UseQueryResult } from "react-query"; @@ -136,11 +135,11 @@ export const WorkspacesPageView = ({ {checkedWorkspaces.length > 0 ? ( <> - +
      Selected {checkedWorkspaces.length} of{" "} {workspaces?.length}{" "} {workspaces?.length === 1 ? "workspace" : "workspaces"} - +
      diff --git a/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx b/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx index 297ae5699fbdc..d9c8a8ab0de8e 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx @@ -7,88 +7,86 @@ import { type ForwardedRef, type KeyboardEvent, + type InputHTMLAttributes, forwardRef, useId, } from "react"; - -import Box from "@mui/system/Box"; import SearchIcon from "@mui/icons-material/SearchOutlined"; import { visuallyHidden } from "@mui/utils"; -import { type SystemStyleObject } from "@mui/system"; - -type Props = { - value: string; - onValueChange: (newValue: string) => void; +import { useTheme } from "@emotion/react"; - placeholder?: string; +interface SearchBoxProps extends InputHTMLAttributes { label?: string; + value: string; onKeyDown?: (event: KeyboardEvent) => void; - sx?: SystemStyleObject; -}; + onValueChange: (newValue: string) => void; +} export const SearchBox = forwardRef(function SearchBox( - { - value, + props: SearchBoxProps, + ref?: ForwardedRef, +) { + const { onValueChange, onKeyDown, label = "Search", placeholder = "Search...", - sx = {}, - }: Props, - ref?: ForwardedRef, -) { + ...attrs + } = props; + const hookId = useId(); + const theme = useTheme(); + const inputId = `${hookId}-${SearchBox.name}-input`; return ( - `1px solid ${theme.palette.divider}`, - ...sx, + borderBottom: `1px solid ${theme.palette.divider}`, }} - onKeyDown={onKeyDown} > - +
      theme.palette.text.secondary, + marginLeft: "auto", + marginRight: "auto", + color: theme.palette.text.secondary, }} /> - +
      - + + - onValueChange(e.target.value)} - sx={{ + css={{ height: "100%", border: 0, background: "none", width: "100%", outline: 0, "&::placeholder": { - color: (theme) => theme.palette.text.secondary, + color: theme.palette.text.secondary, }, }} /> - + ); }); diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 689b857509355..c08563f8bc63f 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -4,16 +4,18 @@ import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; -import { Template, Workspace } from "api/typesGenerated"; -import { FC, ReactNode } from "react"; +import Checkbox from "@mui/material/Checkbox"; +import Skeleton from "@mui/material/Skeleton"; +import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"; +import { useTheme } from "@emotion/react"; +import { type FC, type ReactNode } from "react"; +import { useNavigate } from "react-router-dom"; +import type { Template, Workspace } from "api/typesGenerated"; import { TableLoaderSkeleton, TableRowSkeleton, } from "components/TableLoader/TableLoader"; -import { useNavigate } from "react-router-dom"; import { useClickableTableRow } from "hooks/useClickableTableRow"; -import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"; -import Box from "@mui/material/Box"; import { AvatarData } from "components/AvatarData/AvatarData"; import { Avatar } from "components/Avatar/Avatar"; import { Stack } from "components/Stack/Stack"; @@ -21,9 +23,7 @@ import { LastUsed } from "pages/WorkspacesPage/LastUsed"; import { WorkspaceOutdatedTooltip } from "components/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip"; import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"; import { getDisplayWorkspaceTemplateName } from "utils/workspace"; -import Checkbox from "@mui/material/Checkbox"; import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"; -import Skeleton from "@mui/material/Skeleton"; import { InfoTooltip } from "components/InfoTooltip/InfoTooltip"; import { WorkspacesEmpty } from "./WorkspacesEmpty"; @@ -49,18 +49,20 @@ export const WorkspacesTable: FC = ({ templates, canCreateTemplate, }) => { + const theme = useTheme(); + return ( - +
      {canCheckWorkspaces && ( = ({ /> )} Name - +
      Template Last used @@ -109,12 +111,14 @@ export const WorkspacesTable: FC = ({ checked={checked} > - +
      {canCheckWorkspaces && ( = ({ } /> - +
      @@ -181,7 +185,9 @@ export const WorkspacesTable: FC = ({ - +
      {workspace.latest_build.status === "running" && !workspace.health.healthy && ( @@ -191,19 +197,19 @@ export const WorkspacesTable: FC = ({ message="Your workspace is running but some agents are unhealthy." /> )} - +
      - +
      theme.palette.text.secondary, + css={{ + color: theme.palette.text.secondary, width: 20, height: 20, }} /> - +
      ); @@ -214,12 +220,19 @@ export const WorkspacesTable: FC = ({ ); }; -const WorkspacesRow: FC<{ +interface WorkspacesRowProps { workspace: Workspace; - children: ReactNode; + children?: ReactNode; checked: boolean; -}> = ({ workspace, children, checked }) => { +} + +const WorkspacesRow: FC = ({ + workspace, + children, + checked, +}) => { const navigate = useNavigate(); + const theme = useTheme(); const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`; const openLinkInNewTab = () => window.open(workspacePageLink, "_blank"); @@ -245,9 +258,8 @@ const WorkspacesRow: FC<{ - checked ? theme.palette.action.hover : undefined, + css={{ + backgroundColor: checked ? theme.palette.action.hover : undefined, }} > {children} @@ -255,21 +267,21 @@ const WorkspacesRow: FC<{ ); }; -const TableLoader = ({ - canCheckWorkspaces, -}: { - canCheckWorkspaces: boolean; -}) => { +interface TableLoaderProps { + canCheckWorkspaces?: boolean; +} + +const TableLoader: FC = ({ canCheckWorkspaces }) => { return ( - +
      {canCheckWorkspaces && ( - + )} - +
      diff --git a/site/src/pages/WorkspacesPage/filter/filter.tsx b/site/src/pages/WorkspacesPage/filter/filter.tsx index 2d7a6d6b0467d..3bc7c33240a6f 100644 --- a/site/src/pages/WorkspacesPage/filter/filter.tsx +++ b/site/src/pages/WorkspacesPage/filter/filter.tsx @@ -1,10 +1,9 @@ -import { FC } from "react"; -import Box from "@mui/material/Box"; +import { type FC } from "react"; +import { useTheme } from "@emotion/react"; import { useIsWorkspaceActionsEnabled } from "components/Dashboard/DashboardProvider"; -import { Avatar, AvatarProps } from "components/Avatar/Avatar"; -import { Palette, PaletteColor } from "@mui/material/styles"; -import { TemplateFilterMenu, StatusFilterMenu } from "./menus"; -import { TemplateOption, StatusOption } from "./options"; +import { Avatar, type AvatarProps } from "components/Avatar/Avatar"; +import type { TemplateFilterMenu, StatusFilterMenu } from "./menus"; +import type { TemplateOption, StatusOption } from "./options"; import { Filter, FilterMenu, @@ -14,7 +13,7 @@ import { SearchFieldSkeleton, useFilter, } from "components/Filter/filter"; -import { UserFilterMenu, UserMenu } from "components/Filter/UserFilter"; +import { type UserFilterMenu, UserMenu } from "components/Filter/UserFilter"; import { docs } from "utils/docs"; export const workspaceFilterQuery = { @@ -70,11 +69,11 @@ type WorkspaceFilterProps = { }; }; -export const WorkspacesFilter = ({ +export const WorkspacesFilter: FC = ({ filter, error, menus, -}: WorkspaceFilterProps) => { +}) => { const actionsEnabled = useIsWorkspaceActionsEnabled(); const presets = actionsEnabled ? PRESETS_WITH_DORMANT : PRESET_FILTERS; @@ -87,7 +86,7 @@ export const WorkspacesFilter = ({ learnMoreLink={docs("/workspaces#workspace-filtering")} options={ <> - {menus.user && } + {menus.user && } @@ -122,12 +121,14 @@ const TemplateMenu = (menu: TemplateFilterMenu) => { ); }; -const TemplateOptionItem = ({ - option, - isSelected, -}: { +interface TemplateOptionItemProps { option: TemplateOption; isSelected?: boolean; +} + +const TemplateOptionItem: FC = ({ + option, + isSelected, }) => { return ( } /> ); }; -const TemplateAvatar: FC< - AvatarProps & { templateName: string; icon?: string } -> = ({ templateName, icon, ...avatarProps }) => { +interface TemplateAvatarProps extends AvatarProps { + templateName: string; + icon?: string; +} + +const TemplateAvatar: FC = ({ + templateName, + icon, + ...avatarProps +}) => { return icon ? ( ) : ( @@ -172,13 +180,12 @@ const StatusMenu = (menu: StatusFilterMenu) => { ); }; -const StatusOptionItem = ({ - option, - isSelected, -}: { +interface StatusOptionItem { option: StatusOption; isSelected?: boolean; -}) => { +} + +const StatusOptionItem: FC = ({ option, isSelected }) => { return ( = ({ option }) => { - const color = option.color === "notice" ? "warning" : option.color; +interface StatusIndicatorProps { + option: StatusOption; +} + +const StatusIndicator: FC = ({ option }) => { + const theme = useTheme(); return ( - { - return { - backgroundColor: ( - theme.palette[color as keyof Palette] as PaletteColor - ).light, - }; +
      ); diff --git a/site/src/pages/WorkspacesPage/filter/options.ts b/site/src/pages/WorkspacesPage/filter/options.ts index 5266e4cdb9587..dcdbd99984527 100644 --- a/site/src/pages/WorkspacesPage/filter/options.ts +++ b/site/src/pages/WorkspacesPage/filter/options.ts @@ -1,7 +1,8 @@ import { BaseOption } from "components/Filter/options"; +import type { ThemeRole } from "theme/experimental"; export type StatusOption = BaseOption & { - color: string; + color: ThemeRole; }; export type TemplateOption = BaseOption & { diff --git a/site/src/testHelpers/renderHelpers.tsx b/site/src/testHelpers/renderHelpers.tsx index b9d3fcdde5ded..7cb1ad5400428 100644 --- a/site/src/testHelpers/renderHelpers.tsx +++ b/site/src/testHelpers/renderHelpers.tsx @@ -122,13 +122,9 @@ type RenderHookWithAuthOptions = Partial< */ export async function renderHookWithAuth( render: (initialProps: Props) => Result, - { - initialProps, - path = "/", - route = "/", - extraRoutes = [], - }: RenderHookWithAuthOptions = {}, + options: RenderHookWithAuthOptions = {}, ) { + const { initialProps, path = "/", route = "/", extraRoutes = [] } = options; const queryClient = createTestQueryClient(); // Easy to miss – there's an evil definite assignment via the !