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

View File

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

View File

@ -1,9 +1,8 @@
import { makeStyles } from "@mui/styles";
import { AppPreviewLink } from "components/Resources/AppLink/AppPreviewLink";
import { FC } from "react";
import { combineClasses } from "utils/combineClasses";
import { WorkspaceAgent } from "api/typesGenerated";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FC } from "react";
import type { WorkspaceAgent } from "api/typesGenerated";
import { Stack } from "../Stack/Stack";
import { AppPreviewLink } from "./AppLink/AppPreviewLink";
interface AgentRowPreviewStyles {
// Helpful when there are more than one row so the values are aligned
@ -18,57 +17,58 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
agent,
alignValues,
}) => {
const styles = useStyles({ alignValues });
return (
<Stack
key={agent.id}
direction="row"
alignItems="center"
justifyContent="space-between"
className={styles.agentRow}
css={styles.agentRow}
>
<Stack direction="row" alignItems="baseline">
<div className={styles.agentStatusWrapper}>
<div className={styles.agentStatusPreview}></div>
<div css={styles.agentStatusWrapper}>
<div css={styles.agentStatusPreview}></div>
</div>
<Stack
alignItems="baseline"
direction="row"
spacing={4}
className={styles.agentData}
css={styles.agentData}
>
<Stack
direction="row"
alignItems="baseline"
spacing={1}
className={combineClasses([
css={[
styles.noShrink,
styles.agentDataItem,
styles.agentDataName,
])}
(theme) => ({
[theme.breakpoints.up("sm")]: {
minWidth: alignValues ? 240 : undefined,
},
}),
]}
>
<span>Agent:</span>
<span className={styles.agentDataValue}>{agent.name}</span>
<span css={styles.agentDataValue}>{agent.name}</span>
</Stack>
<Stack
direction="row"
alignItems="baseline"
spacing={1}
className={combineClasses([
css={[
styles.noShrink,
styles.agentDataItem,
styles.agentDataOS,
])}
(theme) => ({
[theme.breakpoints.up("sm")]: {
minWidth: alignValues ? 100 : undefined,
},
}),
]}
>
<span>OS:</span>
<span
className={combineClasses([
styles.agentDataValue,
styles.agentOS,
])}
>
<span css={[styles.agentDataValue, styles.agentOS]}>
{agent.operating_system}
</span>
</Stack>
@ -77,7 +77,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
direction="row"
alignItems="center"
spacing={1}
className={styles.agentDataItem}
css={styles.agentDataItem}
>
<span>Apps:</span>
<Stack
@ -90,7 +90,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
<AppPreviewLink key={app.slug} app={app} />
))}
{agent.apps.length === 0 && (
<span className={styles.agentDataValue}>None</span>
<span css={styles.agentDataValue}>None</span>
)}
</Stack>
</Stack>
@ -100,8 +100,8 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
agentRow: {
const styles = {
agentRow: (theme) => ({
padding: theme.spacing(2, 4),
backgroundColor: theme.palette.background.paperLight,
fontSize: 16,
@ -120,16 +120,16 @@ const useStyles = makeStyles((theme) => ({
top: 0,
left: 49,
},
},
}),
agentStatusWrapper: {
agentStatusWrapper: (theme) => ({
width: theme.spacing(4.5),
display: "flex",
justifyContent: "center",
flexShrink: 0,
},
}),
agentStatusPreview: {
agentStatusPreview: (theme) => ({
width: 10,
height: 10,
border: `2px solid ${theme.palette.text.secondary}`,
@ -137,7 +137,7 @@ const useStyles = makeStyles((theme) => ({
position: "relative",
zIndex: 1,
background: theme.palette.background.paper,
},
}),
agentName: {
fontWeight: 600,
@ -146,10 +146,9 @@ const useStyles = makeStyles((theme) => ({
agentOS: {
textTransform: "capitalize",
fontSize: 14,
color: theme.palette.text.secondary,
},
agentData: {
agentData: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
@ -157,36 +156,22 @@ const useStyles = makeStyles((theme) => ({
gap: theme.spacing(2),
flexWrap: "wrap",
},
},
}),
agentDataName: {
[theme.breakpoints.up("sm")]: {
minWidth: ({ alignValues }: AgentRowPreviewStyles) =>
alignValues ? 240 : undefined,
},
},
agentDataOS: {
[theme.breakpoints.up("sm")]: {
minWidth: ({ alignValues }: AgentRowPreviewStyles) =>
alignValues ? 100 : undefined,
},
},
agentDataValue: {
agentDataValue: (theme) => ({
color: theme.palette.text.primary,
},
}),
noShrink: {
flexShrink: 0,
},
agentDataItem: {
agentDataItem: (theme) => ({
[theme.breakpoints.down("md")]: {
flexDirection: "column",
alignItems: "flex-start",
gap: theme.spacing(1),
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 { makeStyles } from "@mui/styles";
import { combineClasses } from "utils/combineClasses";
import { WorkspaceAgent } from "api/typesGenerated";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import WarningRounded from "@mui/icons-material/WarningRounded";
@ -19,27 +18,23 @@ import Link from "@mui/material/Link";
// connected:shutdown_error, connected:off.
const ReadyLifecycle = () => {
const styles = useStyles();
return (
<div
role="status"
data-testid="agent-status-ready"
aria-label="Ready"
className={combineClasses([styles.status, styles.connected])}
css={[styles.status, styles.connected]}
/>
);
};
const StartingLifecycle: React.FC = () => {
const styles = useStyles();
return (
<Tooltip title="Starting...">
<div
role="status"
aria-label="Starting..."
className={combineClasses([styles.status, styles.connecting])}
css={[styles.status, styles.connecting]}
/>
</Tooltip>
);
@ -48,7 +43,6 @@ const StartingLifecycle: React.FC = () => {
const StartTimeoutLifecycle: React.FC<{
agent: WorkspaceAgent;
}> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined;
@ -61,7 +55,7 @@ const StartTimeoutLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)}
role="status"
aria-label="Start timeout"
className={styles.timeoutWarning}
css={styles.timeoutWarning}
/>
<HelpPopover
id={id}
@ -90,7 +84,6 @@ const StartTimeoutLifecycle: React.FC<{
const StartErrorLifecycle: React.FC<{
agent: WorkspaceAgent;
}> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined;
@ -103,7 +96,7 @@ const StartErrorLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)}
role="status"
aria-label="Start error"
className={styles.errorWarning}
css={styles.errorWarning}
/>
<HelpPopover
id={id}
@ -130,14 +123,12 @@ const StartErrorLifecycle: React.FC<{
};
const ShuttingDownLifecycle: React.FC = () => {
const styles = useStyles();
return (
<Tooltip title="Stopping...">
<div
role="status"
aria-label="Stopping..."
className={combineClasses([styles.status, styles.connecting])}
css={[styles.status, styles.connecting]}
/>
</Tooltip>
);
@ -146,7 +137,6 @@ const ShuttingDownLifecycle: React.FC = () => {
const ShutdownTimeoutLifecycle: React.FC<{
agent: WorkspaceAgent;
}> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined;
@ -159,7 +149,7 @@ const ShutdownTimeoutLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)}
role="status"
aria-label="Stop timeout"
className={styles.timeoutWarning}
css={styles.timeoutWarning}
/>
<HelpPopover
id={id}
@ -188,7 +178,6 @@ const ShutdownTimeoutLifecycle: React.FC<{
const ShutdownErrorLifecycle: React.FC<{
agent: WorkspaceAgent;
}> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined;
@ -201,7 +190,7 @@ const ShutdownErrorLifecycle: React.FC<{
onMouseLeave={() => setIsOpen(false)}
role="status"
aria-label="Stop error"
className={styles.errorWarning}
css={styles.errorWarning}
/>
<HelpPopover
id={id}
@ -228,14 +217,12 @@ const ShutdownErrorLifecycle: React.FC<{
};
const OffLifecycle: React.FC = () => {
const styles = useStyles();
return (
<Tooltip title="Stopped">
<div
role="status"
aria-label="Stopped"
className={combineClasses([styles.status, styles.disconnected])}
css={[styles.status, styles.disconnected]}
/>
</Tooltip>
);
@ -280,28 +267,24 @@ const ConnectedStatus: React.FC<{
};
const DisconnectedStatus: React.FC = () => {
const styles = useStyles();
return (
<Tooltip title="Disconnected">
<div
role="status"
aria-label="Disconnected"
className={combineClasses([styles.status, styles.disconnected])}
css={[styles.status, styles.disconnected]}
/>
</Tooltip>
);
};
const ConnectingStatus: React.FC = () => {
const styles = useStyles();
return (
<Tooltip title="Connecting...">
<div
role="status"
aria-label="Connecting..."
className={combineClasses([styles.status, styles.connecting])}
css={[styles.status, styles.connecting]}
/>
</Tooltip>
);
@ -310,7 +293,6 @@ const ConnectingStatus: React.FC = () => {
const TimeoutStatus: React.FC<{
agent: WorkspaceAgent;
}> = ({ agent }) => {
const styles = useStyles();
const anchorRef = useRef<SVGSVGElement>(null);
const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "timeout-popover" : undefined;
@ -323,7 +305,7 @@ const TimeoutStatus: React.FC<{
onMouseLeave={() => setIsOpen(false)}
role="status"
aria-label="Timeout"
className={styles.timeoutWarning}
css={styles.timeoutWarning}
/>
<HelpPopover
id={id}
@ -370,22 +352,22 @@ export const AgentStatus: React.FC<{
);
};
const useStyles = makeStyles((theme) => ({
status: {
const styles = {
status: (theme) => ({
width: theme.spacing(1),
height: theme.spacing(1),
borderRadius: "100%",
flexShrink: 0,
},
}),
connected: {
connected: (theme) => ({
backgroundColor: theme.palette.success.light,
boxShadow: `0 0 12px 0 ${theme.palette.success.light}`,
},
}),
disconnected: {
disconnected: (theme) => ({
backgroundColor: theme.palette.text.secondary,
},
}),
"@keyframes pulse": {
"0%": {
@ -399,22 +381,22 @@ const useStyles = makeStyles((theme) => ({
},
},
connecting: {
connecting: (theme) => ({
backgroundColor: theme.palette.info.light,
animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite",
},
}),
timeoutWarning: {
timeoutWarning: (theme) => ({
color: theme.palette.warning.light,
width: theme.spacing(2),
height: theme.spacing(2),
position: "relative",
},
}),
errorWarning: {
errorWarning: (theme) => ({
color: theme.palette.error.main,
width: theme.spacing(2),
height: theme.spacing(2),
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 { FC } from "react";
import * as TypesGen from "api/typesGenerated";
import { type FC } from "react";
import type * as TypesGen from "api/typesGenerated";
import { BaseIcon } from "./BaseIcon";
import { ShareIcon } from "./ShareIcon";
@ -10,11 +9,22 @@ interface AppPreviewProps {
}
export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
const styles = useStyles();
return (
<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"
direction="row"
spacing={1}
@ -25,20 +35,3 @@ export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
</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 { makeStyles } from "@mui/styles";
import { type FC, useState } from "react";
import type { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { FC, useState } from "react";
import { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
import { Stack } from "../Stack/Stack";
import { ResourceCard } from "./ResourceCard";
@ -19,7 +19,6 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
resources,
agentRow,
}) => {
const styles = useStyles();
const [shouldDisplayHideResources, setShouldDisplayHideResources] =
useState(false);
const displayResources = shouldDisplayHideResources
@ -40,9 +39,9 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
/>
))}
{hasHideResources && (
<div className={styles.buttonWrapper}>
<div css={styles.buttonWrapper}>
<Button
className={styles.showMoreButton}
css={styles.showMoreButton}
size="small"
onClick={() => setShouldDisplayHideResources((v) => !v)}
>
@ -55,17 +54,17 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
);
};
const useStyles = makeStyles((theme) => ({
buttonWrapper: {
const styles = {
buttonWrapper: (theme) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
marginTop: theme.spacing(2),
},
}),
showMoreButton: {
borderRadius: 9999,
width: "100%",
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 FormHelperText from "@mui/material/FormHelperText";
import { makeStyles } from "@mui/styles";
import { FC } from "react";
import { type FC } from "react";
export type MultiTextFieldProps = {
label: string;
@ -16,11 +16,9 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
values,
onChange,
}) => {
const styles = useStyles();
return (
<div>
<label className={styles.root}>
<label css={styles.root}>
{values.map((value, index) => (
<Chip
key={index}
@ -34,7 +32,7 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
<input
id={id}
aria-label={label}
className={styles.input}
css={styles.input}
onKeyDown={(event) => {
if (event.key === ",") {
event.preventDefault();
@ -71,8 +69,8 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
root: {
const styles = {
root: (theme) => ({
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
minHeight: theme.spacing(6), // Chip height + paddings
@ -91,7 +89,7 @@ const useStyles = makeStyles((theme) => ({
top: -1,
left: -1,
},
},
}),
input: {
flexGrow: 1,
@ -104,4 +102,4 @@ const useStyles = makeStyles((theme) => ({
outline: "none",
},
},
}));
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,11 +1,4 @@
import { makeStyles } from "@mui/styles";
import { FC } from "react";
const useStyles = makeStyles((theme) => ({
root: {
marginTop: theme.spacing(3),
},
}));
import { type FC } from "react";
/**
* 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>> = ({
children,
}) => {
const styles = useStyles();
return <div className={styles.root}>{children}</div>;
return (
<div
css={(theme) => ({
marginTop: theme.spacing(3),
})}
>
{children}
</div>
);
};

View File

@ -1,16 +1,14 @@
import { makeStyles } from "@mui/styles";
import { Sidebar } from "./Sidebar";
import { Stack } from "components/Stack/Stack";
import { FC, Suspense } from "react";
import { type FC, Suspense } from "react";
import { Outlet } from "react-router-dom";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { Margins } from "../Margins/Margins";
import { useMe } from "hooks/useMe";
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 = () => {
const styles = useStyles();
const me = useMe();
return (
@ -20,10 +18,21 @@ export const SettingsLayout: FC = () => {
</Helmet>
<Margins>
<Stack className={styles.wrapper} direction="row" spacing={6}>
<Stack
css={(theme) => ({
padding: theme.spacing(6, 0),
})}
direction="row"
spacing={6}
>
<Sidebar user={me} />
<Suspense fallback={<Loader />}>
<main className={styles.content}>
<main
css={{
maxWidth: 800,
width: "100%",
}}
>
<Outlet />
</main>
</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 FingerprintOutlinedIcon from "@mui/icons-material/FingerprintOutlined";
import { User } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { FC, ElementType, PropsWithChildren, ReactNode } from "react";
import {
type FC,
type ComponentType,
type PropsWithChildren,
type ReactNode,
} from "react";
import { NavLink } from "react-router-dom";
import { combineClasses } from "utils/combineClasses";
import AccountIcon from "@mui/icons-material/Person";
import ScheduleIcon from "@mui/icons-material/EditCalendarOutlined";
import SecurityIcon from "@mui/icons-material/LockOutlined";
import type { User } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { combineClasses } from "utils/combineClasses";
const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }>
> = ({ children, href, icon }) => {
const 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 (
<NavLink
to={href}
className={({ isActive }) =>
combineClasses([
styles.sidebarNavItem,
isActive ? styles.sidebarNavItemActive : undefined,
sidebarNavItemStyles,
isActive ? sidebarNavItemActiveStyles : undefined,
])
}
>
@ -34,26 +79,31 @@ const SidebarNavItem: FC<
);
};
const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({
icon: Icon,
}) => {
const styles = useStyles();
return <Icon className={styles.sidebarNavItemIcon} />;
const SidebarNavItemIcon: React.FC<{
icon: ComponentType<{ className?: string }>;
}> = ({ icon: Icon }) => {
return (
<Icon
css={(theme) => ({
width: theme.spacing(2),
height: theme.spacing(2),
})}
/>
);
};
export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
const styles = useStyles();
const { entitlements } = useDashboard();
const allowAutostopRequirement =
entitlements.features.template_autostop_requirement.enabled;
return (
<nav className={styles.sidebar}>
<Stack direction="row" alignItems="center" className={styles.userInfo}>
<nav css={styles.sidebar}>
<Stack direction="row" alignItems="center" css={styles.userInfo}>
<UserAvatar username={user.username} avatarURL={user.avatar_url} />
<Stack spacing={0} className={styles.userData}>
<span className={styles.username}>{user.username}</span>
<span className={styles.email}>{user.email}</span>
<Stack spacing={0} css={styles.userData}>
<span css={styles.username}>{user.username}</span>
<span css={styles.email}>{user.email}</span>
</Stack>
</Stack>
@ -93,50 +143,15 @@ export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
sidebar: {
width: 245,
flexShrink: 0,
},
sidebarNavItem: {
color: "inherit",
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,
userInfo: (theme) => ({
...(theme.typography.body2 as CSSObject),
marginBottom: theme.spacing(2),
},
}),
userData: {
overflow: "hidden",
},
@ -146,10 +161,10 @@ const useStyles = makeStyles((theme) => ({
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
email: {
email: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 12,
overflow: "hidden",
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 * as monaco from "monaco-editor";
import { useCoderTheme } from "./coderTheme";
import { makeStyles } from "@mui/styles";
loader.config({ monaco });
@ -13,7 +12,6 @@ export const SyntaxHighlighter: FC<{
ComponentProps<typeof DiffEditor>;
compareWith?: string;
}> = ({ value, compareWith, language, editorProps }) => {
const styles = useStyles();
const hasDiff = compareWith && value !== compareWith;
const coderTheme = useCoderTheme();
const commonProps = {
@ -35,7 +33,13 @@ export const SyntaxHighlighter: FC<{
}
return (
<div className={styles.wrapper}>
<div
css={(theme) => ({
padding: theme.spacing(1, 0),
background: theme.palette.background.paper,
height: "100%",
})}
>
{hasDiff ? (
<DiffEditor original={compareWith} modified={value} {...commonProps} />
) : (
@ -44,11 +48,3 @@ export const SyntaxHighlighter: FC<{
</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 { TemplateExample } from "api/typesGenerated";
import { FC } from "react";
import { type Interpolation, type Theme } from "@emotion/react";
import type { TemplateExample } from "api/typesGenerated";
import { type FC } from "react";
import { Link } from "react-router-dom";
import { combineClasses } from "utils/combineClasses";
export interface TemplateExampleCardProps {
example: TemplateExample;
@ -13,29 +12,26 @@ export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
example,
className,
}) => {
const styles = useStyles();
return (
<Link
to={`/starter-templates/${example.id}`}
className={combineClasses([styles.template, className])}
css={styles.template}
className={className}
key={example.id}
>
<div className={styles.templateIcon}>
<div css={styles.templateIcon}>
<img src={example.icon} alt="" />
</div>
<div className={styles.templateInfo}>
<span className={styles.templateName}>{example.name}</span>
<span className={styles.templateDescription}>
{example.description}
</span>
<div css={styles.templateInfo}>
<span css={styles.templateName}>{example.name}</span>
<span css={styles.templateDescription}>{example.description}</span>
</div>
</Link>
);
};
const useStyles = makeStyles((theme) => ({
template: {
const styles = {
template: (theme) => ({
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
background: theme.palette.background.paper,
@ -49,9 +45,9 @@ const useStyles = makeStyles((theme) => ({
"&:hover": {
backgroundColor: theme.palette.background.paperLight,
},
},
}),
templateIcon: {
templateIcon: (theme) => ({
width: theme.spacing(12),
height: theme.spacing(12),
display: "flex",
@ -62,29 +58,29 @@ const useStyles = makeStyles((theme) => ({
"& img": {
height: theme.spacing(4),
},
},
}),
templateInfo: {
templateInfo: (theme) => ({
padding: theme.spacing(2, 2, 2, 0),
display: "flex",
flexDirection: "column",
overflow: "hidden",
},
}),
templateName: {
templateName: (theme) => ({
fontSize: theme.spacing(2),
textOverflow: "ellipsis",
width: "100%",
overflow: "hidden",
whiteSpace: "nowrap",
},
}),
templateDescription: {
templateDescription: (theme) => ({
fontSize: theme.spacing(1.75),
color: theme.palette.text.secondary,
textOverflow: "ellipsis",
width: "100%",
overflow: "hidden",
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 { MarkdownIcon } from "components/Icons/MarkdownIcon";
import { TerraformIcon } from "components/Icons/TerraformIcon";
import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter";
import { UseTabResult } from "hooks/useTab";
import { FC } from "react";
import { combineClasses } from "utils/combineClasses";
import { AllowedExtension, TemplateVersionFiles } from "utils/templateVersion";
import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined";
@ -43,15 +42,14 @@ export const TemplateFiles: FC<{
previousFiles?: TemplateVersionFiles;
tab: UseTabResult;
}> = ({ currentFiles, previousFiles, tab }) => {
const styles = useStyles();
const filenames = Object.keys(currentFiles);
const selectedFilename = filenames[Number(tab.value)];
const currentFile = currentFiles[selectedFilename];
const previousFile = previousFiles && previousFiles[selectedFilename];
return (
<div className={styles.files}>
<div className={styles.tabs}>
<div css={styles.files}>
<div css={styles.tabs}>
{filenames.map((filename, index) => {
const tabValue = index.toString();
const extension = getExtension(filename) as AllowedExtension;
@ -63,10 +61,7 @@ export const TemplateFiles: FC<{
return (
<button
className={combineClasses({
[styles.tab]: true,
[styles.tabActive]: tabValue === tab.value,
})}
css={[styles.tab, tabValue === tab.value && styles.tabActive]}
onClick={() => {
tab.set(tabValue);
}}
@ -74,7 +69,7 @@ export const TemplateFiles: FC<{
>
{icon}
{filename}
{hasDiff && <div className={styles.tabDiff} />}
{hasDiff && <div css={styles.tabDiff} />}
</button>
);
})}
@ -92,16 +87,16 @@ export const TemplateFiles: FC<{
</div>
);
};
const useStyles = makeStyles((theme) => ({
tabs: {
const styles = {
tabs: (theme) => ({
display: "flex",
alignItems: "baseline",
borderBottom: `1px solid ${theme.palette.divider}`,
gap: 1,
overflowX: "auto",
},
}),
tab: {
tab: (theme) => ({
background: "transparent",
border: 0,
padding: theme.spacing(0, 3),
@ -123,9 +118,9 @@ const useStyles = makeStyles((theme) => ({
"&:hover": {
backgroundColor: theme.palette.action.hover,
},
},
}),
tabActive: {
tabActive: (theme) => ({
opacity: 1,
background: theme.palette.action.hover,
color: theme.palette.text.primary,
@ -140,26 +135,26 @@ const useStyles = makeStyles((theme) => ({
backgroundColor: theme.palette.secondary.dark,
position: "absolute",
},
},
}),
tabDiff: {
tabDiff: (theme) => ({
height: 6,
width: 6,
backgroundColor: theme.palette.warning.light,
borderRadius: "100%",
marginLeft: theme.spacing(0.5),
},
}),
codeWrapper: {
codeWrapper: (theme) => ({
background: theme.palette.background.paperLight,
},
}),
files: {
files: (theme) => ({
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
},
}),
prism: {
borderRadius: 0,
},
}));
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,7 +1,5 @@
import { makeStyles } from "@mui/styles";
import TableRow, { TableRowProps } from "@mui/material/TableRow";
import TableRow, { type TableRowProps } from "@mui/material/TableRow";
import { type PropsWithChildren, forwardRef } from "react";
import { combineClasses } from "utils/combineClasses";
type TimelineEntryProps = PropsWithChildren<
TableRowProps & {
@ -13,47 +11,39 @@ export const TimelineEntry = forwardRef(function TimelineEntry(
{ children, clickable = true, ...props }: TimelineEntryProps,
ref?: React.ForwardedRef<HTMLTableRowElement>,
) {
const styles = useStyles();
return (
<TableRow
ref={ref}
className={combineClasses({
[styles.timelineEntry]: true,
[styles.clickable]: clickable,
})}
css={(theme) => [
{
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}
>
{children}
</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 { Template, TemplateExample } from "api/typesGenerated";
import { type FC } from "react";
import type { Template, TemplateExample } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { Stack } from "components/Stack/Stack";
import { FC } from "react";
import { type Interpolation, type Theme } from "@emotion/react";
export interface SelectedTemplateProps {
template: Template | TemplateExample;
}
export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
const styles = useStyles();
return (
<Stack
direction="row"
spacing={3}
className={styles.template}
css={styles.template}
alignItems="center"
>
<Avatar
@ -27,35 +25,33 @@ export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
</Avatar>
<Stack direction="column" spacing={0}>
<span className={styles.templateName}>
<span css={styles.templateName}>
{"display_name" in template && template.display_name.length > 0
? template.display_name
: template.name}
</span>
{template.description && (
<span className={styles.templateDescription}>
{template.description}
</span>
<span css={styles.templateDescription}>{template.description}</span>
)}
</Stack>
</Stack>
);
};
const useStyles = makeStyles((theme) => ({
template: {
const styles = {
template: (theme) => ({
padding: theme.spacing(2.5, 3),
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
},
}),
templateName: {
fontSize: 16,
},
templateDescription: {
templateDescription: (theme) => ({
fontSize: 14,
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 { makeStyles } from "@mui/styles";
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 { Stack } from "components/Stack/Stack";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
import { useClickableTableRow } from "hooks/useClickableTableRow";
import { useNavigate } from "react-router-dom";
import { colors } from "theme/colors";
import { combineClasses } from "utils/combineClasses";
export interface VersionRowProps {
version: TemplateVersion;
@ -27,7 +26,6 @@ export const VersionRow: React.FC<VersionRowProps> = ({
onPromoteClick,
onArchiveClick,
}) => {
const styles = useStyles();
const navigate = useNavigate();
const clickableProps = useClickableTableRow({
@ -40,17 +38,14 @@ export const VersionRow: React.FC<VersionRowProps> = ({
<TimelineEntry
data-testid={`version-${version.id}`}
{...clickableProps}
className={combineClasses({
[clickableProps.className]: true,
[styles.row]: true,
[styles.active]: isActive,
})}
css={[styles.row, isActive && styles.active]}
className={clickableProps.className}
>
<TableCell className={styles.versionCell}>
<TableCell css={styles.versionCell}>
<Stack
direction="row"
alignItems="center"
className={styles.versionWrapper}
css={styles.versionWrapper}
justifyContent="space-between"
>
<Stack direction="row" alignItems="center">
@ -59,7 +54,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
avatarURL={version.created_by.avatar_url}
/>
<Stack
className={styles.versionSummary}
css={styles.versionSummary}
direction="row"
alignItems="center"
spacing={1}
@ -73,7 +68,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
<InfoTooltip title="Message" message={version.message} />
)}
<span className={styles.versionTime}>
<span css={styles.versionTime}>
{new Date(version.created_at).toLocaleTimeString()}
</span>
</Stack>
@ -94,7 +89,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
{jobStatus === "failed" && <Pill text="Failed" type="error" />}
{jobStatus === "failed" ? (
<Button
className={styles.promoteButton}
css={styles.promoteButton}
disabled={isActive || version.archived}
onClick={(e) => {
e.preventDefault();
@ -108,7 +103,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
</Button>
) : (
<Button
className={styles.promoteButton}
css={styles.promoteButton}
disabled={isActive || jobStatus !== "succeeded"}
onClick={(e) => {
e.preventDefault();
@ -128,8 +123,8 @@ export const VersionRow: React.FC<VersionRowProps> = ({
);
};
const useStyles = makeStyles((theme) => ({
row: {
const styles = {
row: (theme) => ({
"&:hover $promoteButton": {
color: theme.palette.text.primary,
borderColor: colors.gray[11],
@ -137,20 +132,20 @@ const useStyles = makeStyles((theme) => ({
borderColor: theme.palette.text.primary,
},
},
},
}),
promoteButton: {
promoteButton: (theme) => ({
color: theme.palette.text.secondary,
transition: "none",
},
}),
versionWrapper: {
versionWrapper: (theme) => ({
padding: theme.spacing(2, 4),
},
}),
active: {
active: (theme) => ({
backgroundColor: theme.palette.background.paperLight,
},
}),
versionCell: {
padding: "0 !important",
@ -158,13 +153,13 @@ const useStyles = makeStyles((theme) => ({
borderBottom: 0,
},
versionSummary: {
...theme.typography.body1,
versionSummary: (theme) => ({
...(theme.typography.body1 as CSSObject),
fontFamily: "inherit",
},
}),
versionTime: {
versionTime: (theme) => ({
color: theme.palette.text.secondary,
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 { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { FormikContextType, FormikTouched, useFormik } from "formik";
import { FC } from "react";
import type { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { type FormikContextType, type FormikTouched, useFormik } from "formik";
import { type FC } from "react";
import {
getFormHelpers,
nameValidator,
@ -23,7 +24,6 @@ import {
HelpTooltip,
HelpTooltipText,
} from "components/HelpTooltip/HelpTooltip";
import { makeStyles } from "@mui/styles";
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
@ -79,7 +79,6 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
initialTouched,
});
const getFieldHelpers = getFormHelpers(form, error);
const styles = useStyles();
return (
<HorizontalForm
@ -154,7 +153,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
direction="row"
alignItems="center"
spacing={0.5}
className={styles.optionText}
css={styles.optionText}
>
Allow users to cancel in-progress workspace jobs.
<HelpTooltip>
@ -163,7 +162,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
</HelpTooltipText>
</HelpTooltip>
</Stack>
<span className={styles.optionHelperText}>
<span css={styles.optionHelperText}>
Depending on your template, canceling builds may leave
workspaces in an unhealthy state. This option isn&apos;t
recommended for most use cases.
@ -186,7 +185,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
direction="row"
alignItems="center"
spacing={0.5}
className={styles.optionText}
css={styles.optionText}
>
Require the active template version for workspace builds.
<HelpTooltip>
@ -195,7 +194,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
</HelpTooltipText>
</HelpTooltip>
</Stack>
<span className={styles.optionHelperText}>
<span css={styles.optionHelperText}>
Workspaces that are manually started or auto-started will
use the promoted template version.
</span>
@ -211,14 +210,14 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
);
};
const useStyles = makeStyles((theme) => ({
optionText: {
const styles = {
optionText: (theme) => ({
fontSize: theme.spacing(2),
color: theme.palette.text.primary,
},
}),
optionHelperText: {
optionHelperText: (theme) => ({
fontSize: theme.spacing(1.5),
color: theme.palette.text.secondary,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -1,18 +1,17 @@
import { makeStyles } from "@mui/styles";
import { Sidebar } from "./Sidebar";
import { Stack } from "components/Stack/Stack";
import { createContext, FC, Suspense, useContext } from "react";
import { createContext, type FC, Suspense, useContext } from "react";
import { Helmet } from "react-helmet-async";
import { useQuery } from "react-query";
import { pageTitle } from "utils/page";
import { Stack } from "components/Stack/Stack";
import { Loader } from "components/Loader/Loader";
import { Outlet, useParams } from "react-router-dom";
import { Margins } from "components/Margins/Margins";
import { useQuery } from "react-query";
import { useOrganizationId } from "hooks/useOrganizationId";
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 { checkAuthorization } from "api/queries/authCheck";
import { Sidebar } from "./Sidebar";
const TemplateSettings = createContext<
{ template: Template; permissions: AuthorizationResponse } | undefined
@ -28,7 +27,6 @@ export function useTemplateSettings() {
}
export const TemplateSettingsLayout: FC = () => {
const styles = useStyles();
const orgId = useOrganizationId();
const { template: templateName } = useParams() as { template: string };
const templateQuery = useQuery(templateByName(orgId, templateName));
@ -58,7 +56,13 @@ export const TemplateSettingsLayout: FC = () => {
</Helmet>
<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 ? (
<ErrorAlert error={templateQuery.error} />
) : (
@ -70,7 +74,11 @@ export const TemplateSettingsLayout: FC = () => {
>
<Sidebar template={templateQuery.data} />
<Suspense fallback={<Loader />}>
<main className={styles.content}>
<main
css={{
width: "100%",
}}
>
<Outlet />
</main>
</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 ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import TreeView from "@mui/lab/TreeView";
import TreeItem from "@mui/lab/TreeItem";
import Menu from "@mui/material/Menu";
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 { DockerIcon } from "components/Icons/DockerIcon";
import { colors } from "theme/colors";
@ -35,7 +35,6 @@ export const FileTreeView: FC<{
fileTree: FileTree;
activePath?: string;
}> = ({ fileTree, activePath, onDelete, onRename, onSelect }) => {
const styles = useStyles();
const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>();
const buildTreeItems = (
@ -60,9 +59,54 @@ export const FileTreeView: FC<{
nodeId={currentPath}
key={currentPath}
label={filename}
className={`${styles.fileTreeItem} ${
currentPath === activePath ? "active" : ""
}`}
css={(theme) => css`
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={() => {
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 = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#813cf3">
<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 DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import { DialogProps } from "components/Dialogs/Dialog";
import { FC, useEffect, useState } from "react";
import { type DialogProps } from "components/Dialogs/Dialog";
import { type FC, useEffect, useState } from "react";
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 Button from "@mui/material/Button";
import { VariableInput } from "pages/CreateTemplatePage/VariableInput";
@ -24,7 +28,7 @@ export type MissingTemplateVariablesDialogProps = Omit<
export const MissingTemplateVariablesDialog: FC<
MissingTemplateVariablesDialogProps
> = ({ missingVariables, onSubmit, ...dialogProps }) => {
const styles = useStyles();
const theme = useTheme();
const [variableValues, setVariableValues] = useState<VariableValue[]>([]);
// Pre-fill the form with the default values when missing variables are loaded
@ -47,16 +51,25 @@ export const MissingTemplateVariablesDialog: FC<
>
<DialogTitle
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
</DialogTitle>
<DialogContent className={styles.content}>
<DialogContentText className={styles.info}>
<DialogContent css={styles.content}>
<DialogContentText css={styles.info}>
There are a few missing template variable values. Please fill them in.
</DialogContentText>
<VerticalForm
className={styles.form}
css={styles.form}
id="updateVariables"
onSubmit={(e) => {
e.preventDefault();
@ -89,7 +102,7 @@ export const MissingTemplateVariablesDialog: FC<
)}
</VerticalForm>
</DialogContent>
<DialogActions disableSpacing className={styles.dialogActions}>
<DialogActions disableSpacing css={styles.dialogActions}>
<Button color="primary" fullWidth type="submit" form="updateVariables">
Submit
</Button>
@ -101,43 +114,22 @@ export const MissingTemplateVariablesDialog: FC<
);
};
const useStyles = makeStyles((theme) => ({
title: {
padding: theme.spacing(3, 5),
"& h2": {
fontSize: theme.spacing(2.5),
fontWeight: 400,
},
},
content: {
padding: theme.spacing(0, 5, 0, 5),
},
const styles = {
content: (theme) => ({
padding: theme.spacing(0, 5),
}),
info: {
margin: 0,
},
form: {
form: (theme) => ({
paddingTop: theme.spacing(4),
},
}),
infoTitle: {
fontSize: theme.spacing(2),
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
formFooter: {
flexDirection: "column",
},
dialogActions: {
dialogActions: (theme) => ({
padding: theme.spacing(5),
flexDirection: "column",
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 FormLabel from "@mui/material/FormLabel";
import MenuItem from "@mui/material/MenuItem";
import makeStyles from "@mui/styles/makeStyles";
import Switch from "@mui/material/Switch";
import TextField from "@mui/material/TextField";
import {
@ -200,8 +199,6 @@ export const WorkspaceScheduleForm: FC<
enableAutoStop,
enableAutoStart,
}) => {
const styles = useStyles();
const form = useFormik<WorkspaceScheduleFormValues>({
initialValues,
onSubmit,
@ -340,11 +337,23 @@ export const WorkspaceScheduleForm: FC<
</Stack>
<FormControl component="fieldset" error={Boolean(form.errors.monday)}>
<FormLabel className={styles.daysOfWeekLabel} component="legend">
<FormLabel
css={{
fontSize: 12,
}}
component="legend"
>
{Language.daysOfWeekLabel}
</FormLabel>
<FormGroup className={styles.daysOfWeekOptions}>
<FormGroup
css={(theme) => ({
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
paddingTop: theme.spacing(0.5),
})}
>
{checkboxes.map((checkbox) => (
<FormControlLabel
control={
@ -422,15 +431,3 @@ export const ttlShutdownAt = (formTTL: number): string => {
.humanize()} ${Language.ttlCausesShutdownAfterStart}.`;
}
};
const useStyles = makeStyles((theme) => ({
daysOfWeekLabel: {
fontSize: 12,
},
daysOfWeekOptions: {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
paddingTop: theme.spacing(0.5),
},
}));