chore: use emotion for styling (pt. 7) (#10431)

This commit is contained in:
Kayla Washburn
2023-11-01 11:28:26 -04:00
committed by GitHub
parent ec7d7595ff
commit 5284d974ef
21 changed files with 556 additions and 668 deletions

View File

@ -1,6 +1,8 @@
import makeStyles from "@mui/styles/makeStyles";
import { watchAgentMetadata } from "api/api"; import { watchAgentMetadata } from "api/api";
import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated"; import type {
WorkspaceAgent,
WorkspaceAgentMetadata,
} from "api/typesGenerated";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { import {
@ -13,17 +15,15 @@ import {
} from "react"; } from "react";
import Skeleton from "@mui/material/Skeleton"; import Skeleton from "@mui/material/Skeleton";
import { MONOSPACE_FONT_FAMILY } from "theme/constants"; import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { combineClasses } from "utils/combineClasses";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Box, { BoxProps } from "@mui/material/Box"; import Box, { BoxProps } from "@mui/material/Box";
import { type Interpolation, type Theme } from "@emotion/react";
type ItemStatus = "stale" | "valid" | "loading"; type ItemStatus = "stale" | "valid" | "loading";
export const WatchAgentMetadataContext = createContext(watchAgentMetadata); export const WatchAgentMetadataContext = createContext(watchAgentMetadata);
const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => { const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
const styles = useStyles();
if (item.result === undefined) { if (item.result === undefined) {
throw new Error("Metadata item result is undefined"); throw new Error("Metadata item result is undefined");
} }
@ -56,41 +56,29 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
// could be buggy. But, how common is that anyways? // could be buggy. But, how common is that anyways?
const value = const value =
status === "loading" ? ( status === "loading" ? (
<Skeleton <Skeleton width={65} height={12} variant="text" css={styles.skeleton} />
width={65}
height={12}
variant="text"
className={styles.skeleton}
/>
) : status === "stale" ? ( ) : status === "stale" ? (
<Tooltip title="This data is stale and no longer up to date"> <Tooltip title="This data is stale and no longer up to date">
<StaticWidth <StaticWidth css={[styles.metadataValue, styles.metadataStale]}>
className={combineClasses([
styles.metadataValue,
styles.metadataStale,
])}
>
{item.result.value} {item.result.value}
</StaticWidth> </StaticWidth>
</Tooltip> </Tooltip>
) : ( ) : (
<StaticWidth <StaticWidth
className={combineClasses([ css={[
styles.metadataValue, styles.metadataValue,
item.result.error.length === 0 item.result.error.length === 0
? styles.metadataValueSuccess ? styles.metadataValueSuccess
: styles.metadataValueError, : styles.metadataValueError,
])} ]}
> >
{item.result.value} {item.result.value}
</StaticWidth> </StaticWidth>
); );
return ( return (
<div className={styles.metadata}> <div css={styles.metadata}>
<div className={styles.metadataLabel}> <div css={styles.metadataLabel}>{item.description.display_name}</div>
{item.description.display_name}
</div>
<Box>{value}</Box> <Box>{value}</Box>
</div> </div>
); );
@ -101,12 +89,11 @@ export interface AgentMetadataViewProps {
} }
export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => { export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
const styles = useStyles();
if (metadata.length === 0) { if (metadata.length === 0) {
return <></>; return <></>;
} }
return ( return (
<div className={styles.root}> <div css={styles.root}>
<Stack alignItems="baseline" direction="row" spacing={6}> <Stack alignItems="baseline" direction="row" spacing={6}>
{metadata.map((m) => { {metadata.map((m) => {
if (m.description === undefined) { if (m.description === undefined) {
@ -127,7 +114,6 @@ export const AgentMetadata: FC<{
WorkspaceAgentMetadata[] | undefined WorkspaceAgentMetadata[] | undefined
>(undefined); >(undefined);
const watchAgentMetadata = useContext(WatchAgentMetadataContext); const watchAgentMetadata = useContext(WatchAgentMetadataContext);
const styles = useStyles();
useEffect(() => { useEffect(() => {
if (storybookMetadata !== undefined) { if (storybookMetadata !== undefined) {
@ -166,7 +152,7 @@ export const AgentMetadata: FC<{
if (metadata === undefined) { if (metadata === undefined) {
return ( return (
<div className={styles.root}> <div css={styles.root}>
<AgentMetadataSkeleton /> <AgentMetadataSkeleton />
</div> </div>
); );
@ -176,21 +162,19 @@ export const AgentMetadata: FC<{
}; };
export const AgentMetadataSkeleton: FC = () => { export const AgentMetadataSkeleton: FC = () => {
const styles = useStyles();
return ( return (
<Stack alignItems="baseline" direction="row" spacing={6}> <Stack alignItems="baseline" direction="row" spacing={6}>
<div className={styles.metadata}> <div css={styles.metadata}>
<Skeleton width={40} height={12} variant="text" /> <Skeleton width={40} height={12} variant="text" />
<Skeleton width={65} height={14} variant="text" /> <Skeleton width={65} height={14} variant="text" />
</div> </div>
<div className={styles.metadata}> <div css={styles.metadata}>
<Skeleton width={40} height={12} variant="text" /> <Skeleton width={40} height={12} variant="text" />
<Skeleton width={65} height={14} variant="text" /> <Skeleton width={65} height={14} variant="text" />
</div> </div>
<div className={styles.metadata}> <div css={styles.metadata}>
<Skeleton width={40} height={12} variant="text" /> <Skeleton width={40} height={12} variant="text" />
<Skeleton width={65} height={14} variant="text" /> <Skeleton width={65} height={14} variant="text" />
</div> </div>
@ -219,16 +203,16 @@ const StaticWidth = (props: BoxProps) => {
// These are more or less copied from // These are more or less copied from
// site/src/components/Resources/ResourceCard.tsx // site/src/components/Resources/ResourceCard.tsx
const useStyles = makeStyles((theme) => ({ const styles = {
root: { root: (theme) => ({
padding: theme.spacing(2.5, 4), padding: theme.spacing(2.5, 4),
borderTop: `1px solid ${theme.palette.divider}`, borderTop: `1px solid ${theme.palette.divider}`,
background: theme.palette.background.paper, background: theme.palette.background.paper,
overflowX: "auto", overflowX: "auto",
scrollPadding: theme.spacing(0, 4), scrollPadding: theme.spacing(0, 4),
}, }),
metadata: { metadata: (theme) => ({
fontSize: 12, fontSize: 12,
lineHeight: "normal", lineHeight: "normal",
display: "flex", display: "flex",
@ -240,15 +224,15 @@ const useStyles = makeStyles((theme) => ({
"&:last-child": { "&:last-child": {
paddingRight: theme.spacing(4), paddingRight: theme.spacing(4),
}, },
}, }),
metadataLabel: { metadataLabel: (theme) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
textOverflow: "ellipsis", textOverflow: "ellipsis",
overflow: "hidden", overflow: "hidden",
whiteSpace: "nowrap", whiteSpace: "nowrap",
fontWeight: 500, fontWeight: 500,
}, }),
metadataValue: { metadataValue: {
textOverflow: "ellipsis", textOverflow: "ellipsis",
@ -258,29 +242,29 @@ const useStyles = makeStyles((theme) => ({
fontSize: 14, fontSize: 14,
}, },
metadataValueSuccess: { metadataValueSuccess: (theme) => ({
color: theme.palette.success.light, color: theme.palette.success.light,
}, }),
metadataValueError: { metadataValueError: (theme) => ({
color: theme.palette.error.main, color: theme.palette.error.main,
}, }),
metadataStale: { metadataStale: (theme) => ({
color: theme.palette.text.disabled, color: theme.palette.text.disabled,
cursor: "pointer", cursor: "pointer",
}, }),
skeleton: { skeleton: (theme) => ({
marginTop: theme.spacing(0.5), marginTop: theme.spacing(0.5),
}, }),
inlineCommand: { inlineCommand: (theme) => ({
fontFamily: MONOSPACE_FONT_FAMILY, fontFamily: MONOSPACE_FONT_FAMILY,
display: "inline-block", display: "inline-block",
fontWeight: 600, fontWeight: 600,
margin: 0, margin: 0,
borderRadius: 4, borderRadius: 4,
color: theme.palette.text.primary, color: theme.palette.text.primary,
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,9 +1,9 @@
import Collapse from "@mui/material/Collapse"; import Collapse from "@mui/material/Collapse";
import Skeleton from "@mui/material/Skeleton"; import Skeleton from "@mui/material/Skeleton";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import { makeStyles } from "@mui/styles"; import { type Interpolation, type Theme } from "@emotion/react";
import * as API from "api/api"; import * as API from "api/api";
import { import type {
Workspace, Workspace,
WorkspaceAgent, WorkspaceAgent,
WorkspaceAgentLogSource, WorkspaceAgentLogSource,
@ -30,7 +30,6 @@ import {
import AutoSizer from "react-virtualized-auto-sizer"; import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeList as List, ListOnScrollProps } from "react-window"; import { FixedSizeList as List, ListOnScrollProps } from "react-window";
import { colors } from "theme/colors"; import { colors } from "theme/colors";
import { combineClasses } from "utils/combineClasses";
import { Stack } from "../Stack/Stack"; import { Stack } from "../Stack/Stack";
import { AgentLatency } from "./AgentLatency"; import { AgentLatency } from "./AgentLatency";
import { AgentMetadata } from "./AgentMetadata"; import { AgentMetadata } from "./AgentMetadata";
@ -75,7 +74,6 @@ export const AgentRow: FC<AgentRowProps> = ({
sshPrefix, sshPrefix,
storybookLogs, storybookLogs,
}) => { }) => {
const styles = useStyles();
const hasAppsToDisplay = !hideVSCodeDesktopButton || agent.apps.length > 0; const hasAppsToDisplay = !hideVSCodeDesktopButton || agent.apps.length > 0;
const shouldDisplayApps = const shouldDisplayApps =
showApps && showApps &&
@ -159,28 +157,26 @@ export const AgentRow: FC<AgentRowProps> = ({
key={agent.id} key={agent.id}
direction="column" direction="column"
spacing={0} spacing={0}
className={combineClasses([ css={[
styles.agentRow, styles.agentRow,
styles[`agentRow-${agent.status}`], styles[`agentRow-${agent.status}`],
styles[`agentRow-lifecycle-${agent.lifecycle_state}`], styles[`agentRow-lifecycle-${agent.lifecycle_state}`],
])} ]}
> >
<div className={styles.agentInfo}> <div css={styles.agentInfo}>
<div className={styles.agentNameAndStatus}> <div css={styles.agentNameAndStatus}>
<div className={styles.agentNameAndInfo}> <div css={styles.agentNameAndInfo}>
<AgentStatus agent={agent} /> <AgentStatus agent={agent} />
<div className={styles.agentName}>{agent.name}</div> <div css={styles.agentName}>{agent.name}</div>
<Stack <Stack
direction="row" direction="row"
spacing={2} spacing={2}
alignItems="baseline" alignItems="baseline"
className={styles.agentDescription} css={styles.agentDescription}
> >
{agent.status === "connected" && ( {agent.status === "connected" && (
<> <>
<span className={styles.agentOS}> <span css={styles.agentOS}>{agent.operating_system}</span>
{agent.operating_system}
</span>
<AgentVersion <AgentVersion
agent={agent} agent={agent}
serverVersion={serverVersion} serverVersion={serverVersion}
@ -200,7 +196,7 @@ export const AgentRow: FC<AgentRowProps> = ({
</div> </div>
{agent.status === "connected" && ( {agent.status === "connected" && (
<div className={styles.agentButtons}> <div css={styles.agentButtons}>
{shouldDisplayApps && ( {shouldDisplayApps && (
<> <>
{(agent.display_apps.includes("vscode") || {(agent.display_apps.includes("vscode") ||
@ -258,18 +254,18 @@ export const AgentRow: FC<AgentRowProps> = ({
)} )}
{agent.status === "connecting" && ( {agent.status === "connecting" && (
<div className={styles.agentButtons}> <div css={styles.agentButtons}>
<Skeleton <Skeleton
width={80} width={80}
height={32} height={32}
variant="rectangular" variant="rectangular"
className={styles.buttonSkeleton} css={styles.buttonSkeleton}
/> />
<Skeleton <Skeleton
width={110} width={110}
height={32} height={32}
variant="rectangular" variant="rectangular"
className={styles.buttonSkeleton} css={styles.buttonSkeleton}
/> />
</div> </div>
)} )}
@ -278,7 +274,7 @@ export const AgentRow: FC<AgentRowProps> = ({
<AgentMetadata storybookMetadata={storybookAgentMetadata} agent={agent} /> <AgentMetadata storybookMetadata={storybookAgentMetadata} agent={agent} />
{hasStartupFeatures && ( {hasStartupFeatures && (
<div className={styles.logsPanel}> <div css={styles.logsPanel}>
<Collapse in={showLogs}> <Collapse in={showLogs}>
<AutoSizer disableHeight> <AutoSizer disableHeight>
{({ width }) => ( {({ width }) => (
@ -289,7 +285,7 @@ export const AgentRow: FC<AgentRowProps> = ({
itemCount={startupLogs.length} itemCount={startupLogs.length}
itemSize={logLineHeight} itemSize={logLineHeight}
width={width} width={width}
className={styles.startupLogs} css={styles.startupLogs}
onScroll={handleLogScroll} onScroll={handleLogScroll}
> >
{({ index, style }) => { {({ index, style }) => {
@ -323,7 +319,7 @@ export const AgentRow: FC<AgentRowProps> = ({
alt="" alt=""
width={16} width={16}
height={16} height={16}
style={{ css={{
marginRight: 8, marginRight: 8,
}} }}
/> />
@ -331,7 +327,7 @@ export const AgentRow: FC<AgentRowProps> = ({
} else { } else {
icon = ( icon = (
<div <div
style={{ css={{
width: 16, width: 16,
height: 16, height: 16,
marginRight: 8, marginRight: 8,
@ -363,7 +359,7 @@ export const AgentRow: FC<AgentRowProps> = ({
) { ) {
icon = ( icon = (
<div <div
style={{ css={{
minWidth: 16, minWidth: 16,
width: 16, width: 16,
height: 16, height: 16,
@ -374,7 +370,7 @@ export const AgentRow: FC<AgentRowProps> = ({
}} }}
> >
<div <div
style={{ css={{
height: nextChangesSource ? "50%" : "100%", height: nextChangesSource ? "50%" : "100%",
width: 4, width: 4,
background: "hsl(222, 31%, 25%)", background: "hsl(222, 31%, 25%)",
@ -383,7 +379,7 @@ export const AgentRow: FC<AgentRowProps> = ({
/> />
{nextChangesSource && ( {nextChangesSource && (
<div <div
style={{ css={{
height: 4, height: 4,
width: "50%", width: "50%",
top: "calc(50% - 2px)", top: "calc(50% - 2px)",
@ -429,13 +425,10 @@ export const AgentRow: FC<AgentRowProps> = ({
</AutoSizer> </AutoSizer>
</Collapse> </Collapse>
<div className={styles.logsPanelButtons}> <div css={styles.logsPanelButtons}>
{showLogs ? ( {showLogs ? (
<button <button
className={combineClasses([ css={[styles.logsPanelButton, styles.toggleLogsButton]}
styles.logsPanelButton,
styles.toggleLogsButton,
])}
onClick={() => { onClick={() => {
setShowLogs((v) => !v); setShowLogs((v) => !v);
}} }}
@ -445,10 +438,7 @@ export const AgentRow: FC<AgentRowProps> = ({
</button> </button>
) : ( ) : (
<button <button
className={combineClasses([ css={[styles.logsPanelButton, styles.toggleLogsButton]}
styles.logsPanelButton,
styles.toggleLogsButton,
])}
onClick={() => { onClick={() => {
setShowLogs((v) => !v); setShowLogs((v) => !v);
}} }}
@ -511,8 +501,8 @@ const useAgentLogs = (
return logs; return logs;
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
agentRow: { agentRow: (theme) => ({
backgroundColor: theme.palette.background.paperLight, backgroundColor: theme.palette.background.paperLight,
fontSize: 16, fontSize: 16,
borderLeft: `2px solid ${theme.palette.text.secondary}`, borderLeft: `2px solid ${theme.palette.text.secondary}`,
@ -520,59 +510,59 @@ const useStyles = makeStyles((theme) => ({
"&:not(:first-of-type)": { "&:not(:first-of-type)": {
borderTop: `2px solid ${theme.palette.divider}`, borderTop: `2px solid ${theme.palette.divider}`,
}, },
}, }),
"agentRow-connected": { "agentRow-connected": (theme) => ({
borderLeftColor: theme.palette.success.light, borderLeftColor: theme.palette.success.light,
}, }),
"agentRow-disconnected": { "agentRow-disconnected": (theme) => ({
borderLeftColor: theme.palette.text.secondary, borderLeftColor: theme.palette.text.secondary,
}, }),
"agentRow-connecting": { "agentRow-connecting": (theme) => ({
borderLeftColor: theme.palette.info.light, borderLeftColor: theme.palette.info.light,
}, }),
"agentRow-timeout": { "agentRow-timeout": (theme) => ({
borderLeftColor: theme.palette.warning.light, borderLeftColor: theme.palette.warning.light,
}, }),
"agentRow-lifecycle-created": {}, "agentRow-lifecycle-created": {},
"agentRow-lifecycle-starting": { "agentRow-lifecycle-starting": (theme) => ({
borderLeftColor: theme.palette.info.light, borderLeftColor: theme.palette.info.light,
}, }),
"agentRow-lifecycle-ready": { "agentRow-lifecycle-ready": (theme) => ({
borderLeftColor: theme.palette.success.light, borderLeftColor: theme.palette.success.light,
}, }),
"agentRow-lifecycle-start_timeout": { "agentRow-lifecycle-start_timeout": (theme) => ({
borderLeftColor: theme.palette.warning.light, borderLeftColor: theme.palette.warning.light,
}, }),
"agentRow-lifecycle-start_error": { "agentRow-lifecycle-start_error": (theme) => ({
borderLeftColor: theme.palette.error.light, borderLeftColor: theme.palette.error.light,
}, }),
"agentRow-lifecycle-shutting_down": { "agentRow-lifecycle-shutting_down": (theme) => ({
borderLeftColor: theme.palette.info.light, borderLeftColor: theme.palette.info.light,
}, }),
"agentRow-lifecycle-shutdown_timeout": { "agentRow-lifecycle-shutdown_timeout": (theme) => ({
borderLeftColor: theme.palette.warning.light, borderLeftColor: theme.palette.warning.light,
}, }),
"agentRow-lifecycle-shutdown_error": { "agentRow-lifecycle-shutdown_error": (theme) => ({
borderLeftColor: theme.palette.error.light, borderLeftColor: theme.palette.error.light,
}, }),
"agentRow-lifecycle-off": { "agentRow-lifecycle-off": (theme) => ({
borderLeftColor: theme.palette.text.secondary, borderLeftColor: theme.palette.text.secondary,
}, }),
agentInfo: { agentInfo: (theme) => ({
padding: theme.spacing(2, 4), padding: theme.spacing(2, 4),
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
@ -582,9 +572,9 @@ const useStyles = makeStyles((theme) => ({
[theme.breakpoints.down("md")]: { [theme.breakpoints.down("md")]: {
gap: theme.spacing(2), gap: theme.spacing(2),
}, },
}, }),
agentNameAndInfo: { agentNameAndInfo: (theme) => ({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: theme.spacing(3), gap: theme.spacing(3),
@ -593,9 +583,9 @@ const useStyles = makeStyles((theme) => ({
[theme.breakpoints.down("md")]: { [theme.breakpoints.down("md")]: {
gap: theme.spacing(1.5), gap: theme.spacing(1.5),
}, },
}, }),
agentButtons: { agentButtons: (theme) => ({
display: "flex", display: "flex",
gap: theme.spacing(1), gap: theme.spacing(1),
justifyContent: "flex-end", justifyContent: "flex-end",
@ -606,14 +596,14 @@ const useStyles = makeStyles((theme) => ({
marginLeft: 0, marginLeft: 0,
justifyContent: "flex-start", justifyContent: "flex-start",
}, },
}, }),
agentDescription: { agentDescription: (theme) => ({
fontSize: 14, fontSize: 14,
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}, }),
startupLogs: { startupLogs: (theme) => ({
maxHeight: 256, maxHeight: 256,
borderBottom: `1px solid ${theme.palette.divider}`, borderBottom: `1px solid ${theme.palette.divider}`,
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
@ -623,9 +613,9 @@ const useStyles = makeStyles((theme) => ({
"& > div": { "& > div": {
position: "relative", position: "relative",
}, },
}, }),
agentNameAndStatus: { agentNameAndStatus: (theme) => ({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: theme.spacing(4), gap: theme.spacing(4),
@ -633,9 +623,9 @@ const useStyles = makeStyles((theme) => ({
[theme.breakpoints.down("md")]: { [theme.breakpoints.down("md")]: {
width: "100%", width: "100%",
}, },
}, }),
agentName: { agentName: (theme) => ({
whiteSpace: "nowrap", whiteSpace: "nowrap",
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
@ -648,15 +638,15 @@ const useStyles = makeStyles((theme) => ({
[theme.breakpoints.down("md")]: { [theme.breakpoints.down("md")]: {
overflow: "unset", overflow: "unset",
}, },
}, }),
agentDataGroup: { agentDataGroup: (theme) => ({
display: "flex", display: "flex",
alignItems: "baseline", alignItems: "baseline",
gap: theme.spacing(6), gap: theme.spacing(6),
}, }),
agentData: { agentData: (theme) => ({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
fontSize: 12, fontSize: 12,
@ -665,17 +655,17 @@ const useStyles = makeStyles((theme) => ({
fontWeight: 500, fontWeight: 500,
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}, },
}, }),
logsPanel: { logsPanel: (theme) => ({
borderTop: `1px solid ${theme.palette.divider}`, borderTop: `1px solid ${theme.palette.divider}`,
}, }),
logsPanelButtons: { logsPanelButtons: {
display: "flex", display: "flex",
}, },
logsPanelButton: { logsPanelButton: (theme) => ({
textAlign: "left", textAlign: "left",
background: "transparent", background: "transparent",
border: 0, border: 0,
@ -696,7 +686,7 @@ const useStyles = makeStyles((theme) => ({
"& svg": { "& svg": {
color: "inherit", color: "inherit",
}, },
}, }),
toggleLogsButton: { toggleLogsButton: {
width: "100%", width: "100%",
@ -706,17 +696,17 @@ const useStyles = makeStyles((theme) => ({
borderRadius: 4, borderRadius: 4,
}, },
agentErrorMessage: { agentErrorMessage: (theme) => ({
fontSize: 12, fontSize: 12,
fontWeight: 400, fontWeight: 400,
marginTop: theme.spacing(0.5), marginTop: theme.spacing(0.5),
color: theme.palette.warning.light, color: theme.palette.warning.light,
}, }),
agentOS: { agentOS: {
textTransform: "capitalize", textTransform: "capitalize",
}, },
})); } satisfies Record<string, Interpolation<Theme>>;
// These colors were picked at random. Feel free // These colors were picked at random. Feel free
// to add more, adjust, or change! Users will not // to add more, adjust, or change! Users will not

View File

@ -1,9 +1,8 @@
import { makeStyles } from "@mui/styles"; import { type Interpolation, type Theme } from "@emotion/react";
import { AppPreviewLink } from "components/Resources/AppLink/AppPreviewLink"; import { type FC } from "react";
import { FC } from "react"; import type { WorkspaceAgent } from "api/typesGenerated";
import { combineClasses } from "utils/combineClasses";
import { WorkspaceAgent } from "api/typesGenerated";
import { Stack } from "../Stack/Stack"; import { Stack } from "../Stack/Stack";
import { AppPreviewLink } from "./AppLink/AppPreviewLink";
interface AgentRowPreviewStyles { interface AgentRowPreviewStyles {
// Helpful when there are more than one row so the values are aligned // Helpful when there are more than one row so the values are aligned
@ -18,57 +17,58 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
agent, agent,
alignValues, alignValues,
}) => { }) => {
const styles = useStyles({ alignValues });
return ( return (
<Stack <Stack
key={agent.id} key={agent.id}
direction="row" direction="row"
alignItems="center" alignItems="center"
justifyContent="space-between" justifyContent="space-between"
className={styles.agentRow} css={styles.agentRow}
> >
<Stack direction="row" alignItems="baseline"> <Stack direction="row" alignItems="baseline">
<div className={styles.agentStatusWrapper}> <div css={styles.agentStatusWrapper}>
<div className={styles.agentStatusPreview}></div> <div css={styles.agentStatusPreview}></div>
</div> </div>
<Stack <Stack
alignItems="baseline" alignItems="baseline"
direction="row" direction="row"
spacing={4} spacing={4}
className={styles.agentData} css={styles.agentData}
> >
<Stack <Stack
direction="row" direction="row"
alignItems="baseline" alignItems="baseline"
spacing={1} spacing={1}
className={combineClasses([ css={[
styles.noShrink, styles.noShrink,
styles.agentDataItem, styles.agentDataItem,
styles.agentDataName, (theme) => ({
])} [theme.breakpoints.up("sm")]: {
minWidth: alignValues ? 240 : undefined,
},
}),
]}
> >
<span>Agent:</span> <span>Agent:</span>
<span className={styles.agentDataValue}>{agent.name}</span> <span css={styles.agentDataValue}>{agent.name}</span>
</Stack> </Stack>
<Stack <Stack
direction="row" direction="row"
alignItems="baseline" alignItems="baseline"
spacing={1} spacing={1}
className={combineClasses([ css={[
styles.noShrink, styles.noShrink,
styles.agentDataItem, styles.agentDataItem,
styles.agentDataOS, (theme) => ({
])} [theme.breakpoints.up("sm")]: {
minWidth: alignValues ? 100 : undefined,
},
}),
]}
> >
<span>OS:</span> <span>OS:</span>
<span <span css={[styles.agentDataValue, styles.agentOS]}>
className={combineClasses([
styles.agentDataValue,
styles.agentOS,
])}
>
{agent.operating_system} {agent.operating_system}
</span> </span>
</Stack> </Stack>
@ -77,7 +77,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
direction="row" direction="row"
alignItems="center" alignItems="center"
spacing={1} spacing={1}
className={styles.agentDataItem} css={styles.agentDataItem}
> >
<span>Apps:</span> <span>Apps:</span>
<Stack <Stack
@ -90,7 +90,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
<AppPreviewLink key={app.slug} app={app} /> <AppPreviewLink key={app.slug} app={app} />
))} ))}
{agent.apps.length === 0 && ( {agent.apps.length === 0 && (
<span className={styles.agentDataValue}>None</span> <span css={styles.agentDataValue}>None</span>
)} )}
</Stack> </Stack>
</Stack> </Stack>
@ -100,8 +100,8 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
agentRow: { agentRow: (theme) => ({
padding: theme.spacing(2, 4), padding: theme.spacing(2, 4),
backgroundColor: theme.palette.background.paperLight, backgroundColor: theme.palette.background.paperLight,
fontSize: 16, fontSize: 16,
@ -120,16 +120,16 @@ const useStyles = makeStyles((theme) => ({
top: 0, top: 0,
left: 49, left: 49,
}, },
}, }),
agentStatusWrapper: { agentStatusWrapper: (theme) => ({
width: theme.spacing(4.5), width: theme.spacing(4.5),
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
flexShrink: 0, flexShrink: 0,
}, }),
agentStatusPreview: { agentStatusPreview: (theme) => ({
width: 10, width: 10,
height: 10, height: 10,
border: `2px solid ${theme.palette.text.secondary}`, border: `2px solid ${theme.palette.text.secondary}`,
@ -137,7 +137,7 @@ const useStyles = makeStyles((theme) => ({
position: "relative", position: "relative",
zIndex: 1, zIndex: 1,
background: theme.palette.background.paper, background: theme.palette.background.paper,
}, }),
agentName: { agentName: {
fontWeight: 600, fontWeight: 600,
@ -146,10 +146,9 @@ const useStyles = makeStyles((theme) => ({
agentOS: { agentOS: {
textTransform: "capitalize", textTransform: "capitalize",
fontSize: 14, fontSize: 14,
color: theme.palette.text.secondary,
}, },
agentData: { agentData: (theme) => ({
fontSize: 14, fontSize: 14,
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@ -157,36 +156,22 @@ const useStyles = makeStyles((theme) => ({
gap: theme.spacing(2), gap: theme.spacing(2),
flexWrap: "wrap", flexWrap: "wrap",
}, },
}, }),
agentDataName: { agentDataValue: (theme) => ({
[theme.breakpoints.up("sm")]: {
minWidth: ({ alignValues }: AgentRowPreviewStyles) =>
alignValues ? 240 : undefined,
},
},
agentDataOS: {
[theme.breakpoints.up("sm")]: {
minWidth: ({ alignValues }: AgentRowPreviewStyles) =>
alignValues ? 100 : undefined,
},
},
agentDataValue: {
color: theme.palette.text.primary, color: theme.palette.text.primary,
}, }),
noShrink: { noShrink: {
flexShrink: 0, flexShrink: 0,
}, },
agentDataItem: { agentDataItem: (theme) => ({
[theme.breakpoints.down("md")]: { [theme.breakpoints.down("md")]: {
flexDirection: "column", flexDirection: "column",
alignItems: "flex-start", alignItems: "flex-start",
gap: theme.spacing(1), gap: theme.spacing(1),
width: "fit-content", width: "fit-content",
}, },
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,6 +1,5 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import { makeStyles } from "@mui/styles";
import { combineClasses } from "utils/combineClasses";
import { WorkspaceAgent } from "api/typesGenerated"; import { WorkspaceAgent } from "api/typesGenerated";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import WarningRounded from "@mui/icons-material/WarningRounded"; import WarningRounded from "@mui/icons-material/WarningRounded";
@ -19,27 +18,23 @@ import Link from "@mui/material/Link";
// connected:shutdown_error, connected:off. // connected:shutdown_error, connected:off.
const ReadyLifecycle = () => { const ReadyLifecycle = () => {
const styles = useStyles();
return ( return (
<div <div
role="status" role="status"
data-testid="agent-status-ready" data-testid="agent-status-ready"
aria-label="Ready" aria-label="Ready"
className={combineClasses([styles.status, styles.connected])} css={[styles.status, styles.connected]}
/> />
); );
}; };
const StartingLifecycle: React.FC = () => { const StartingLifecycle: React.FC = () => {
const styles = useStyles();
return ( return (
<Tooltip title="Starting..."> <Tooltip title="Starting...">
<div <div
role="status" role="status"
aria-label="Starting..." aria-label="Starting..."
className={combineClasses([styles.status, styles.connecting])} css={[styles.status, styles.connecting]}
/> />
</Tooltip> </Tooltip>
); );
@ -48,7 +43,6 @@ const StartingLifecycle: React.FC = () => {
const StartTimeoutLifecycle: React.FC<{ const StartTimeoutLifecycle: React.FC<{
agent: WorkspaceAgent; agent: WorkspaceAgent;
}> = ({ agent }) => { }> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null); const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined; const id = isOpen ? "timeout-popover" : undefined;
@ -61,7 +55,7 @@ const StartTimeoutLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)} onMouseLeave={() => setIsOpen(false)}
role="status" role="status"
aria-label="Start timeout" aria-label="Start timeout"
className={styles.timeoutWarning} css={styles.timeoutWarning}
/> />
<HelpPopover <HelpPopover
id={id} id={id}
@ -90,7 +84,6 @@ const StartTimeoutLifecycle: React.FC<{
const StartErrorLifecycle: React.FC<{ const StartErrorLifecycle: React.FC<{
agent: WorkspaceAgent; agent: WorkspaceAgent;
}> = ({ agent }) => { }> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null); const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined; const id = isOpen ? "timeout-popover" : undefined;
@ -103,7 +96,7 @@ const StartErrorLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)} onMouseLeave={() => setIsOpen(false)}
role="status" role="status"
aria-label="Start error" aria-label="Start error"
className={styles.errorWarning} css={styles.errorWarning}
/> />
<HelpPopover <HelpPopover
id={id} id={id}
@ -130,14 +123,12 @@ const StartErrorLifecycle: React.FC<{
}; };
const ShuttingDownLifecycle: React.FC = () => { const ShuttingDownLifecycle: React.FC = () => {
const styles = useStyles();
return ( return (
<Tooltip title="Stopping..."> <Tooltip title="Stopping...">
<div <div
role="status" role="status"
aria-label="Stopping..." aria-label="Stopping..."
className={combineClasses([styles.status, styles.connecting])} css={[styles.status, styles.connecting]}
/> />
</Tooltip> </Tooltip>
); );
@ -146,7 +137,6 @@ const ShuttingDownLifecycle: React.FC = () => {
const ShutdownTimeoutLifecycle: React.FC<{ const ShutdownTimeoutLifecycle: React.FC<{
agent: WorkspaceAgent; agent: WorkspaceAgent;
}> = ({ agent }) => { }> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null); const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined; const id = isOpen ? "timeout-popover" : undefined;
@ -159,7 +149,7 @@ const ShutdownTimeoutLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)} onMouseLeave={() => setIsOpen(false)}
role="status" role="status"
aria-label="Stop timeout" aria-label="Stop timeout"
className={styles.timeoutWarning} css={styles.timeoutWarning}
/> />
<HelpPopover <HelpPopover
id={id} id={id}
@ -188,7 +178,6 @@ const ShutdownTimeoutLifecycle: React.FC<{
const ShutdownErrorLifecycle: React.FC<{ const ShutdownErrorLifecycle: React.FC<{
agent: WorkspaceAgent; agent: WorkspaceAgent;
}> = ({ agent }) => { }> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null); const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined; const id = isOpen ? "timeout-popover" : undefined;
@ -201,7 +190,7 @@ const ShutdownErrorLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)} onMouseLeave={() => setIsOpen(false)}
role="status" role="status"
aria-label="Stop error" aria-label="Stop error"
className={styles.errorWarning} css={styles.errorWarning}
/> />
<HelpPopover <HelpPopover
id={id} id={id}
@ -228,14 +217,12 @@ const ShutdownErrorLifecycle: React.FC<{
}; };
const OffLifecycle: React.FC = () => { const OffLifecycle: React.FC = () => {
const styles = useStyles();
return ( return (
<Tooltip title="Stopped"> <Tooltip title="Stopped">
<div <div
role="status" role="status"
aria-label="Stopped" aria-label="Stopped"
className={combineClasses([styles.status, styles.disconnected])} css={[styles.status, styles.disconnected]}
/> />
</Tooltip> </Tooltip>
); );
@ -280,28 +267,24 @@ const ConnectedStatus: React.FC<{
}; };
const DisconnectedStatus: React.FC = () => { const DisconnectedStatus: React.FC = () => {
const styles = useStyles();
return ( return (
<Tooltip title="Disconnected"> <Tooltip title="Disconnected">
<div <div
role="status" role="status"
aria-label="Disconnected" aria-label="Disconnected"
className={combineClasses([styles.status, styles.disconnected])} css={[styles.status, styles.disconnected]}
/> />
</Tooltip> </Tooltip>
); );
}; };
const ConnectingStatus: React.FC = () => { const ConnectingStatus: React.FC = () => {
const styles = useStyles();
return ( return (
<Tooltip title="Connecting..."> <Tooltip title="Connecting...">
<div <div
role="status" role="status"
aria-label="Connecting..." aria-label="Connecting..."
className={combineClasses([styles.status, styles.connecting])} css={[styles.status, styles.connecting]}
/> />
</Tooltip> </Tooltip>
); );
@ -310,7 +293,6 @@ const ConnectingStatus: React.FC = () => {
const TimeoutStatus: React.FC<{ const TimeoutStatus: React.FC<{
agent: WorkspaceAgent; agent: WorkspaceAgent;
}> = ({ agent }) => { }> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null); const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined; const id = isOpen ? "timeout-popover" : undefined;
@ -323,7 +305,7 @@ const TimeoutStatus: React.FC<{
onMouseLeave={() => setIsOpen(false)} onMouseLeave={() => setIsOpen(false)}
role="status" role="status"
aria-label="Timeout" aria-label="Timeout"
className={styles.timeoutWarning} css={styles.timeoutWarning}
/> />
<HelpPopover <HelpPopover
id={id} id={id}
@ -370,22 +352,22 @@ export const AgentStatus: React.FC<{
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
status: { status: (theme) => ({
width: theme.spacing(1), width: theme.spacing(1),
height: theme.spacing(1), height: theme.spacing(1),
borderRadius: "100%", borderRadius: "100%",
flexShrink: 0, flexShrink: 0,
}, }),
connected: { connected: (theme) => ({
backgroundColor: theme.palette.success.light, backgroundColor: theme.palette.success.light,
boxShadow: `0 0 12px 0 ${theme.palette.success.light}`, boxShadow: `0 0 12px 0 ${theme.palette.success.light}`,
}, }),
disconnected: { disconnected: (theme) => ({
backgroundColor: theme.palette.text.secondary, backgroundColor: theme.palette.text.secondary,
}, }),
"@keyframes pulse": { "@keyframes pulse": {
"0%": { "0%": {
@ -399,22 +381,22 @@ const useStyles = makeStyles((theme) => ({
}, },
}, },
connecting: { connecting: (theme) => ({
backgroundColor: theme.palette.info.light, backgroundColor: theme.palette.info.light,
animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite", animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite",
}, }),
timeoutWarning: { timeoutWarning: (theme) => ({
color: theme.palette.warning.light, color: theme.palette.warning.light,
width: theme.spacing(2), width: theme.spacing(2),
height: theme.spacing(2), height: theme.spacing(2),
position: "relative", position: "relative",
}, }),
errorWarning: { errorWarning: (theme) => ({
color: theme.palette.error.main, color: theme.palette.error.main,
width: theme.spacing(2), width: theme.spacing(2),
height: theme.spacing(2), height: theme.spacing(2),
position: "relative", position: "relative",
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,7 +1,6 @@
import { makeStyles } from "@mui/styles";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { FC } from "react"; import { type FC } from "react";
import * as TypesGen from "api/typesGenerated"; import type * as TypesGen from "api/typesGenerated";
import { BaseIcon } from "./BaseIcon"; import { BaseIcon } from "./BaseIcon";
import { ShareIcon } from "./ShareIcon"; import { ShareIcon } from "./ShareIcon";
@ -10,11 +9,22 @@ interface AppPreviewProps {
} }
export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => { export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
const styles = useStyles();
return ( return (
<Stack <Stack
className={styles.appPreviewLink} css={(theme) => ({
padding: theme.spacing(0.25, 1.5),
borderRadius: 9999,
border: `1px solid ${theme.palette.divider}`,
color: theme.palette.text.primary,
background: theme.palette.background.paper,
flexShrink: 0,
width: "fit-content",
fontSize: 12,
"& img, & svg": {
width: 13,
},
})}
alignItems="center" alignItems="center"
direction="row" direction="row"
spacing={1} spacing={1}
@ -25,20 +35,3 @@ export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
</Stack> </Stack>
); );
}; };
const useStyles = makeStyles((theme) => ({
appPreviewLink: {
padding: theme.spacing(0.25, 1.5),
borderRadius: 9999,
border: `1px solid ${theme.palette.divider}`,
color: theme.palette.text.primary,
background: theme.palette.background.paper,
flexShrink: 0,
width: "fit-content",
fontSize: 12,
"& img, & svg": {
width: 13,
},
},
}));

View File

@ -1,8 +1,8 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles"; import { type FC, useState } from "react";
import type { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow"; import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { FC, useState } from "react";
import { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
import { Stack } from "../Stack/Stack"; import { Stack } from "../Stack/Stack";
import { ResourceCard } from "./ResourceCard"; import { ResourceCard } from "./ResourceCard";
@ -19,7 +19,6 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
resources, resources,
agentRow, agentRow,
}) => { }) => {
const styles = useStyles();
const [shouldDisplayHideResources, setShouldDisplayHideResources] = const [shouldDisplayHideResources, setShouldDisplayHideResources] =
useState(false); useState(false);
const displayResources = shouldDisplayHideResources const displayResources = shouldDisplayHideResources
@ -40,9 +39,9 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
/> />
))} ))}
{hasHideResources && ( {hasHideResources && (
<div className={styles.buttonWrapper}> <div css={styles.buttonWrapper}>
<Button <Button
className={styles.showMoreButton} css={styles.showMoreButton}
size="small" size="small"
onClick={() => setShouldDisplayHideResources((v) => !v)} onClick={() => setShouldDisplayHideResources((v) => !v)}
> >
@ -55,17 +54,17 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
buttonWrapper: { buttonWrapper: (theme) => ({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
}, }),
showMoreButton: { showMoreButton: {
borderRadius: 9999, borderRadius: 9999,
width: "100%", width: "100%",
maxWidth: 260, maxWidth: 260,
}, },
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,7 +1,7 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip";
import FormHelperText from "@mui/material/FormHelperText"; import FormHelperText from "@mui/material/FormHelperText";
import { makeStyles } from "@mui/styles"; import { type FC } from "react";
import { FC } from "react";
export type MultiTextFieldProps = { export type MultiTextFieldProps = {
label: string; label: string;
@ -16,11 +16,9 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
values, values,
onChange, onChange,
}) => { }) => {
const styles = useStyles();
return ( return (
<div> <div>
<label className={styles.root}> <label css={styles.root}>
{values.map((value, index) => ( {values.map((value, index) => (
<Chip <Chip
key={index} key={index}
@ -34,7 +32,7 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
<input <input
id={id} id={id}
aria-label={label} aria-label={label}
className={styles.input} css={styles.input}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === ",") { if (event.key === ",") {
event.preventDefault(); event.preventDefault();
@ -71,8 +69,8 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
root: { root: (theme) => ({
border: `1px solid ${theme.palette.divider}`, border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
minHeight: theme.spacing(6), // Chip height + paddings minHeight: theme.spacing(6), // Chip height + paddings
@ -91,7 +89,7 @@ const useStyles = makeStyles((theme) => ({
top: -1, top: -1,
left: -1, left: -1,
}, },
}, }),
input: { input: {
flexGrow: 1, flexGrow: 1,
@ -104,4 +102,4 @@ const useStyles = makeStyles((theme) => ({
outline: "none", outline: "none",
}, },
}, },
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,11 +1,4 @@
import { makeStyles } from "@mui/styles"; import { type FC } from "react";
import { FC } from "react";
const useStyles = makeStyles((theme) => ({
root: {
marginTop: theme.spacing(3),
},
}));
/** /**
* SectionAction is a content box that call to actions should be placed * SectionAction is a content box that call to actions should be placed
@ -14,6 +7,13 @@ const useStyles = makeStyles((theme) => ({
export const SectionAction: FC<React.PropsWithChildren<unknown>> = ({ export const SectionAction: FC<React.PropsWithChildren<unknown>> = ({
children, children,
}) => { }) => {
const styles = useStyles(); return (
return <div className={styles.root}>{children}</div>; <div
css={(theme) => ({
marginTop: theme.spacing(3),
})}
>
{children}
</div>
);
}; };

View File

@ -1,16 +1,14 @@
import { makeStyles } from "@mui/styles"; import { type FC, Suspense } from "react";
import { Sidebar } from "./Sidebar"; import { Outlet } from "react-router-dom";
import { Stack } from "components/Stack/Stack";
import { FC, Suspense } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { Margins } from "../Margins/Margins";
import { useMe } from "hooks/useMe"; import { useMe } from "hooks/useMe";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { Outlet } from "react-router-dom"; import { Stack } from "components/Stack/Stack";
import { Margins } from "../Margins/Margins";
import { Sidebar } from "./Sidebar";
export const SettingsLayout: FC = () => { export const SettingsLayout: FC = () => {
const styles = useStyles();
const me = useMe(); const me = useMe();
return ( return (
@ -20,10 +18,21 @@ export const SettingsLayout: FC = () => {
</Helmet> </Helmet>
<Margins> <Margins>
<Stack className={styles.wrapper} direction="row" spacing={6}> <Stack
css={(theme) => ({
padding: theme.spacing(6, 0),
})}
direction="row"
spacing={6}
>
<Sidebar user={me} /> <Sidebar user={me} />
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<main className={styles.content}> <main
css={{
maxWidth: 800,
width: "100%",
}}
>
<Outlet /> <Outlet />
</main> </main>
</Suspense> </Suspense>
@ -32,14 +41,3 @@ export const SettingsLayout: FC = () => {
</> </>
); );
}; };
const useStyles = makeStyles((theme) => ({
wrapper: {
padding: theme.spacing(6, 0),
},
content: {
maxWidth: 800,
width: "100%",
},
}));

View File

@ -1,28 +1,73 @@
import { makeStyles } from "@mui/styles"; import { css } from "@emotion/css";
import {
type CSSObject,
type Interpolation,
type Theme,
useTheme,
} from "@emotion/react";
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined"; import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
import FingerprintOutlinedIcon from "@mui/icons-material/FingerprintOutlined"; import FingerprintOutlinedIcon from "@mui/icons-material/FingerprintOutlined";
import { User } from "api/typesGenerated"; import {
import { Stack } from "components/Stack/Stack"; type FC,
import { UserAvatar } from "components/UserAvatar/UserAvatar"; type ComponentType,
import { FC, ElementType, PropsWithChildren, ReactNode } from "react"; type PropsWithChildren,
type ReactNode,
} from "react";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { combineClasses } from "utils/combineClasses";
import AccountIcon from "@mui/icons-material/Person"; import AccountIcon from "@mui/icons-material/Person";
import ScheduleIcon from "@mui/icons-material/EditCalendarOutlined"; import ScheduleIcon from "@mui/icons-material/EditCalendarOutlined";
import SecurityIcon from "@mui/icons-material/LockOutlined"; import SecurityIcon from "@mui/icons-material/LockOutlined";
import type { User } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { useDashboard } from "components/Dashboard/DashboardProvider"; import { useDashboard } from "components/Dashboard/DashboardProvider";
import { combineClasses } from "utils/combineClasses";
const SidebarNavItem: FC< const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }> PropsWithChildren<{ href: string; icon: ReactNode }>
> = ({ children, href, icon }) => { > = ({ children, href, icon }) => {
const styles = useStyles(); const theme = useTheme();
const sidebarNavItemStyles = css`
color: inherit;
display: block;
font-size: 14px;
text-decoration: none;
padding: ${theme.spacing(1.5, 1.5, 1.5, 2)};
border-radius: ${theme.shape.borderRadius / 2}px;
transition: background-color 0.15s ease-in-out;
margin-bottom: 1px;
position: relative;
&:hover {
background-color: theme.palette.action.hover;
}
`;
const sidebarNavItemActiveStyles = css`
background-color: ${theme.palette.action.hover};
&:before {
content: "";
display: block;
width: 3px;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: ${theme.palette.secondary.dark};
border-top-left-radius: ${theme.shape.borderRadius};
border-bottom-left-radius: ${theme.shape.borderRadius};
}
`;
return ( return (
<NavLink <NavLink
to={href} to={href}
className={({ isActive }) => className={({ isActive }) =>
combineClasses([ combineClasses([
styles.sidebarNavItem, sidebarNavItemStyles,
isActive ? styles.sidebarNavItemActive : undefined, isActive ? sidebarNavItemActiveStyles : undefined,
]) ])
} }
> >
@ -34,26 +79,31 @@ const SidebarNavItem: FC<
); );
}; };
const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({ const SidebarNavItemIcon: React.FC<{
icon: Icon, icon: ComponentType<{ className?: string }>;
}) => { }> = ({ icon: Icon }) => {
const styles = useStyles(); return (
return <Icon className={styles.sidebarNavItemIcon} />; <Icon
css={(theme) => ({
width: theme.spacing(2),
height: theme.spacing(2),
})}
/>
);
}; };
export const Sidebar: React.FC<{ user: User }> = ({ user }) => { export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
const styles = useStyles();
const { entitlements } = useDashboard(); const { entitlements } = useDashboard();
const allowAutostopRequirement = const allowAutostopRequirement =
entitlements.features.template_autostop_requirement.enabled; entitlements.features.template_autostop_requirement.enabled;
return ( return (
<nav className={styles.sidebar}> <nav css={styles.sidebar}>
<Stack direction="row" alignItems="center" className={styles.userInfo}> <Stack direction="row" alignItems="center" css={styles.userInfo}>
<UserAvatar username={user.username} avatarURL={user.avatar_url} /> <UserAvatar username={user.username} avatarURL={user.avatar_url} />
<Stack spacing={0} className={styles.userData}> <Stack spacing={0} css={styles.userData}>
<span className={styles.username}>{user.username}</span> <span css={styles.username}>{user.username}</span>
<span className={styles.email}>{user.email}</span> <span css={styles.email}>{user.email}</span>
</Stack> </Stack>
</Stack> </Stack>
@ -93,50 +143,15 @@ export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
sidebar: { sidebar: {
width: 245, width: 245,
flexShrink: 0, flexShrink: 0,
}, },
sidebarNavItem: { userInfo: (theme) => ({
color: "inherit", ...(theme.typography.body2 as CSSObject),
display: "block",
fontSize: 14,
textDecoration: "none",
padding: theme.spacing(1.5, 1.5, 1.5, 2),
borderRadius: theme.shape.borderRadius / 2,
transition: "background-color 0.15s ease-in-out",
marginBottom: 1,
position: "relative",
"&:hover": {
backgroundColor: theme.palette.action.hover,
},
},
sidebarNavItemActive: {
backgroundColor: theme.palette.action.hover,
"&:before": {
content: '""',
display: "block",
width: 3,
height: "100%",
position: "absolute",
left: 0,
top: 0,
backgroundColor: theme.palette.secondary.dark,
borderTopLeftRadius: theme.shape.borderRadius,
borderBottomLeftRadius: theme.shape.borderRadius,
},
},
sidebarNavItemIcon: {
width: theme.spacing(2),
height: theme.spacing(2),
},
userInfo: {
...theme.typography.body2,
marginBottom: theme.spacing(2), marginBottom: theme.spacing(2),
}, }),
userData: { userData: {
overflow: "hidden", overflow: "hidden",
}, },
@ -146,10 +161,10 @@ const useStyles = makeStyles((theme) => ({
textOverflow: "ellipsis", textOverflow: "ellipsis",
whiteSpace: "nowrap", whiteSpace: "nowrap",
}, },
email: { email: (theme) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
fontSize: 12, fontSize: 12,
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,8 +1,7 @@
import { ComponentProps, FC } from "react"; import { type ComponentProps, type FC } from "react";
import Editor, { DiffEditor, loader } from "@monaco-editor/react"; import Editor, { DiffEditor, loader } from "@monaco-editor/react";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { useCoderTheme } from "./coderTheme"; import { useCoderTheme } from "./coderTheme";
import { makeStyles } from "@mui/styles";
loader.config({ monaco }); loader.config({ monaco });
@ -13,7 +12,6 @@ export const SyntaxHighlighter: FC<{
ComponentProps<typeof DiffEditor>; ComponentProps<typeof DiffEditor>;
compareWith?: string; compareWith?: string;
}> = ({ value, compareWith, language, editorProps }) => { }> = ({ value, compareWith, language, editorProps }) => {
const styles = useStyles();
const hasDiff = compareWith && value !== compareWith; const hasDiff = compareWith && value !== compareWith;
const coderTheme = useCoderTheme(); const coderTheme = useCoderTheme();
const commonProps = { const commonProps = {
@ -35,7 +33,13 @@ export const SyntaxHighlighter: FC<{
} }
return ( return (
<div className={styles.wrapper}> <div
css={(theme) => ({
padding: theme.spacing(1, 0),
background: theme.palette.background.paper,
height: "100%",
})}
>
{hasDiff ? ( {hasDiff ? (
<DiffEditor original={compareWith} modified={value} {...commonProps} /> <DiffEditor original={compareWith} modified={value} {...commonProps} />
) : ( ) : (
@ -44,11 +48,3 @@ export const SyntaxHighlighter: FC<{
</div> </div>
); );
}; };
const useStyles = makeStyles((theme) => ({
wrapper: {
padding: theme.spacing(1, 0),
background: theme.palette.background.paper,
height: "100%",
},
}));

View File

@ -1,8 +1,7 @@
import { makeStyles } from "@mui/styles"; import { type Interpolation, type Theme } from "@emotion/react";
import { TemplateExample } from "api/typesGenerated"; import type { TemplateExample } from "api/typesGenerated";
import { FC } from "react"; import { type FC } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { combineClasses } from "utils/combineClasses";
export interface TemplateExampleCardProps { export interface TemplateExampleCardProps {
example: TemplateExample; example: TemplateExample;
@ -13,29 +12,26 @@ export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
example, example,
className, className,
}) => { }) => {
const styles = useStyles();
return ( return (
<Link <Link
to={`/starter-templates/${example.id}`} to={`/starter-templates/${example.id}`}
className={combineClasses([styles.template, className])} css={styles.template}
className={className}
key={example.id} key={example.id}
> >
<div className={styles.templateIcon}> <div css={styles.templateIcon}>
<img src={example.icon} alt="" /> <img src={example.icon} alt="" />
</div> </div>
<div className={styles.templateInfo}> <div css={styles.templateInfo}>
<span className={styles.templateName}>{example.name}</span> <span css={styles.templateName}>{example.name}</span>
<span className={styles.templateDescription}> <span css={styles.templateDescription}>{example.description}</span>
{example.description}
</span>
</div> </div>
</Link> </Link>
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
template: { template: (theme) => ({
border: `1px solid ${theme.palette.divider}`, border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
background: theme.palette.background.paper, background: theme.palette.background.paper,
@ -49,9 +45,9 @@ const useStyles = makeStyles((theme) => ({
"&:hover": { "&:hover": {
backgroundColor: theme.palette.background.paperLight, backgroundColor: theme.palette.background.paperLight,
}, },
}, }),
templateIcon: { templateIcon: (theme) => ({
width: theme.spacing(12), width: theme.spacing(12),
height: theme.spacing(12), height: theme.spacing(12),
display: "flex", display: "flex",
@ -62,29 +58,29 @@ const useStyles = makeStyles((theme) => ({
"& img": { "& img": {
height: theme.spacing(4), height: theme.spacing(4),
}, },
}, }),
templateInfo: { templateInfo: (theme) => ({
padding: theme.spacing(2, 2, 2, 0), padding: theme.spacing(2, 2, 2, 0),
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
overflow: "hidden", overflow: "hidden",
}, }),
templateName: { templateName: (theme) => ({
fontSize: theme.spacing(2), fontSize: theme.spacing(2),
textOverflow: "ellipsis", textOverflow: "ellipsis",
width: "100%", width: "100%",
overflow: "hidden", overflow: "hidden",
whiteSpace: "nowrap", whiteSpace: "nowrap",
}, }),
templateDescription: { templateDescription: (theme) => ({
fontSize: theme.spacing(1.75), fontSize: theme.spacing(1.75),
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
textOverflow: "ellipsis", textOverflow: "ellipsis",
width: "100%", width: "100%",
overflow: "hidden", overflow: "hidden",
whiteSpace: "nowrap", whiteSpace: "nowrap",
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,11 +1,10 @@
import { makeStyles } from "@mui/styles"; import { type Interpolation, type Theme } from "@emotion/react";
import { type FC } from "react";
import { DockerIcon } from "components/Icons/DockerIcon"; import { DockerIcon } from "components/Icons/DockerIcon";
import { MarkdownIcon } from "components/Icons/MarkdownIcon"; import { MarkdownIcon } from "components/Icons/MarkdownIcon";
import { TerraformIcon } from "components/Icons/TerraformIcon"; import { TerraformIcon } from "components/Icons/TerraformIcon";
import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter"; import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter";
import { UseTabResult } from "hooks/useTab"; import { UseTabResult } from "hooks/useTab";
import { FC } from "react";
import { combineClasses } from "utils/combineClasses";
import { AllowedExtension, TemplateVersionFiles } from "utils/templateVersion"; import { AllowedExtension, TemplateVersionFiles } from "utils/templateVersion";
import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined"; import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined";
@ -43,15 +42,14 @@ export const TemplateFiles: FC<{
previousFiles?: TemplateVersionFiles; previousFiles?: TemplateVersionFiles;
tab: UseTabResult; tab: UseTabResult;
}> = ({ currentFiles, previousFiles, tab }) => { }> = ({ currentFiles, previousFiles, tab }) => {
const styles = useStyles();
const filenames = Object.keys(currentFiles); const filenames = Object.keys(currentFiles);
const selectedFilename = filenames[Number(tab.value)]; const selectedFilename = filenames[Number(tab.value)];
const currentFile = currentFiles[selectedFilename]; const currentFile = currentFiles[selectedFilename];
const previousFile = previousFiles && previousFiles[selectedFilename]; const previousFile = previousFiles && previousFiles[selectedFilename];
return ( return (
<div className={styles.files}> <div css={styles.files}>
<div className={styles.tabs}> <div css={styles.tabs}>
{filenames.map((filename, index) => { {filenames.map((filename, index) => {
const tabValue = index.toString(); const tabValue = index.toString();
const extension = getExtension(filename) as AllowedExtension; const extension = getExtension(filename) as AllowedExtension;
@ -63,10 +61,7 @@ export const TemplateFiles: FC<{
return ( return (
<button <button
className={combineClasses({ css={[styles.tab, tabValue === tab.value && styles.tabActive]}
[styles.tab]: true,
[styles.tabActive]: tabValue === tab.value,
})}
onClick={() => { onClick={() => {
tab.set(tabValue); tab.set(tabValue);
}} }}
@ -74,7 +69,7 @@ export const TemplateFiles: FC<{
> >
{icon} {icon}
{filename} {filename}
{hasDiff && <div className={styles.tabDiff} />} {hasDiff && <div css={styles.tabDiff} />}
</button> </button>
); );
})} })}
@ -92,16 +87,16 @@ export const TemplateFiles: FC<{
</div> </div>
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
tabs: { tabs: (theme) => ({
display: "flex", display: "flex",
alignItems: "baseline", alignItems: "baseline",
borderBottom: `1px solid ${theme.palette.divider}`, borderBottom: `1px solid ${theme.palette.divider}`,
gap: 1, gap: 1,
overflowX: "auto", overflowX: "auto",
}, }),
tab: { tab: (theme) => ({
background: "transparent", background: "transparent",
border: 0, border: 0,
padding: theme.spacing(0, 3), padding: theme.spacing(0, 3),
@ -123,9 +118,9 @@ const useStyles = makeStyles((theme) => ({
"&:hover": { "&:hover": {
backgroundColor: theme.palette.action.hover, backgroundColor: theme.palette.action.hover,
}, },
}, }),
tabActive: { tabActive: (theme) => ({
opacity: 1, opacity: 1,
background: theme.palette.action.hover, background: theme.palette.action.hover,
color: theme.palette.text.primary, color: theme.palette.text.primary,
@ -140,26 +135,26 @@ const useStyles = makeStyles((theme) => ({
backgroundColor: theme.palette.secondary.dark, backgroundColor: theme.palette.secondary.dark,
position: "absolute", position: "absolute",
}, },
}, }),
tabDiff: { tabDiff: (theme) => ({
height: 6, height: 6,
width: 6, width: 6,
backgroundColor: theme.palette.warning.light, backgroundColor: theme.palette.warning.light,
borderRadius: "100%", borderRadius: "100%",
marginLeft: theme.spacing(0.5), marginLeft: theme.spacing(0.5),
}, }),
codeWrapper: { codeWrapper: (theme) => ({
background: theme.palette.background.paperLight, background: theme.palette.background.paperLight,
}, }),
files: { files: (theme) => ({
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`, border: `1px solid ${theme.palette.divider}`,
}, }),
prism: { prism: {
borderRadius: 0, borderRadius: 0,
}, },
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,7 +1,5 @@
import { makeStyles } from "@mui/styles"; import TableRow, { type TableRowProps } from "@mui/material/TableRow";
import TableRow, { TableRowProps } from "@mui/material/TableRow";
import { type PropsWithChildren, forwardRef } from "react"; import { type PropsWithChildren, forwardRef } from "react";
import { combineClasses } from "utils/combineClasses";
type TimelineEntryProps = PropsWithChildren< type TimelineEntryProps = PropsWithChildren<
TableRowProps & { TableRowProps & {
@ -13,47 +11,39 @@ export const TimelineEntry = forwardRef(function TimelineEntry(
{ children, clickable = true, ...props }: TimelineEntryProps, { children, clickable = true, ...props }: TimelineEntryProps,
ref?: React.ForwardedRef<HTMLTableRowElement>, ref?: React.ForwardedRef<HTMLTableRowElement>,
) { ) {
const styles = useStyles();
return ( return (
<TableRow <TableRow
ref={ref} ref={ref}
className={combineClasses({ css={(theme) => [
[styles.timelineEntry]: true, {
[styles.clickable]: clickable, position: "relative",
})} "&:focus": {
outlineStyle: "solid",
outlineOffset: -1,
outlineWidth: 2,
outlineColor: theme.palette.secondary.dark,
},
"& td:before": {
position: "absolute",
left: 50,
display: "block",
content: "''",
height: "100%",
width: 2,
background: theme.palette.divider,
},
},
clickable && {
cursor: "pointer",
"&:hover": {
backgroundColor: theme.palette.action.hover,
},
},
]}
{...props} {...props}
> >
{children} {children}
</TableRow> </TableRow>
); );
}); });
const useStyles = makeStyles((theme) => ({
clickable: {
cursor: "pointer",
"&:hover": {
backgroundColor: theme.palette.action.hover,
},
},
timelineEntry: {
position: "relative",
"&:focus": {
outlineStyle: "solid",
outlineOffset: -1,
outlineWidth: 2,
outlineColor: theme.palette.secondary.dark,
},
"& td:before": {
position: "absolute",
left: 50,
display: "block",
content: "''",
height: "100%",
width: 2,
background: theme.palette.divider,
},
},
}));

View File

@ -1,21 +1,19 @@
import { makeStyles } from "@mui/styles"; import { type FC } from "react";
import { Template, TemplateExample } from "api/typesGenerated"; import type { Template, TemplateExample } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar"; import { Avatar } from "components/Avatar/Avatar";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { FC } from "react"; import { type Interpolation, type Theme } from "@emotion/react";
export interface SelectedTemplateProps { export interface SelectedTemplateProps {
template: Template | TemplateExample; template: Template | TemplateExample;
} }
export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => { export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
const styles = useStyles();
return ( return (
<Stack <Stack
direction="row" direction="row"
spacing={3} spacing={3}
className={styles.template} css={styles.template}
alignItems="center" alignItems="center"
> >
<Avatar <Avatar
@ -27,35 +25,33 @@ export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
</Avatar> </Avatar>
<Stack direction="column" spacing={0}> <Stack direction="column" spacing={0}>
<span className={styles.templateName}> <span css={styles.templateName}>
{"display_name" in template && template.display_name.length > 0 {"display_name" in template && template.display_name.length > 0
? template.display_name ? template.display_name
: template.name} : template.name}
</span> </span>
{template.description && ( {template.description && (
<span className={styles.templateDescription}> <span css={styles.templateDescription}>{template.description}</span>
{template.description}
</span>
)} )}
</Stack> </Stack>
</Stack> </Stack>
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
template: { template: (theme) => ({
padding: theme.spacing(2.5, 3), padding: theme.spacing(2.5, 3),
borderRadius: theme.shape.borderRadius, borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`, border: `1px solid ${theme.palette.divider}`,
}, }),
templateName: { templateName: {
fontSize: 16, fontSize: 16,
}, },
templateDescription: { templateDescription: (theme) => ({
fontSize: 14, fontSize: 14,
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,16 +1,15 @@
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import TableCell from "@mui/material/TableCell"; import TableCell from "@mui/material/TableCell";
import { TemplateVersion } from "api/typesGenerated"; import { useNavigate } from "react-router-dom";
import type { TemplateVersion } from "api/typesGenerated";
import { Pill } from "components/Pill/Pill"; import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { TimelineEntry } from "components/Timeline/TimelineEntry"; import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { UserAvatar } from "components/UserAvatar/UserAvatar"; import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip"; import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
import { useClickableTableRow } from "hooks/useClickableTableRow"; import { useClickableTableRow } from "hooks/useClickableTableRow";
import { useNavigate } from "react-router-dom";
import { colors } from "theme/colors"; import { colors } from "theme/colors";
import { combineClasses } from "utils/combineClasses";
export interface VersionRowProps { export interface VersionRowProps {
version: TemplateVersion; version: TemplateVersion;
@ -27,7 +26,6 @@ export const VersionRow: React.FC<VersionRowProps> = ({
onPromoteClick, onPromoteClick,
onArchiveClick, onArchiveClick,
}) => { }) => {
const styles = useStyles();
const navigate = useNavigate(); const navigate = useNavigate();
const clickableProps = useClickableTableRow({ const clickableProps = useClickableTableRow({
@ -40,17 +38,14 @@ export const VersionRow: React.FC<VersionRowProps> = ({
<TimelineEntry <TimelineEntry
data-testid={`version-${version.id}`} data-testid={`version-${version.id}`}
{...clickableProps} {...clickableProps}
className={combineClasses({ css={[styles.row, isActive && styles.active]}
[clickableProps.className]: true, className={clickableProps.className}
[styles.row]: true,
[styles.active]: isActive,
})}
> >
<TableCell className={styles.versionCell}> <TableCell css={styles.versionCell}>
<Stack <Stack
direction="row" direction="row"
alignItems="center" alignItems="center"
className={styles.versionWrapper} css={styles.versionWrapper}
justifyContent="space-between" justifyContent="space-between"
> >
<Stack direction="row" alignItems="center"> <Stack direction="row" alignItems="center">
@ -59,7 +54,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
avatarURL={version.created_by.avatar_url} avatarURL={version.created_by.avatar_url}
/> />
<Stack <Stack
className={styles.versionSummary} css={styles.versionSummary}
direction="row" direction="row"
alignItems="center" alignItems="center"
spacing={1} spacing={1}
@ -73,7 +68,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
<InfoTooltip title="Message" message={version.message} /> <InfoTooltip title="Message" message={version.message} />
)} )}
<span className={styles.versionTime}> <span css={styles.versionTime}>
{new Date(version.created_at).toLocaleTimeString()} {new Date(version.created_at).toLocaleTimeString()}
</span> </span>
</Stack> </Stack>
@ -94,7 +89,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
{jobStatus === "failed" && <Pill text="Failed" type="error" />} {jobStatus === "failed" && <Pill text="Failed" type="error" />}
{jobStatus === "failed" ? ( {jobStatus === "failed" ? (
<Button <Button
className={styles.promoteButton} css={styles.promoteButton}
disabled={isActive || version.archived} disabled={isActive || version.archived}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@ -108,7 +103,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
</Button> </Button>
) : ( ) : (
<Button <Button
className={styles.promoteButton} css={styles.promoteButton}
disabled={isActive || jobStatus !== "succeeded"} disabled={isActive || jobStatus !== "succeeded"}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@ -128,8 +123,8 @@ export const VersionRow: React.FC<VersionRowProps> = ({
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
row: { row: (theme) => ({
"&:hover $promoteButton": { "&:hover $promoteButton": {
color: theme.palette.text.primary, color: theme.palette.text.primary,
borderColor: colors.gray[11], borderColor: colors.gray[11],
@ -137,20 +132,20 @@ const useStyles = makeStyles((theme) => ({
borderColor: theme.palette.text.primary, borderColor: theme.palette.text.primary,
}, },
}, },
}, }),
promoteButton: { promoteButton: (theme) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
transition: "none", transition: "none",
}, }),
versionWrapper: { versionWrapper: (theme) => ({
padding: theme.spacing(2, 4), padding: theme.spacing(2, 4),
}, }),
active: { active: (theme) => ({
backgroundColor: theme.palette.background.paperLight, backgroundColor: theme.palette.background.paperLight,
}, }),
versionCell: { versionCell: {
padding: "0 !important", padding: "0 !important",
@ -158,13 +153,13 @@ const useStyles = makeStyles((theme) => ({
borderBottom: 0, borderBottom: 0,
}, },
versionSummary: { versionSummary: (theme) => ({
...theme.typography.body1, ...(theme.typography.body1 as CSSObject),
fontFamily: "inherit", fontFamily: "inherit",
}, }),
versionTime: { versionTime: (theme) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
fontSize: 12, fontSize: 12,
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,7 +1,8 @@
import { type Interpolation, type Theme } from "@emotion/react";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { Template, UpdateTemplateMeta } from "api/typesGenerated"; import type { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { FormikContextType, FormikTouched, useFormik } from "formik"; import { type FormikContextType, type FormikTouched, useFormik } from "formik";
import { FC } from "react"; import { type FC } from "react";
import { import {
getFormHelpers, getFormHelpers,
nameValidator, nameValidator,
@ -23,7 +24,6 @@ import {
HelpTooltip, HelpTooltip,
HelpTooltipText, HelpTooltipText,
} from "components/HelpTooltip/HelpTooltip"; } from "components/HelpTooltip/HelpTooltip";
import { makeStyles } from "@mui/styles";
const MAX_DESCRIPTION_CHAR_LIMIT = 128; const MAX_DESCRIPTION_CHAR_LIMIT = 128;
@ -79,7 +79,6 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
initialTouched, initialTouched,
}); });
const getFieldHelpers = getFormHelpers(form, error); const getFieldHelpers = getFormHelpers(form, error);
const styles = useStyles();
return ( return (
<HorizontalForm <HorizontalForm
@ -154,7 +153,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
direction="row" direction="row"
alignItems="center" alignItems="center"
spacing={0.5} spacing={0.5}
className={styles.optionText} css={styles.optionText}
> >
Allow users to cancel in-progress workspace jobs. Allow users to cancel in-progress workspace jobs.
<HelpTooltip> <HelpTooltip>
@ -163,7 +162,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
</HelpTooltipText> </HelpTooltipText>
</HelpTooltip> </HelpTooltip>
</Stack> </Stack>
<span className={styles.optionHelperText}> <span css={styles.optionHelperText}>
Depending on your template, canceling builds may leave Depending on your template, canceling builds may leave
workspaces in an unhealthy state. This option isn&apos;t workspaces in an unhealthy state. This option isn&apos;t
recommended for most use cases. recommended for most use cases.
@ -186,7 +185,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
direction="row" direction="row"
alignItems="center" alignItems="center"
spacing={0.5} spacing={0.5}
className={styles.optionText} css={styles.optionText}
> >
Require the active template version for workspace builds. Require the active template version for workspace builds.
<HelpTooltip> <HelpTooltip>
@ -195,7 +194,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
</HelpTooltipText> </HelpTooltipText>
</HelpTooltip> </HelpTooltip>
</Stack> </Stack>
<span className={styles.optionHelperText}> <span css={styles.optionHelperText}>
Workspaces that are manually started or auto-started will Workspaces that are manually started or auto-started will
use the promoted template version. use the promoted template version.
</span> </span>
@ -211,14 +210,14 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
optionText: { optionText: (theme) => ({
fontSize: theme.spacing(2), fontSize: theme.spacing(2),
color: theme.palette.text.primary, color: theme.palette.text.primary,
}, }),
optionHelperText: { optionHelperText: (theme) => ({
fontSize: theme.spacing(1.5), fontSize: theme.spacing(1.5),
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,18 +1,17 @@
import { makeStyles } from "@mui/styles"; import { createContext, type FC, Suspense, useContext } from "react";
import { Sidebar } from "./Sidebar";
import { Stack } from "components/Stack/Stack";
import { createContext, FC, Suspense, useContext } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useQuery } from "react-query";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { Stack } from "components/Stack/Stack";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { Outlet, useParams } from "react-router-dom"; import { Outlet, useParams } from "react-router-dom";
import { Margins } from "components/Margins/Margins"; import { Margins } from "components/Margins/Margins";
import { useQuery } from "react-query";
import { useOrganizationId } from "hooks/useOrganizationId"; import { useOrganizationId } from "hooks/useOrganizationId";
import { templateByName } from "api/queries/templates"; import { templateByName } from "api/queries/templates";
import { type AuthorizationResponse, type Template } from "api/typesGenerated"; import type { AuthorizationResponse, Template } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ErrorAlert } from "components/Alert/ErrorAlert";
import { checkAuthorization } from "api/queries/authCheck"; import { checkAuthorization } from "api/queries/authCheck";
import { Sidebar } from "./Sidebar";
const TemplateSettings = createContext< const TemplateSettings = createContext<
{ template: Template; permissions: AuthorizationResponse } | undefined { template: Template; permissions: AuthorizationResponse } | undefined
@ -28,7 +27,6 @@ export function useTemplateSettings() {
} }
export const TemplateSettingsLayout: FC = () => { export const TemplateSettingsLayout: FC = () => {
const styles = useStyles();
const orgId = useOrganizationId(); const orgId = useOrganizationId();
const { template: templateName } = useParams() as { template: string }; const { template: templateName } = useParams() as { template: string };
const templateQuery = useQuery(templateByName(orgId, templateName)); const templateQuery = useQuery(templateByName(orgId, templateName));
@ -58,7 +56,13 @@ export const TemplateSettingsLayout: FC = () => {
</Helmet> </Helmet>
<Margins> <Margins>
<Stack className={styles.wrapper} direction="row" spacing={10}> <Stack
css={(theme) => ({
padding: theme.spacing(6, 0),
})}
direction="row"
spacing={10}
>
{templateQuery.isError || permissionsQuery.isError ? ( {templateQuery.isError || permissionsQuery.isError ? (
<ErrorAlert error={templateQuery.error} /> <ErrorAlert error={templateQuery.error} />
) : ( ) : (
@ -70,7 +74,11 @@ export const TemplateSettingsLayout: FC = () => {
> >
<Sidebar template={templateQuery.data} /> <Sidebar template={templateQuery.data} />
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<main className={styles.content}> <main
css={{
width: "100%",
}}
>
<Outlet /> <Outlet />
</main> </main>
</Suspense> </Suspense>
@ -81,13 +89,3 @@ export const TemplateSettingsLayout: FC = () => {
</> </>
); );
}; };
const useStyles = makeStyles((theme) => ({
wrapper: {
padding: theme.spacing(6, 0),
},
content: {
width: "100%",
},
}));

View File

@ -1,11 +1,11 @@
import { makeStyles } from "@mui/styles";
import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import TreeView from "@mui/lab/TreeView"; import TreeView from "@mui/lab/TreeView";
import TreeItem from "@mui/lab/TreeItem"; import TreeItem from "@mui/lab/TreeItem";
import Menu from "@mui/material/Menu"; import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import { CSSProperties, FC, useState } from "react"; import { type CSSProperties, type FC, useState } from "react";
import { css } from "@emotion/react";
import { FileTree } from "utils/filetree"; import { FileTree } from "utils/filetree";
import { DockerIcon } from "components/Icons/DockerIcon"; import { DockerIcon } from "components/Icons/DockerIcon";
import { colors } from "theme/colors"; import { colors } from "theme/colors";
@ -35,7 +35,6 @@ export const FileTreeView: FC<{
fileTree: FileTree; fileTree: FileTree;
activePath?: string; activePath?: string;
}> = ({ fileTree, activePath, onDelete, onRename, onSelect }) => { }> = ({ fileTree, activePath, onDelete, onRename, onSelect }) => {
const styles = useStyles();
const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>(); const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>();
const buildTreeItems = ( const buildTreeItems = (
@ -60,9 +59,54 @@ export const FileTreeView: FC<{
nodeId={currentPath} nodeId={currentPath}
key={currentPath} key={currentPath}
label={filename} label={filename}
className={`${styles.fileTreeItem} ${ css={(theme) => css`
currentPath === activePath ? "active" : "" overflow: hidden;
}`} user-select: none;
&:focus:not(.active) > .MuiTreeItem-content {
background: ${theme.palette.action.hover};
color: ${theme.palette.text.primary};
}
&:not(:focus):not(.active) > .MuiTreeItem-content:hover {
background: ${theme.palette.action.hover};
color: ${theme.palette.text.primary};
}
& > .MuiTreeItem-content {
padding: ${theme.spacing(0.25, 2)};
color: ${theme.palette.text.secondary};
& svg {
width: 16px;
height: 16px;
}
& > .MuiTreeItem-label {
margin-left: 4px;
font-size: 13px;
color: inherit;
}
}
&.active {
& > .MuiTreeItem-content {
color: ${theme.palette.text.primary};
background: ${colors.gray[13]};
pointer-events: none;
}
}
& .MuiTreeItem-group {
margin-left: 0;
// We need to find a better way to recursive padding here
& .MuiTreeItem-content {
padding-left: calc(var(--level) * ${theme.spacing(5)});
}
}
`}
className={currentPath === activePath ? "active" : ""}
onClick={() => { onClick={() => {
onSelect(currentPath); onSelect(currentPath);
}} }}
@ -161,60 +205,6 @@ export const FileTreeView: FC<{
); );
}; };
const useStyles = makeStyles((theme) => ({
fileTreeItem: {
overflow: "hidden",
userSelect: "none",
"&:focus:not(.active) > .MuiTreeItem-content": {
background: theme.palette.action.hover,
color: theme.palette.text.primary,
},
"&:not(:focus):not(.active) > .MuiTreeItem-content:hover": {
background: theme.palette.action.hover,
color: theme.palette.text.primary,
},
"& > .MuiTreeItem-content": {
padding: theme.spacing(0.25, 2),
color: theme.palette.text.secondary,
"& svg": {
width: 16,
height: 16,
},
"& > .MuiTreeItem-label": {
marginLeft: 4,
fontSize: 13,
color: "inherit",
},
},
"&.active": {
"& > .MuiTreeItem-content": {
color: theme.palette.text.primary,
background: colors.gray[13],
pointerEvents: "none",
},
},
"& .MuiTreeItem-group": {
marginLeft: 0,
// We need to find a better way to recursive padding here
"& .MuiTreeItem-content": {
paddingLeft: `calc(var(--level) * ${theme.spacing(5)})`,
},
},
},
editor: {
flex: 1,
},
preview: {},
}));
const FileTypeTerraform = () => ( const FileTypeTerraform = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#813cf3"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#813cf3">
<title>file_type_terraform</title> <title>file_type_terraform</title>

View File

@ -1,12 +1,16 @@
import { makeStyles } from "@mui/styles"; import { css } from "@emotion/css";
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
import Dialog from "@mui/material/Dialog"; import Dialog from "@mui/material/Dialog";
import DialogContent from "@mui/material/DialogContent"; import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText"; import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle"; import DialogTitle from "@mui/material/DialogTitle";
import { DialogProps } from "components/Dialogs/Dialog"; import { type DialogProps } from "components/Dialogs/Dialog";
import { FC, useEffect, useState } from "react"; import { type FC, useEffect, useState } from "react";
import { FormFields, VerticalForm } from "components/Form/Form"; import { FormFields, VerticalForm } from "components/Form/Form";
import { TemplateVersionVariable, VariableValue } from "api/typesGenerated"; import type {
TemplateVersionVariable,
VariableValue,
} from "api/typesGenerated";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { VariableInput } from "pages/CreateTemplatePage/VariableInput"; import { VariableInput } from "pages/CreateTemplatePage/VariableInput";
@ -24,7 +28,7 @@ export type MissingTemplateVariablesDialogProps = Omit<
export const MissingTemplateVariablesDialog: FC< export const MissingTemplateVariablesDialog: FC<
MissingTemplateVariablesDialogProps MissingTemplateVariablesDialogProps
> = ({ missingVariables, onSubmit, ...dialogProps }) => { > = ({ missingVariables, onSubmit, ...dialogProps }) => {
const styles = useStyles(); const theme = useTheme();
const [variableValues, setVariableValues] = useState<VariableValue[]>([]); const [variableValues, setVariableValues] = useState<VariableValue[]>([]);
// Pre-fill the form with the default values when missing variables are loaded // Pre-fill the form with the default values when missing variables are loaded
@ -47,16 +51,25 @@ export const MissingTemplateVariablesDialog: FC<
> >
<DialogTitle <DialogTitle
id="update-build-parameters-title" id="update-build-parameters-title"
classes={{ root: styles.title }} classes={{
root: css`
padding: ${theme.spacing(3, 5)};
& h2 {
font-size: ${theme.spacing(2.5)};
font-weight: 400;
}
`,
}}
> >
Template variables Template variables
</DialogTitle> </DialogTitle>
<DialogContent className={styles.content}> <DialogContent css={styles.content}>
<DialogContentText className={styles.info}> <DialogContentText css={styles.info}>
There are a few missing template variable values. Please fill them in. There are a few missing template variable values. Please fill them in.
</DialogContentText> </DialogContentText>
<VerticalForm <VerticalForm
className={styles.form} css={styles.form}
id="updateVariables" id="updateVariables"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
@ -89,7 +102,7 @@ export const MissingTemplateVariablesDialog: FC<
)} )}
</VerticalForm> </VerticalForm>
</DialogContent> </DialogContent>
<DialogActions disableSpacing className={styles.dialogActions}> <DialogActions disableSpacing css={styles.dialogActions}>
<Button color="primary" fullWidth type="submit" form="updateVariables"> <Button color="primary" fullWidth type="submit" form="updateVariables">
Submit Submit
</Button> </Button>
@ -101,43 +114,22 @@ export const MissingTemplateVariablesDialog: FC<
); );
}; };
const useStyles = makeStyles((theme) => ({ const styles = {
title: { content: (theme) => ({
padding: theme.spacing(3, 5), padding: theme.spacing(0, 5),
}),
"& h2": {
fontSize: theme.spacing(2.5),
fontWeight: 400,
},
},
content: {
padding: theme.spacing(0, 5, 0, 5),
},
info: { info: {
margin: 0, margin: 0,
}, },
form: { form: (theme) => ({
paddingTop: theme.spacing(4), paddingTop: theme.spacing(4),
}, }),
infoTitle: { dialogActions: (theme) => ({
fontSize: theme.spacing(2),
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
formFooter: {
flexDirection: "column",
},
dialogActions: {
padding: theme.spacing(5), padding: theme.spacing(5),
flexDirection: "column", flexDirection: "column",
gap: theme.spacing(1), gap: theme.spacing(1),
}, }),
})); } satisfies Record<string, Interpolation<Theme>>;

View File

@ -5,7 +5,6 @@ import FormGroup from "@mui/material/FormGroup";
import FormHelperText from "@mui/material/FormHelperText"; import FormHelperText from "@mui/material/FormHelperText";
import FormLabel from "@mui/material/FormLabel"; import FormLabel from "@mui/material/FormLabel";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import makeStyles from "@mui/styles/makeStyles";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { import {
@ -200,8 +199,6 @@ export const WorkspaceScheduleForm: FC<
enableAutoStop, enableAutoStop,
enableAutoStart, enableAutoStart,
}) => { }) => {
const styles = useStyles();
const form = useFormik<WorkspaceScheduleFormValues>({ const form = useFormik<WorkspaceScheduleFormValues>({
initialValues, initialValues,
onSubmit, onSubmit,
@ -340,11 +337,23 @@ export const WorkspaceScheduleForm: FC<
</Stack> </Stack>
<FormControl component="fieldset" error={Boolean(form.errors.monday)}> <FormControl component="fieldset" error={Boolean(form.errors.monday)}>
<FormLabel className={styles.daysOfWeekLabel} component="legend"> <FormLabel
css={{
fontSize: 12,
}}
component="legend"
>
{Language.daysOfWeekLabel} {Language.daysOfWeekLabel}
</FormLabel> </FormLabel>
<FormGroup className={styles.daysOfWeekOptions}> <FormGroup
css={(theme) => ({
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
paddingTop: theme.spacing(0.5),
})}
>
{checkboxes.map((checkbox) => ( {checkboxes.map((checkbox) => (
<FormControlLabel <FormControlLabel
control={ control={
@ -422,15 +431,3 @@ export const ttlShutdownAt = (formTTL: number): string => {
.humanize()} ${Language.ttlCausesShutdownAfterStart}.`; .humanize()} ${Language.ttlCausesShutdownAfterStart}.`;
} }
}; };
const useStyles = makeStyles((theme) => ({
daysOfWeekLabel: {
fontSize: 12,
},
daysOfWeekOptions: {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
paddingTop: theme.spacing(0.5),
},
}));