chore: use emotion for styling (pt. 3) (#10026)

This commit is contained in:
Kayla Washburn
2023-10-05 10:49:44 -06:00
committed by GitHub
parent f001a57614
commit 246dae0e1a
21 changed files with 543 additions and 697 deletions
+33 -41
View File
@@ -1,9 +1,7 @@
import { makeStyles } from "@mui/styles";
import { FC } from "react";
import { type FC } from "react";
import { useTheme } from "@emotion/react";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { combineClasses } from "utils/combineClasses";
import { CopyButton } from "../CopyButton/CopyButton";
import { Theme } from "@mui/material/styles";
export interface CodeExampleProps {
code: string;
@@ -14,46 +12,40 @@ export interface CodeExampleProps {
/**
* Component to show single-line code examples, with a copy button
*/
export const CodeExample: FC<CodeExampleProps> = ({
code,
password,
className,
}) => {
const styles = useStyles({ password });
export const CodeExample: FC<CodeExampleProps> = (props) => {
const { code, password, className } = props;
const theme = useTheme();
return (
<div className={combineClasses([styles.root, className])}>
<code className={styles.code}>{code}</code>
<div
css={{
display: "flex",
flexDirection: "row",
alignItems: "center",
background: "rgb(0 0 0 / 30%)",
color: theme.palette.primary.contrastText,
fontFamily: MONOSPACE_FONT_FAMILY,
fontSize: 14,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(1),
lineHeight: "150%",
border: `1px solid ${theme.palette.divider}`,
}}
className={className}
>
<code
css={{
padding: theme.spacing(0, 1),
width: "100%",
display: "flex",
alignItems: "center",
wordBreak: "break-all",
"-webkit-text-security": password ? "disc" : undefined,
}}
>
{code}
</code>
<CopyButton text={code} />
</div>
);
};
interface styleProps {
inline?: boolean;
password?: boolean;
}
const useStyles = makeStyles<Theme, styleProps>((theme) => ({
root: (props) => ({
display: props.inline ? "inline-flex" : "flex",
flexDirection: "row",
alignItems: "center",
background: "rgb(0 0 0 / 30%)",
color: theme.palette.primary.contrastText,
fontFamily: MONOSPACE_FONT_FAMILY,
fontSize: 14,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(1),
lineHeight: "150%",
border: `1px solid ${theme.palette.divider}`,
}),
code: {
padding: theme.spacing(0, 1),
width: "100%",
display: "flex",
alignItems: "center",
wordBreak: "break-all",
"-webkit-text-security": (props) => (props.password ? "disc" : undefined),
},
}));
@@ -5,10 +5,7 @@ import { colors } from "theme/colors";
import * as TypesGen from "api/typesGenerated";
import { navHeight } from "theme/constants";
import { BorderedMenu } from "./BorderedMenu";
import {
CloseDropdown,
OpenDropdown,
} from "components/DropdownArrows/DropdownArrows";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { UserDropdownContent } from "./UserDropdownContent";
import { BUTTON_SM_HEIGHT } from "theme/theme";
@@ -69,11 +66,7 @@ export const UserDropdown: FC<PropsWithChildren<UserDropdownProps>> = ({
avatarURL={user.avatar_url}
/>
</Badge>
{anchorEl ? (
<CloseDropdown color={colors.gray[6]} />
) : (
<OpenDropdown color={colors.gray[6]} />
)}
<DropdownArrow color={colors.gray[6]} close={Boolean(anchorEl)} />
</div>
</MenuItem>
@@ -0,0 +1,28 @@
import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUp from "@mui/icons-material/KeyboardArrowUp";
import { type FC } from "react";
import { type Theme } from "@emotion/react";
interface ArrowProps {
margin?: boolean;
color?: string;
close?: boolean;
}
export const DropdownArrow: FC<ArrowProps> = (props) => {
const { margin = true, color, close } = props;
const Arrow = close ? KeyboardArrowUp : KeyboardArrowDown;
return (
<Arrow
aria-label={close ? "close-dropdown" : "open-dropdown"}
css={(theme: Theme) => ({
color: color ?? theme.palette.primary.contrastText,
marginLeft: margin ? theme.spacing(1) : 0,
width: 16,
height: 16,
})}
/>
);
};
@@ -1,42 +0,0 @@
import { makeStyles } from "@mui/styles";
import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUp from "@mui/icons-material/KeyboardArrowUp";
import { FC } from "react";
import { Theme } from "@mui/material/styles";
const useStyles = makeStyles<Theme, ArrowProps>((theme: Theme) => ({
arrowIcon: {
color: ({ color }) => color ?? theme.palette.primary.contrastText,
marginLeft: ({ margin }) => (margin ? theme.spacing(1) : 0),
width: 16,
height: 16,
},
arrowIconUp: {
color: ({ color }) => color ?? theme.palette.primary.contrastText,
},
}));
interface ArrowProps {
margin?: boolean;
color?: string;
}
export const OpenDropdown: FC<ArrowProps> = ({ margin = true, color }) => {
const styles = useStyles({ margin, color });
return (
<KeyboardArrowDown
aria-label="open-dropdown"
className={styles.arrowIcon}
/>
);
};
export const CloseDropdown: FC<ArrowProps> = ({ margin = true, color }) => {
const styles = useStyles({ margin, color });
return (
<KeyboardArrowUp
aria-label="close-dropdown"
className={`${styles.arrowIcon} ${styles.arrowIconUp}`}
/>
);
};
+5 -8
View File
@@ -1,11 +1,8 @@
import Collapse from "@mui/material/Collapse";
import Link from "@mui/material/Link";
import makeStyles from "@mui/styles/makeStyles";
import {
CloseDropdown,
OpenDropdown,
} from "components/DropdownArrows/DropdownArrows";
import { PropsWithChildren, FC } from "react";
import Collapse from "@mui/material/Collapse";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { type FC, type PropsWithChildren } from "react";
import { combineClasses } from "utils/combineClasses";
export interface ExpanderProps {
@@ -28,7 +25,7 @@ export const Expander: FC<PropsWithChildren<ExpanderProps>> = ({
<Link onClick={toggleExpanded} className={styles.expandLink}>
<span className={styles.text}>
Click here to learn more
<OpenDropdown margin={false} />
<DropdownArrow margin={false} />
</span>
</Link>
)}
@@ -42,7 +39,7 @@ export const Expander: FC<PropsWithChildren<ExpanderProps>> = ({
>
<span className={styles.text}>
Click here to hide
<CloseDropdown margin={false} />
<DropdownArrow margin={false} close />
</span>
</Link>
)}
+68 -88
View File
@@ -1,6 +1,5 @@
import Link from "@mui/material/Link";
import Popover, { PopoverProps } from "@mui/material/Popover";
import { makeStyles } from "@mui/styles";
import HelpIcon from "@mui/icons-material/HelpOutline";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import {
@@ -11,9 +10,10 @@ import {
FC,
PropsWithChildren,
} from "react";
import { combineClasses } from "utils/combineClasses";
import { Stack } from "components/Stack/Stack";
import Box, { BoxProps } from "@mui/material/Box";
import { type CSSObject, css as className } from "@emotion/css";
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
type Icon = typeof HelpIcon;
@@ -46,12 +46,24 @@ const useHelpTooltip = () => {
export const HelpPopover: FC<
PopoverProps & { onOpen: () => void; onClose: () => void }
> = ({ onOpen, onClose, children, ...props }) => {
const styles = useStyles({ size: "small" });
const theme = useTheme();
return (
<Popover
className={styles.popover}
classes={{ paper: styles.popoverPaper }}
className={className`
pointer-events: none;
`}
classes={{
paper: className`
${theme.typography.body2 as CSSObject}
margin-top: ${theme.spacing(0.5)};
width: ${theme.spacing(38)};
padding: ${theme.spacing(2.5)};
color: ${theme.palette.text.secondary};
pointer-events: auto;
`,
}}
onClose={onClose}
anchorOrigin={{
vertical: "bottom",
@@ -80,7 +92,7 @@ export const HelpTooltip: FC<PropsWithChildren<HelpTooltipProps>> = ({
iconClassName,
buttonClassName,
}) => {
const styles = useStyles({ size });
const theme = useTheme();
const anchorRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState(Boolean(open));
const id = isOpen ? "help-popover" : undefined;
@@ -94,7 +106,24 @@ export const HelpTooltip: FC<PropsWithChildren<HelpTooltipProps>> = ({
<button
ref={anchorRef}
aria-describedby={id}
className={combineClasses([styles.button, buttonClassName])}
css={css`
display: flex;
align-items: center;
justify-content: center;
width: ${theme.spacing(getButtonSpacingFromSize(size))};
height: ${theme.spacing(getButtonSpacingFromSize(size))};
padding: 0;
border: 0;
background: transparent;
color: ${theme.palette.text.primary};
opacity: 0.5;
cursor: pointer;
&:hover {
opacity: 0.75;
}
`}
className={buttonClassName}
onClick={(event) => {
event.stopPropagation();
setIsOpen(true);
@@ -107,7 +136,13 @@ export const HelpTooltip: FC<PropsWithChildren<HelpTooltipProps>> = ({
}}
aria-label="More info"
>
<Icon className={combineClasses([styles.icon, iconClassName])} />
<Icon
css={{
width: theme.spacing(getIconSpacingFromSize(size)),
height: theme.spacing(getIconSpacingFromSize(size)),
}}
className={iconClassName}
/>
</button>
<HelpPopover
id={id}
@@ -127,32 +162,20 @@ export const HelpTooltip: FC<PropsWithChildren<HelpTooltipProps>> = ({
export const HelpTooltipTitle: FC<PropsWithChildren<unknown>> = ({
children,
}) => {
const styles = useStyles({});
return <h4 className={styles.title}>{children}</h4>;
return <h4 css={styles.title}>{children}</h4>;
};
export const HelpTooltipText = (props: BoxProps) => {
const styles = useStyles({});
return (
<Box
component="p"
{...props}
className={combineClasses([styles.text, props.className])}
/>
);
return <Box component="p" css={styles.text} {...props} />;
};
export const HelpTooltipLink: FC<PropsWithChildren<{ href: string }>> = ({
children,
href,
}) => {
const styles = useStyles({});
return (
<Link href={href} target="_blank" rel="noreferrer" className={styles.link}>
<OpenInNewIcon className={styles.linkIcon} />
<Link href={href} target="_blank" rel="noreferrer" css={styles.link}>
<OpenInNewIcon css={styles.linkIcon} />
{children}
</Link>
);
@@ -165,20 +188,19 @@ export const HelpTooltipAction: FC<
ariaLabel?: string;
}>
> = ({ children, icon: Icon, onClick, ariaLabel }) => {
const styles = useStyles({});
const tooltip = useHelpTooltip();
return (
<button
aria-label={ariaLabel ?? ""}
className={styles.action}
css={styles.action}
onClick={(event) => {
event.stopPropagation();
onClick();
tooltip.onClose();
}}
>
<Icon className={styles.actionIcon} />
<Icon css={styles.actionIcon} />
{children}
</button>
);
@@ -187,10 +209,8 @@ export const HelpTooltipAction: FC<
export const HelpTooltipLinksGroup: FC<PropsWithChildren<unknown>> = ({
children,
}) => {
const styles = useStyles({});
return (
<Stack spacing={1} className={styles.linksGroup}>
<Stack spacing={1} css={styles.linksGroup}>
{children}
</Stack>
);
@@ -216,80 +236,40 @@ const getIconSpacingFromSize = (size?: Size): number => {
}
};
const useStyles = makeStyles((theme) => ({
button: {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: ({ size }: { size?: Size }) =>
theme.spacing(getButtonSpacingFromSize(size)),
height: ({ size }: { size?: Size }) =>
theme.spacing(getButtonSpacingFromSize(size)),
padding: 0,
border: 0,
background: "transparent",
color: theme.palette.text.primary,
opacity: 0.5,
cursor: "pointer",
"&:hover": {
opacity: 0.75,
},
},
icon: {
width: ({ size }: { size?: Size }) =>
theme.spacing(getIconSpacingFromSize(size)),
height: ({ size }: { size?: Size }) =>
theme.spacing(getIconSpacingFromSize(size)),
},
popover: {
pointerEvents: "none",
},
popoverPaper: {
marginTop: theme.spacing(0.5),
width: theme.spacing(38),
padding: theme.spacing(2.5),
color: theme.palette.text.secondary,
pointerEvents: "auto",
...theme.typography.body2,
},
title: {
const styles = {
title: (theme) => ({
marginTop: 0,
marginBottom: theme.spacing(1),
color: theme.palette.text.primary,
fontSize: 14,
lineHeight: "120%",
fontWeight: 600,
},
}),
text: {
text: (theme) => ({
marginTop: theme.spacing(0.5),
marginBottom: theme.spacing(0.5),
...theme.typography.body2,
},
...(theme.typography.body2 as CSSObject),
}),
link: {
link: (theme) => ({
display: "flex",
alignItems: "center",
...theme.typography.body2,
},
...(theme.typography.body2 as CSSObject),
}),
linkIcon: {
linkIcon: (theme) => ({
color: "inherit",
width: 14,
height: 14,
marginRight: theme.spacing(1),
},
}),
linksGroup: {
linksGroup: (theme) => ({
marginTop: theme.spacing(2),
},
}),
action: {
action: (theme) => ({
display: "flex",
alignItems: "center",
background: "none",
@@ -298,12 +278,12 @@ const useStyles = makeStyles((theme) => ({
padding: 0,
cursor: "pointer",
fontSize: 14,
},
}),
actionIcon: {
actionIcon: (theme) => ({
color: "inherit",
width: 14,
height: 14,
marginRight: theme.spacing(1),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+2 -2
View File
@@ -2,7 +2,7 @@ import Button from "@mui/material/Button";
import InputAdornment from "@mui/material/InputAdornment";
import Popover from "@mui/material/Popover";
import TextField, { TextFieldProps } from "@mui/material/TextField";
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { useRef, FC, useState } from "react";
import Picker from "@emoji-mart/react";
import { makeStyles } from "@mui/styles";
@@ -52,7 +52,7 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
<Button
fullWidth
ref={emojiButtonRef}
endIcon={<OpenDropdown />}
endIcon={<DropdownArrow />}
onClick={() => {
setIsEmojiPickerOpen((v) => !v);
}}
+10 -19
View File
@@ -5,7 +5,7 @@ import {
HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { makeStyles } from "@mui/styles";
import { css } from "@emotion/css";
import { colors } from "theme/colors";
interface InfoTooltipProps {
@@ -17,31 +17,22 @@ interface InfoTooltipProps {
export const InfoTooltip: FC<InfoTooltipProps> = (props) => {
const { title, message, type = "info" } = props;
const styles = useStyles({ type });
return (
<HelpTooltip
size="small"
icon={InfoIcon}
iconClassName={styles.icon}
buttonClassName={styles.button}
iconClassName={css`
color: ${type === "info" ? colors.blue[5] : colors.yellow[5]};
`}
buttonClassName={css`
opacity: 1;
&:hover {
opacity: 1;
}
`}
>
<HelpTooltipTitle>{title}</HelpTooltipTitle>
<HelpTooltipText>{message}</HelpTooltipText>
</HelpTooltip>
);
};
const useStyles = makeStyles<unknown, Pick<InfoTooltipProps, "type">>(() => ({
icon: ({ type }) => ({
color: type === "info" ? colors.blue[5] : colors.yellow[5],
}),
button: {
opacity: 1,
"&:hover": {
opacity: 1,
},
},
}));
+74 -69
View File
@@ -1,5 +1,4 @@
import Link from "@mui/material/Link";
import { makeStyles } from "@mui/styles";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
@@ -12,17 +11,24 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import gfm from "remark-gfm";
import { colors } from "theme/colors";
import { darcula } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { combineClasses } from "utils/combineClasses";
import { type Interpolation, type Theme } from "@emotion/react";
export const Markdown: FC<{ children: string; className?: string }> = ({
children,
className,
}) => {
const styles = useStyles();
interface MarkdownProps {
/**
* The Markdown text to parse and render
*/
children: string;
className?: string;
}
export const Markdown: FC<MarkdownProps> = (props) => {
const { children, className } = props;
return (
<ReactMarkdown
className={combineClasses([styles.markdown, className])}
css={markdownStyles}
className={className}
remarkPlugins={[gfm]}
components={{
a: ({ href, target, children }) => (
@@ -58,7 +64,16 @@ export const Markdown: FC<{ children: string; className?: string }> = ({
{String(children)}
</SyntaxHighlighter>
) : (
<code className={styles.codeWithoutLanguage} {...props}>
<code
css={(theme) => ({
padding: theme.spacing(0.125, 0.5),
background: theme.palette.divider,
borderRadius: 4,
color: theme.palette.text.primary,
fontSize: 14,
})}
{...props}
>
{children}
</code>
);
@@ -100,67 +115,57 @@ export const Markdown: FC<{ children: string; className?: string }> = ({
export const MemoizedMarkdown = memo(Markdown);
const useStyles = makeStyles((theme) => ({
markdown: {
fontSize: 16,
lineHeight: "24px",
const markdownStyles: Interpolation<Theme> = (theme: Theme) => ({
fontSize: 16,
lineHeight: "24px",
"& h1, & h2, & h3, & h4, & h5, & h6": {
marginTop: theme.spacing(4),
marginBottom: theme.spacing(2),
lineHeight: "1.25",
},
"& p": {
marginTop: 0,
marginBottom: theme.spacing(2),
},
"& p:only-child": {
marginTop: 0,
marginBottom: 0,
},
"& ul, & ol": {
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
marginBottom: theme.spacing(2),
},
"& li > ul, & li > ol": {
marginTop: theme.spacing(2),
},
"& li > p": {
marginBottom: 0,
},
"& .prismjs": {
background: theme.palette.background.paperLight,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(2, 3),
overflowX: "auto",
"& code": {
color: theme.palette.text.secondary,
},
"& .key, & .property, & .inserted, .keyword": {
color: colors.turquoise[7],
},
"& .deleted": {
color: theme.palette.error.light,
},
},
"& h1, & h2, & h3, & h4, & h5, & h6": {
marginTop: theme.spacing(4),
marginBottom: theme.spacing(2),
lineHeight: "1.25",
},
codeWithoutLanguage: {
padding: theme.spacing(0.125, 0.5),
background: theme.palette.divider,
borderRadius: 4,
color: theme.palette.text.primary,
fontSize: 14,
"& p": {
marginTop: 0,
marginBottom: theme.spacing(2),
},
}));
"& p:only-child": {
marginTop: 0,
marginBottom: 0,
},
"& ul, & ol": {
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
marginBottom: theme.spacing(2),
},
"& li > ul, & li > ol": {
marginTop: theme.spacing(2),
},
"& li > p": {
marginBottom: 0,
},
"& .prismjs": {
background: theme.palette.background.paperLight,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(2, 3),
overflowX: "auto",
"& code": {
color: theme.palette.text.secondary,
},
"& .key, & .property, & .inserted, .keyword": {
color: colors.turquoise[7],
},
"& .deleted": {
color: theme.palette.error.light,
},
},
});
+3 -6
View File
@@ -9,10 +9,7 @@ import {
WorkspaceAgentLogSource,
WorkspaceAgentMetadata,
} from "api/typesGenerated";
import {
CloseDropdown,
OpenDropdown,
} from "components/DropdownArrows/DropdownArrows";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { displayError } from "components/GlobalSnackbar/utils";
import { VSCodeDesktopButton } from "components/Resources/VSCodeDesktopButton/VSCodeDesktopButton";
import {
@@ -443,7 +440,7 @@ export const AgentRow: FC<AgentRowProps> = ({
setShowLogs((v) => !v);
}}
>
<CloseDropdown />
<DropdownArrow close />
Hide logs
</button>
) : (
@@ -456,7 +453,7 @@ export const AgentRow: FC<AgentRowProps> = ({
setShowLogs((v) => !v);
}}
>
<OpenDropdown />
<DropdownArrow />
Show logs
</button>
)}
+87 -92
View File
@@ -1,17 +1,71 @@
import { makeStyles } from "@mui/styles";
import { FC, useState } from "react";
import { type FC, useState } from "react";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
import { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { CopyableValue } from "components/CopyableValue/CopyableValue";
import { Stack } from "../Stack/Stack";
import { ResourceAvatar } from "./ResourceAvatar";
import { SensitiveValue } from "./SensitiveValue";
import {
OpenDropdown,
CloseDropdown,
} from "components/DropdownArrows/DropdownArrows";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import { CopyableValue } from "components/CopyableValue/CopyableValue";
import { type Theme } from "@mui/material/styles";
const styles = {
resourceCard: (theme) => ({
background: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
"&:not(:first-of-type)": {
borderTop: 0,
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
},
"&:not(:last-child)": {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
},
}),
resourceCardProfile: {
flexShrink: 0,
width: "fit-content",
},
resourceCardHeader: (theme) => ({
padding: theme.spacing(3, 4),
borderBottom: `1px solid ${theme.palette.divider}`,
"&:last-child": {
borderBottom: 0,
},
[theme.breakpoints.down("md")]: {
width: "100%",
overflow: "scroll",
},
}),
metadata: (theme) => ({
...(theme.typography.body2 as CSSObject),
lineHeight: "120%",
}),
metadataLabel: (theme) => ({
fontSize: 12,
color: theme.palette.text.secondary,
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}),
metadataValue: (theme) => ({
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
...(theme.typography.body1 as CSSObject),
}),
} satisfies Record<string, Interpolation<Theme>>;
export interface ResourceCardProps {
resource: WorkspaceResource;
@@ -33,44 +87,52 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
metadataLength += 1;
visibleMetadata.pop();
}
const styles = useStyles({ metadataLength });
const gridWidth = metadataLength === 1 ? 1 : 4;
return (
<div key={resource.id} className={`${styles.resourceCard} resource-card`}>
<div key={resource.id} css={styles.resourceCard} className="resource-card">
<Stack
direction="row"
alignItems="flex-start"
className={styles.resourceCardHeader}
css={styles.resourceCardHeader}
spacing={10}
>
<Stack
direction="row"
alignItems="center"
className={styles.resourceCardProfile}
css={styles.resourceCardProfile}
>
<div>
<ResourceAvatar resource={resource} />
</div>
<div className={styles.metadata}>
<div className={styles.metadataLabel}>{resource.type}</div>
<div className={styles.metadataValue}>{resource.name}</div>
<div css={styles.metadata}>
<div css={styles.metadataLabel}>{resource.type}</div>
<div css={styles.metadataValue}>{resource.name}</div>
</div>
</Stack>
<div className={styles.metadataHeader}>
<div
css={(theme) => ({
flexGrow: 2,
display: "grid",
gridTemplateColumns: `repeat(${gridWidth}, minmax(0, 1fr))`,
gap: theme.spacing(5),
rowGap: theme.spacing(3),
})}
>
{resource.daily_cost > 0 && (
<div className={styles.metadata}>
<div className={styles.metadataLabel}>
<div css={styles.metadata}>
<div css={styles.metadataLabel}>
<b>cost</b>
</div>
<div className={styles.metadataValue}>{resource.daily_cost}</div>
<div css={styles.metadataValue}>{resource.daily_cost}</div>
</div>
)}
{visibleMetadata.map((meta) => {
return (
<div className={styles.metadata} key={meta.key}>
<div className={styles.metadataLabel}>{meta.key}</div>
<div className={styles.metadataValue}>
<div css={styles.metadata} key={meta.key}>
<div css={styles.metadataLabel}>{meta.key}</div>
<div css={styles.metadataValue}>
{meta.sensitive ? (
<SensitiveValue value={meta.value} />
) : (
@@ -95,11 +157,7 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
}}
size="large"
>
{shouldDisplayAllMetadata ? (
<CloseDropdown margin={false} />
) : (
<OpenDropdown margin={false} />
)}
<DropdownArrow margin={false} close={shouldDisplayAllMetadata} />
</IconButton>
</Tooltip>
)}
@@ -111,66 +169,3 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
</div>
);
};
const useStyles = makeStyles<Theme, { metadataLength: number }>((theme) => ({
resourceCard: {
background: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
"&:not(:first-of-type)": {
borderTop: 0,
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
},
"&:not(:last-child)": {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
},
},
resourceCardProfile: {
flexShrink: 0,
width: "fit-content",
},
resourceCardHeader: {
padding: theme.spacing(3, 4),
borderBottom: `1px solid ${theme.palette.divider}`,
"&:last-child": {
borderBottom: 0,
},
},
metadataHeader: (props) => ({
flexGrow: 2,
display: "grid",
gridTemplateColumns: `repeat(${
props.metadataLength === 1 ? 1 : 4
}, minmax(0, 1fr))`,
gap: theme.spacing(5),
rowGap: theme.spacing(3),
}),
metadata: {
...theme.typography.body2,
lineHeight: "120%",
},
metadataLabel: {
fontSize: 12,
color: theme.palette.text.secondary,
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
},
metadataValue: {
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
...theme.typography.body1,
},
}));
+3 -13
View File
@@ -1,9 +1,6 @@
import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import {
CloseDropdown,
OpenDropdown,
} from "components/DropdownArrows/DropdownArrows";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { FC, useState } from "react";
import { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
import { Stack } from "../Stack/Stack";
@@ -49,15 +46,8 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
size="small"
onClick={() => setShouldDisplayHideResources((v) => !v)}
>
{shouldDisplayHideResources ? (
<>
Hide resources <CloseDropdown />
</>
) : (
<>
Show hidden resources <OpenDropdown />
</>
)}
{shouldDisplayHideResources ? "Hide" : "Show hidden"} resources
<DropdownArrow close={shouldDisplayHideResources} />
</Button>
</div>
)}
@@ -1,27 +1,111 @@
import FormControlLabel from "@mui/material/FormControlLabel";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import { makeStyles } from "@mui/styles";
import TextField, { TextFieldProps } from "@mui/material/TextField";
import { Stack } from "components/Stack/Stack";
import Box from "@mui/material/Box";
import { FC } from "react";
import { TemplateVersionParameter } from "api/typesGenerated";
import { colors } from "theme/colors";
import { MemoizedMarkdown } from "components/Markdown/Markdown";
import { Stack } from "components/Stack/Stack";
import { colors } from "theme/colors";
import { MultiTextField } from "./MultiTextField";
import Box from "@mui/material/Box";
import { Theme } from "@mui/material/styles";
import { Interpolation, Theme } from "@emotion/react";
const isBoolean = (parameter: TemplateVersionParameter) => {
return parameter.type === "bool";
};
const styles = {
label: (theme) => ({
marginBottom: theme.spacing(0.5),
}),
labelCaption: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
".small &": {
fontSize: 13,
lineHeight: "140%",
},
}),
labelPrimary: (theme) => ({
fontSize: 16,
color: theme.palette.text.primary,
fontWeight: 600,
"& p": {
margin: 0,
lineHeight: "24px", // Keep the same as ParameterInput
},
".small &": {
fontSize: 14,
},
}),
labelImmutable: (theme) => ({
marginTop: theme.spacing(0.5),
marginBottom: theme.spacing(0.5),
color: colors.yellow[7],
}),
textField: {
".small & .MuiInputBase-root": {
height: 36,
fontSize: 14,
borderRadius: 6,
},
},
radioGroup: (theme) => ({
".small & .MuiFormControlLabel-label": {
fontSize: 14,
},
".small & .MuiRadio-root": {
padding: theme.spacing(0.75, "9px"), // 8px + 1px border
},
".small & .MuiRadio-root svg": {
width: 16,
height: 16,
},
}),
checkbox: (theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
}),
labelIconWrapper: (theme) => ({
width: theme.spacing(2.5),
height: theme.spacing(2.5),
display: "block",
".small &": {
display: "none",
},
}),
labelIcon: {
width: "100%",
height: "100%",
objectFit: "contain",
},
radioOption: (theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(1.5),
}),
optionIcon: {
maxHeight: 20,
width: 20,
".small &": {
maxHeight: 16,
width: 16,
},
},
} satisfies Record<string, Interpolation<Theme>>;
export interface ParameterLabelProps {
parameter: TemplateVersionParameter;
}
const ParameterLabel: FC<ParameterLabelProps> = ({ parameter }) => {
const styles = useStyles();
const hasDescription = parameter.description && parameter.description !== "";
const displayName = parameter.display_name
? parameter.display_name
@@ -31,9 +115,9 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter }) => {
<label htmlFor={parameter.name}>
<Stack direction="row" alignItems="center">
{parameter.icon && (
<span className={styles.labelIconWrapper}>
<span css={styles.labelIconWrapper}>
<img
className={styles.labelIcon}
css={styles.labelIcon}
alt="Parameter icon"
src={parameter.icon}
/>
@@ -42,13 +126,13 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter }) => {
{hasDescription ? (
<Stack spacing={0}>
<span className={styles.labelPrimary}>{displayName}</span>
<MemoizedMarkdown className={styles.labelCaption}>
<span css={styles.labelPrimary}>{displayName}</span>
<MemoizedMarkdown css={styles.labelCaption}>
{parameter.description}
</MemoizedMarkdown>
</Stack>
) : (
<span className={styles.labelPrimary}>{displayName}</span>
<span css={styles.labelPrimary}>{displayName}</span>
)}
</Stack>
</label>
@@ -94,14 +178,12 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
size,
...props
}) => {
const styles = useStyles();
if (isBoolean(parameter)) {
return (
<RadioGroup
id={parameter.name}
data-testid="parameter-field-bool"
className={styles.radioGroup}
css={styles.radioGroup}
value={value}
onChange={(_, value) => onChange(value)}
>
@@ -126,7 +208,7 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
<RadioGroup
id={parameter.name}
data-testid="parameter-field-options"
className={styles.radioGroup}
css={styles.radioGroup}
value={value}
onChange={(_, value) => onChange(value)}
>
@@ -137,10 +219,10 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
value={option.value}
control={<Radio size="small" />}
label={
<span className={styles.radioOption}>
<span css={styles.radioOption}>
{option.icon && (
<img
className={styles.optionIcon}
css={styles.optionIcon}
alt="Parameter icon"
src={option.icon}
style={{
@@ -198,7 +280,7 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
{...props}
id={parameter.name}
data-testid="parameter-field-text"
className={styles.textField}
css={styles.textField}
type={parameter.type}
disabled={disabled}
required={parameter.required}
@@ -210,89 +292,3 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
/>
);
};
const useStyles = makeStyles<Theme>((theme) => ({
label: {
marginBottom: theme.spacing(0.5),
},
labelCaption: {
fontSize: 14,
color: theme.palette.text.secondary,
".small &": {
fontSize: 13,
lineHeight: "140%",
},
},
labelPrimary: {
fontSize: 16,
color: theme.palette.text.primary,
fontWeight: 600,
"& p": {
margin: 0,
lineHeight: "24px", // Keep the same as ParameterInput
},
".small &": {
fontSize: 14,
},
},
labelImmutable: {
marginTop: theme.spacing(0.5),
marginBottom: theme.spacing(0.5),
color: colors.yellow[7],
},
textField: {
".small & .MuiInputBase-root": {
height: 36,
fontSize: 14,
borderRadius: 6,
},
},
radioGroup: {
".small & .MuiFormControlLabel-label": {
fontSize: 14,
},
".small & .MuiRadio-root": {
padding: theme.spacing(0.75, "9px"), // 8px + 1px border
},
".small & .MuiRadio-root svg": {
width: 16,
height: 16,
},
},
checkbox: {
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
},
labelIconWrapper: {
width: theme.spacing(2.5),
height: theme.spacing(2.5),
display: "block",
".small &": {
display: "none",
},
},
labelIcon: {
width: "100%",
height: "100%",
objectFit: "contain",
},
radioOption: {
display: "flex",
alignItems: "center",
gap: theme.spacing(1.5),
},
optionIcon: {
maxHeight: 20,
width: 20,
".small &": {
maxHeight: 16,
width: 16,
},
},
}));
+25 -47
View File
@@ -1,61 +1,39 @@
import { makeStyles } from "@mui/styles";
import { CSSProperties } from "@mui/styles/withStyles";
import { FC } from "react";
import { ReactNode } from "react-markdown/lib/react-markdown";
import { combineClasses } from "utils/combineClasses";
type Direction = "column" | "row";
import type { FC } from "react";
import type { CSSObject } from "@emotion/react";
export type StackProps = {
className?: string;
direction?: Direction;
direction?: "column" | "row";
spacing?: number;
alignItems?: CSSProperties["alignItems"];
justifyContent?: CSSProperties["justifyContent"];
maxWidth?: CSSProperties["maxWidth"];
wrap?: CSSProperties["flexWrap"];
alignItems?: CSSObject["alignItems"];
justifyContent?: CSSObject["justifyContent"];
wrap?: CSSObject["flexWrap"];
} & React.HTMLProps<HTMLDivElement>;
type StyleProps = Omit<StackProps, "className">;
const useStyles = makeStyles((theme) => ({
stack: {
display: "flex",
flexDirection: ({ direction }: StyleProps) => direction,
gap: ({ spacing }: StyleProps) => spacing && theme.spacing(spacing),
alignItems: ({ alignItems }: StyleProps) => alignItems,
justifyContent: ({ justifyContent }: StyleProps) => justifyContent,
flexWrap: ({ wrap }: StyleProps) => wrap,
maxWidth: ({ maxWidth }: StyleProps) => maxWidth,
[theme.breakpoints.down("md")]: {
width: "100%",
},
},
}));
export const Stack: FC<StackProps & { children: ReactNode | ReactNode[] }> = ({
children,
className,
direction = "column",
spacing = 2,
alignItems,
justifyContent,
maxWidth,
wrap,
...divProps
}) => {
const styles = useStyles({
spacing,
direction,
export const Stack: FC<StackProps> = (props) => {
const {
children,
direction = "column",
spacing = 2,
alignItems,
justifyContent,
wrap,
maxWidth,
});
...divProps
} = props;
return (
<div {...divProps} className={combineClasses([styles.stack, className])}>
<div
{...divProps}
css={(theme) => ({
display: "flex",
flexDirection: direction,
gap: spacing && theme.spacing(spacing),
alignItems: alignItems,
justifyContent: justifyContent,
flexWrap: wrap,
maxWidth: "100%",
})}
>
{children}
</div>
);
@@ -2,10 +2,7 @@ import Collapse from "@mui/material/Collapse";
import { makeStyles } from "@mui/styles";
import TableCell from "@mui/material/TableCell";
import { AuditLog } from "api/typesGenerated";
import {
CloseDropdown,
OpenDropdown,
} from "components/DropdownArrows/DropdownArrows";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { Pill, type PillType } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
@@ -154,7 +151,7 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
</Stack>
{shouldDisplayDiff ? (
<div> {isDiffOpen ? <CloseDropdown /> : <OpenDropdown />}</div>
<div> {<DropdownArrow close={isDiffOpen} />}</div>
) : (
<div className={styles.columnWithoutDiff}></div>
)}
@@ -1,7 +1,10 @@
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import Link from "@mui/material/Link";
import { FormikContextType, useFormik } from "formik";
import { FC } from "react";
import * as Yup from "yup";
import { hasApiFieldErrors, isApiError } from "api/errors";
import * as TypesGen from "api/typesGenerated";
import {
getFormHelpers,
@@ -12,11 +15,6 @@ import { FormFooter } from "components/FormFooter/FormFooter";
import { FullPageForm } from "components/FullPageForm/FullPageForm";
import { Stack } from "components/Stack/Stack";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { hasApiFieldErrors, isApiError } from "api/errors";
import MenuItem from "@mui/material/MenuItem";
import { makeStyles } from "@mui/styles";
import { Theme } from "@mui/material/styles";
import Link from "@mui/material/Link";
export const Language = {
emailLabel: "Email",
@@ -104,8 +102,6 @@ export const CreateUserForm: FC<
error,
);
const styles = useStyles();
const methods = [
authMethods?.password.enabled && "password",
authMethods?.oidc.enabled && "oidc",
@@ -161,9 +157,21 @@ export const CreateUserForm: FC<
const language = authMethodLanguage[value];
return (
<MenuItem key={value} id={"item-" + value} value={value}>
<Stack spacing={0} maxWidth={400}>
<Stack
spacing={0}
css={{
maxWidth: 400,
}}
>
{language.displayName}
<span className={styles.labelDescription}>
<span
css={(theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
wordWrap: "normal",
whiteSpace: "break-spaces",
})}
>
{language.description}
</span>
</Stack>
@@ -192,12 +200,3 @@ export const CreateUserForm: FC<
</FullPageForm>
);
};
const useStyles = makeStyles<Theme>((theme) => ({
labelDescription: {
fontSize: 14,
color: theme.palette.text.secondary,
wordWrap: "normal",
whiteSpace: "break-spaces",
},
}));
@@ -32,31 +32,6 @@ export const NoIcon: Story = {
},
};
export const SmallViewport: Story = {
args: {
template: MockTemplate,
activeVersion: {
...MockTemplateVersion,
readme: `---
name:Template test
---
## Instructions
You can add instructions here
[Some link info](https://coder.com)
\`\`\`
# This is a really long sentence to test that the code block wraps into a new line properly.
\`\`\`
`,
},
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
},
};
SmallViewport.parameters = {
chromatic: { viewports: [600] },
};
export const WithDeprecatedParameters: Story = {
args: {
template: MockTemplate,
@@ -1,4 +1,6 @@
import {
import { type FC, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import type {
Template,
TemplateVersion,
WorkspaceResource,
@@ -7,8 +9,6 @@ import { Loader } from "components/Loader/Loader";
import { Stack } from "components/Stack/Stack";
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable";
import { TemplateStats } from "./TemplateStats";
import { FC, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings";
export interface TemplateSummaryPageViewProps {
@@ -1,4 +1,6 @@
import {
MockFailedProvisionerJob,
MockRunningProvisionerJob,
MockTemplate,
MockTemplateVersion,
MockTemplateVersionFileTree,
@@ -15,7 +17,7 @@ import { TemplateVersionEditor } from "./TemplateVersionEditor";
import type { Meta, StoryObj } from "@storybook/react";
const meta: Meta<typeof TemplateVersionEditor> = {
title: "components/TemplateVersionEditor",
title: "pages/TemplateVersionEditor",
component: TemplateVersionEditor,
args: {
template: MockTemplate,
@@ -34,12 +36,18 @@ export const Example: Story = {};
export const Logs = {
args: {
isBuildingNewVersion: true,
buildLogs: MockWorkspaceBuildLogs,
templateVersion: {
...MockTemplateVersion,
job: MockRunningProvisionerJob,
},
},
};
export const Resources: Story = {
args: {
isBuildingNewVersion: true,
buildLogs: MockWorkspaceBuildLogs,
resources: [
MockWorkspaceResource,
@@ -54,10 +62,11 @@ export const Resources: Story = {
export const ManyLogs = {
args: {
isBuildingNewVersion: true,
templateVersion: {
...MockTemplateVersion,
job: {
...MockTemplateVersion.job,
...MockFailedProvisionerJob,
error:
"template import provision for start: terraform plan: exit status 1",
},
@@ -1,7 +1,6 @@
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import Link from "@mui/material/Link";
import { makeStyles } from "@mui/styles";
import Tooltip from "@mui/material/Tooltip";
import CreateIcon from "@mui/icons-material/AddOutlined";
import BuildIcon from "@mui/icons-material/BuildOutlined";
@@ -21,7 +20,7 @@ import { AvatarData } from "components/AvatarData/AvatarData";
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable";
import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs";
import { PublishVersionData } from "pages/TemplateVersionEditorPage/types";
import { FC, useCallback, useEffect, useRef, useState } from "react";
import { type FC, useCallback, useEffect, useRef, useState } from "react";
import {
createFile,
existsFile,
@@ -46,17 +45,17 @@ import {
getStatus,
TemplateVersionStatusBadge,
} from "./TemplateVersionStatusBadge";
import { Theme } from "@mui/material/styles";
import AlertTitle from "@mui/material/AlertTitle";
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
export interface TemplateVersionEditorProps {
template: Template;
templateVersion: TemplateVersion;
isBuildingNewVersion: boolean;
defaultFileTree: FileTree;
buildLogs?: ProvisionerJobLog[];
resources?: WorkspaceResource[];
deploymentBannerVisible?: boolean;
disablePreview: boolean;
disableUpdate: boolean;
onPreview: (files: FileTree) => void;
@@ -92,8 +91,8 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
disablePreview,
disableUpdate,
template,
deploymentBannerVisible,
templateVersion,
isBuildingNewVersion,
defaultFileTree,
onPreview,
onPublish,
@@ -111,6 +110,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
onSubmitMissingVariableValues,
onCancelSubmitMissingVariableValues,
}) => {
const theme = useTheme();
// If resources are provided, show them by default!
// This is for Storybook!
const [selectedTab, setSelectedTab] = useState(() => (resources ? 1 : 0));
@@ -170,25 +170,22 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
}, [templateVersion]);
const hasIcon = template.icon && template.icon !== "";
const templateVersionSucceeded = templateVersion.job.status === "succeeded";
const showBuildLogs = Boolean(buildLogs);
const editorValue = getFileContent(activePath ?? "", fileTree) as string;
const firstTemplateVersionOnEditor = useRef(templateVersion);
useEffect(() => {
window.dispatchEvent(new Event("resize"));
}, [showBuildLogs]);
const styles = useStyles({
templateVersionSucceeded,
showBuildLogs,
deploymentBannerVisible,
});
return (
<>
<DashboardFullPage className={styles.root}>
<div className={styles.topbar} data-testid="topbar">
<div className={styles.topbarSides}>
<DashboardFullPage
css={{
background: theme.palette.background.default,
}}
>
<div css={styles.topbar} data-testid="topbar">
<div css={styles.topbarSides}>
<Link
component={RouterLink}
underline="none"
@@ -220,12 +217,9 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
</Alert>
)}
<div className={styles.topbarSides}>
{/* Only start to show the build when a new template version is building */}
{templateVersion.id !== firstTemplateVersionOnEditor.current.id && (
<div className={styles.buildStatus}>
<TemplateVersionStatusBadge version={templateVersion} />
</div>
<div css={styles.topbarSides}>
{isBuildingNewVersion && (
<TemplateVersionStatusBadge version={templateVersion} />
)}
<Button
@@ -248,11 +242,11 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
</div>
</div>
<div className={styles.sidebarAndEditor}>
<div className={styles.sidebar}>
<div className={styles.sidebarTitle}>
<div css={styles.sidebarAndEditor}>
<div css={styles.sidebar}>
<div css={styles.sidebarTitle}>
Template files
<div className={styles.sidebarActions}>
<div css={styles.sidebarActions}>
<Tooltip title="Create File" placement="top">
<IconButton
aria-label="Create File"
@@ -331,8 +325,16 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
/>
</div>
<div className={styles.editorPane}>
<div className={styles.editor} data-chromatic="ignore">
<div
css={{
display: "grid",
width: "100%",
gridTemplateColumns: showBuildLogs ? "1fr 1fr" : "1fr 0fr",
minHeight: "100%",
overflow: "hidden",
}}
>
<div css={styles.editor} data-chromatic="ignore">
{activePath ? (
<MonacoEditor
value={editorValue}
@@ -352,12 +354,11 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
)}
</div>
<div className={styles.panelWrapper}>
<div className={styles.tabs}>
<div css={styles.panelWrapper}>
<div css={styles.tabs}>
<button
className={`${styles.tab} ${
selectedTab === 0 ? "active" : ""
}`}
css={styles.tab}
className={selectedTab === 0 ? "active" : ""}
onClick={() => {
setSelectedTab(0);
}}
@@ -372,9 +373,8 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
{!disableUpdate && (
<button
className={`${styles.tab} ${
selectedTab === 1 ? "active" : ""
}`}
css={styles.tab}
className={selectedTab === 1 ? "active" : ""}
onClick={() => {
setSelectedTab(1);
}}
@@ -386,9 +386,13 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
</div>
<div
className={`${styles.panel} ${styles.buildLogs} ${
selectedTab === 0 ? "" : "hidden"
}`}
css={[
styles.panel,
{
display: selectedTab !== 0 ? "none" : "flex",
flexDirection: "column",
},
]}
>
{templateVersion.job.error && (
<div>
@@ -419,9 +423,13 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
</div>
<div
className={`${styles.panel} ${styles.resources} ${
selectedTab === 1 ? "" : "hidden"
}`}
css={[
styles.panel,
{
paddingBottom: theme.spacing(2),
display: selectedTab !== 1 ? "none" : undefined,
},
]}
>
{resources && (
<TemplateResourcesTable
@@ -432,12 +440,6 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
)}
</div>
</div>
{templateVersionSucceeded && (
<>
<div className={styles.panelDivider} />
</>
)}
</div>
</div>
</DashboardFullPage>
@@ -462,18 +464,8 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
);
};
const useStyles = makeStyles<
Theme,
{
templateVersionSucceeded: boolean;
showBuildLogs: boolean;
deploymentBannerVisible?: boolean;
}
>((theme) => ({
root: {
background: theme.palette.background.default,
},
topbar: {
const styles = {
topbar: (theme) => ({
padding: theme.spacing(2),
borderBottom: `1px solid ${theme.palette.divider}`,
display: "flex",
@@ -481,29 +473,24 @@ const useStyles = makeStyles<
justifyContent: "space-between",
height: topbarHeight,
background: theme.palette.background.paper,
},
topbarSides: {
}),
topbarSides: (theme) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing(2),
},
buildStatus: {
display: "flex",
alignItems: "center",
gap: 8,
},
}),
sidebarAndEditor: {
display: "flex",
flex: 1,
flexBasis: 0,
overflow: "hidden",
},
sidebar: {
sidebar: (theme) => ({
minWidth: 256,
backgroundColor: theme.palette.background.paper,
borderRight: `1px solid ${theme.palette.divider}`,
},
sidebarTitle: {
}),
sidebarTitle: (theme) => ({
fontSize: 10,
textTransform: "uppercase",
padding: theme.spacing(1, 2),
@@ -512,45 +499,33 @@ const useStyles = makeStyles<
letterSpacing: "0.5px",
display: "flex",
alignItems: "center",
},
sidebarActions: {
}),
sidebarActions: (theme) => ({
marginLeft: "auto",
"& svg": {
fill: theme.palette.text.primary,
},
},
editorPane: {
display: "grid",
width: "100%",
gridTemplateColumns: (props) =>
props.showBuildLogs ? "1fr 1fr" : "1fr 0fr",
minHeight: "100%",
overflow: "hidden",
},
}),
editor: {
flex: 1,
},
panelWrapper: {
panelWrapper: (theme) => ({
flex: 1,
borderLeft: `1px solid ${theme.palette.divider}`,
overflow: "hidden",
display: "flex",
flexDirection: "column",
},
}),
panel: {
overflowY: "auto",
height: "100%",
"&.hidden": {
display: "none",
},
// Hack to access customize resource-card from here
"& .resource-card": {
border: 0,
},
},
tabs: {
tabs: (theme) => ({
borderBottom: `1px solid ${theme.palette.divider}`,
display: "flex",
boxShadow: "#000000 0 6px 6px -6px inset",
@@ -561,8 +536,8 @@ const useStyles = makeStyles<
textTransform: "none",
letterSpacing: "unset",
},
},
tab: {
}),
tab: (theme) => ({
cursor: "pointer",
padding: theme.spacing(1.5),
fontSize: 10,
@@ -601,8 +576,8 @@ const useStyles = makeStyles<
"&:hover": {
color: theme.palette.text.primary,
},
},
tabBar: {
}),
tabBar: (theme) => ({
padding: "8px 16px",
position: "sticky",
top: 0,
@@ -615,12 +590,5 @@ const useStyles = makeStyles<
"&.top": {
borderTop: `1px solid ${theme.palette.divider}`,
},
},
buildLogs: {
display: "flex",
flexDirection: "column",
},
resources: {
paddingBottom: theme.spacing(2),
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,7 +1,6 @@
import { useMachine } from "@xstate/react";
import { TemplateVersionEditor } from "./TemplateVersionEditor";
import { useOrganizationId } from "hooks/useOrganizationId";
import { usePermissions } from "hooks/usePermissions";
import { FC } from "react";
import { Helmet } from "react-helmet-async";
import { useNavigate, useParams } from "react-router-dom";
@@ -22,7 +21,6 @@ export const TemplateVersionEditorPage: FC = () => {
const [editorState, sendEvent] = useMachine(templateVersionEditorMachine, {
context: { orgId },
});
const permissions = usePermissions();
const { isSuccess, data } = useTemplateVersionData(
{
orgId,
@@ -45,8 +43,8 @@ export const TemplateVersionEditorPage: FC = () => {
{isSuccess && (
<TemplateVersionEditor
template={data.template}
deploymentBannerVisible={permissions.viewDeploymentStats}
templateVersion={editorState.context.version || data.version}
isBuildingNewVersion={Boolean(editorState.context.version)}
defaultFileTree={data.fileTree}
onPreview={(fileTree) => {
sendEvent({