chore: use emotion for styling (pt. 4) (#10149)

This commit is contained in:
Kayla Washburn
2023-10-10 10:46:45 -06:00
committed by GitHub
parent 00589d6422
commit 7ea58eac18
22 changed files with 519 additions and 638 deletions
@@ -1,10 +1,13 @@
import { makeStyles } from "@mui/styles";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import { type FC } from "react";
import Box from "@mui/material/Box";
import { css } from "@emotion/react";
import type { ClibaseOption } from "api/typesGenerated";
import {
OptionConfig,
OptionConfigFlag,
@@ -12,23 +15,30 @@ import {
OptionName,
OptionValue,
} from "components/DeploySettingsLayout/Option";
import { FC } from "react";
import { optionValue } from "./optionValue";
import Box from "@mui/material/Box";
import { ClibaseOption } from "api/typesGenerated";
const OptionsTable: FC<{
options: ClibaseOption[];
}> = ({ options }) => {
const styles = useStyles();
if (options.length === 0) {
return <p>No options to configure</p>;
}
return (
<TableContainer>
<Table className={styles.table}>
<Table
css={(theme) => css`
& td {
padding-top: ${theme.spacing(3)};
padding-bottom: ${theme.spacing(3)};
}
& td:last-child,
& th:last-child {
padding-left: ${theme.spacing(4)};
}
`}
>
<TableHead>
<TableRow>
<TableCell width="50%">Option</TableCell>
@@ -96,17 +106,4 @@ const OptionsTable: FC<{
);
};
const useStyles = makeStyles((theme) => ({
table: {
"& td": {
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(3),
},
"& td:last-child, & th:last-child": {
paddingLeft: theme.spacing(4),
},
},
}));
export default OptionsTable;
@@ -1,15 +1,12 @@
import DialogActions from "@mui/material/DialogActions";
import { makeStyles } from "@mui/styles";
import { ReactNode, FC, PropsWithChildren } from "react";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FC, type PropsWithChildren, type ReactNode } from "react";
import {
Dialog,
DialogActionButtons,
DialogActionButtonsProps,
} from "../Dialog";
import { ConfirmDialogType } from "../types";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import { Stack } from "@mui/system";
import type { ConfirmDialogType } from "../types";
interface ConfirmDialogTypeConfig {
confirmText: ReactNode;
@@ -58,8 +55,8 @@ export interface ConfirmDialogProps
readonly title: string;
}
const useStyles = makeStyles((theme) => ({
dialogWrapper: {
const styles = {
dialogWrapper: (theme) => ({
"& .MuiPaper-root": {
background: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
@@ -69,19 +66,19 @@ const useStyles = makeStyles((theme) => ({
"& .MuiDialogActions-spacing": {
padding: `0 ${theme.spacing(5)} ${theme.spacing(5)}`,
},
},
dialogContent: {
}),
dialogContent: (theme) => ({
color: theme.palette.text.secondary,
padding: theme.spacing(5),
},
dialogTitle: {
}),
dialogTitle: (theme) => ({
margin: 0,
marginBottom: theme.spacing(2),
color: theme.palette.text.primary,
fontWeight: 400,
fontSize: theme.spacing(2.5),
},
dialogDescription: {
}),
dialogDescription: (theme) => ({
color: theme.palette.text.secondary,
lineHeight: "160%",
fontSize: 16,
@@ -97,8 +94,8 @@ const useStyles = makeStyles((theme) => ({
"& > p": {
margin: theme.spacing(1, 0),
},
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
/**
* Quick-use version of the Dialog component with slightly alternative styles,
@@ -117,8 +114,6 @@ export const ConfirmDialog: FC<PropsWithChildren<ConfirmDialogProps>> = ({
title,
type = "info",
}) => {
const styles = useStyles({ type });
const defaults = CONFIRM_DIALOG_DEFAULTS[type];
if (typeof hideCancel === "undefined") {
@@ -127,16 +122,14 @@ export const ConfirmDialog: FC<PropsWithChildren<ConfirmDialogProps>> = ({
return (
<Dialog
className={styles.dialogWrapper}
css={styles.dialogWrapper}
onClose={onClose}
open={open}
data-testid="dialog"
>
<div className={styles.dialogContent}>
<h3 className={styles.dialogTitle}>{title}</h3>
{description && (
<div className={styles.dialogDescription}>{description}</div>
)}
<div css={styles.dialogContent}>
<h3 css={styles.dialogTitle}>{title}</h3>
{description && <div css={styles.dialogDescription}>{description}</div>}
</div>
<DialogActions>
@@ -154,169 +147,3 @@ export const ConfirmDialog: FC<PropsWithChildren<ConfirmDialogProps>> = ({
</Dialog>
);
};
export interface ScheduleDialogProps extends ConfirmDialogProps {
readonly inactiveWorkspacesToGoDormant: number;
readonly inactiveWorkspacesToGoDormantInWeek: number;
readonly dormantWorkspacesToBeDeleted: number;
readonly dormantWorkspacesToBeDeletedInWeek: number;
readonly updateDormantWorkspaces: (confirm: boolean) => void;
readonly updateInactiveWorkspaces: (confirm: boolean) => void;
readonly dormantValueChanged: boolean;
readonly deletionValueChanged: boolean;
}
export const ScheduleDialog: FC<PropsWithChildren<ScheduleDialogProps>> = ({
cancelText,
confirmLoading,
disabled = false,
hideCancel,
onClose,
onConfirm,
type,
open = false,
title,
inactiveWorkspacesToGoDormant,
inactiveWorkspacesToGoDormantInWeek,
dormantWorkspacesToBeDeleted,
dormantWorkspacesToBeDeletedInWeek,
updateDormantWorkspaces,
updateInactiveWorkspaces,
dormantValueChanged,
deletionValueChanged,
}) => {
const styles = useScheduleStyles({ type });
const defaults = CONFIRM_DIALOG_DEFAULTS["delete"];
if (typeof hideCancel === "undefined") {
hideCancel = defaults.hideCancel;
}
const showDormancyWarning =
dormantValueChanged &&
(inactiveWorkspacesToGoDormant > 0 ||
inactiveWorkspacesToGoDormantInWeek > 0);
const showDeletionWarning =
deletionValueChanged &&
(dormantWorkspacesToBeDeleted > 0 ||
dormantWorkspacesToBeDeletedInWeek > 0);
return (
<Dialog
className={styles.dialogWrapper}
onClose={onClose}
open={open}
data-testid="dialog"
>
<div className={styles.dialogContent}>
<h3 className={styles.dialogTitle}>{title}</h3>
<>
{showDormancyWarning && (
<>
<h4>{"Dormancy Threshold"}</h4>
<Stack direction="row" spacing={5}>
<div className={styles.dialogDescription}>{`
This change will result in ${inactiveWorkspacesToGoDormant} workspaces being immediately transitioned to the dormant state and ${inactiveWorkspacesToGoDormantInWeek} over the next seven days. To prevent this, do you want to reset the inactivity period for all template workspaces?`}</div>
<FormControlLabel
sx={{
marginTop: 2,
}}
control={
<Checkbox
size="small"
onChange={(e) => {
updateInactiveWorkspaces(e.target.checked);
}}
/>
}
label="Reset"
/>
</Stack>
</>
)}
{showDeletionWarning && (
<>
<h4>{"Dormancy Auto-Deletion"}</h4>
<Stack direction="row" spacing={5}>
<div
className={styles.dialogDescription}
>{`This change will result in ${dormantWorkspacesToBeDeleted} workspaces being immediately deleted and ${dormantWorkspacesToBeDeletedInWeek} over the next 7 days. To prevent this, do you want to reset the dormancy period for all template workspaces?`}</div>
<FormControlLabel
sx={{
marginTop: 2,
}}
control={
<Checkbox
size="small"
onChange={(e) => {
updateDormantWorkspaces(e.target.checked);
}}
/>
}
label="Reset"
/>
</Stack>
</>
)}
</>
</div>
<DialogActions>
<DialogActionButtons
cancelText={cancelText}
confirmDialog
confirmLoading={confirmLoading}
confirmText="Submit"
disabled={disabled}
onCancel={!hideCancel ? onClose : undefined}
onConfirm={onConfirm || onClose}
type="delete"
/>
</DialogActions>
</Dialog>
);
};
const useScheduleStyles = makeStyles((theme) => ({
dialogWrapper: {
"& .MuiPaper-root": {
background: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
width: "100%",
maxWidth: theme.spacing(125),
},
"& .MuiDialogActions-spacing": {
padding: `0 ${theme.spacing(5)} ${theme.spacing(5)}`,
},
},
dialogContent: {
color: theme.palette.text.secondary,
padding: theme.spacing(5),
},
dialogTitle: {
margin: 0,
marginBottom: theme.spacing(2),
color: theme.palette.text.primary,
fontWeight: 400,
fontSize: theme.spacing(2.5),
},
dialogDescription: {
color: theme.palette.text.secondary,
lineHeight: "160%",
fontSize: 16,
"& strong": {
color: theme.palette.text.primary,
},
"& p:not(.MuiFormHelperText-root)": {
margin: 0,
},
"& > p": {
margin: theme.spacing(1, 0),
},
},
}));
+13 -16
View File
@@ -1,19 +1,18 @@
import MuiDialog, { DialogProps as MuiDialogProps } from "@mui/material/Dialog";
import { makeStyles } from "@mui/styles";
import * as React from "react";
import { type ReactNode } from "react";
import { colors } from "theme/colors";
import { combineClasses } from "utils/combineClasses";
import {
LoadingButton,
LoadingButtonProps,
} from "../LoadingButton/LoadingButton";
import { ConfirmDialogType } from "./types";
import { type Interpolation, type Theme } from "@emotion/react";
export interface DialogActionButtonsProps {
/** Text to display in the cancel button */
cancelText?: string;
/** Text to display in the confirm button */
confirmText?: React.ReactNode;
confirmText?: ReactNode;
/** Whether or not confirm is loading, also disables cancel when true */
confirmLoading?: boolean;
/** Whether or not this is a confirm dialog */
@@ -46,8 +45,6 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
onConfirm,
type = "info",
}) => {
const styles = useButtonStyles({ type });
return (
<>
{onCancel && (
@@ -65,10 +62,10 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
loading={confirmLoading}
disabled={disabled}
type="submit"
className={combineClasses({
[styles.errorButton]: type === "delete",
[styles.successButton]: type === "success",
})}
css={[
type === "delete" && styles.errorButton,
type === "success" && styles.successButton,
]}
>
{confirmText}
</LoadingButton>
@@ -77,8 +74,8 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
);
};
const useButtonStyles = makeStyles((theme) => ({
errorButton: {
const styles = {
errorButton: (theme) => ({
"&.MuiButton-contained": {
backgroundColor: colors.red[10],
borderColor: colors.red[9],
@@ -95,8 +92,8 @@ const useButtonStyles = makeStyles((theme) => ({
color: colors.red[9],
},
},
},
successButton: {
}),
successButton: (theme) => ({
"&.MuiButton-contained": {
backgroundColor: theme.palette.success.main,
color: theme.palette.primary.contrastText,
@@ -145,8 +142,8 @@ const useButtonStyles = makeStyles((theme) => ({
color: theme.palette.text.secondary,
},
},
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
export type DialogProps = MuiDialogProps;
+14 -17
View File
@@ -1,9 +1,8 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Collapse from "@mui/material/Collapse";
import Link from "@mui/material/Link";
import makeStyles from "@mui/styles/makeStyles";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { type FC, type PropsWithChildren } from "react";
import { combineClasses } from "utils/combineClasses";
export interface ExpanderProps {
expanded: boolean;
@@ -15,29 +14,27 @@ export const Expander: FC<PropsWithChildren<ExpanderProps>> = ({
setExpanded,
children,
}) => {
const styles = useStyles();
const toggleExpanded = () => setExpanded(!expanded);
return (
<>
{!expanded && (
<Link onClick={toggleExpanded} className={styles.expandLink}>
<span className={styles.text}>
<Link onClick={toggleExpanded} css={styles.expandLink}>
<span css={styles.text}>
Click here to learn more
<DropdownArrow margin={false} />
</span>
</Link>
)}
<Collapse in={expanded}>
<div className={styles.text}>{children}</div>
<div css={styles.text}>{children}</div>
</Collapse>
{expanded && (
<Link
onClick={toggleExpanded}
className={combineClasses([styles.expandLink, styles.collapseLink])}
css={[styles.expandLink, styles.collapseLink]}
>
<span className={styles.text}>
<span css={styles.text}>
Click here to hide
<DropdownArrow margin={false} close />
</span>
@@ -47,18 +44,18 @@ export const Expander: FC<PropsWithChildren<ExpanderProps>> = ({
);
};
const useStyles = makeStyles((theme) => ({
expandLink: {
const styles = {
expandLink: (theme) => ({
cursor: "pointer",
color: theme.palette.text.secondary,
},
collapseLink: {
}),
collapseLink: (theme) => ({
marginTop: theme.spacing(2),
},
text: {
}),
text: (theme) => ({
display: "flex",
alignItems: "center",
color: theme.palette.text.secondary,
fontSize: theme.typography.caption.fontSize,
},
}));
}),
} satisfies Record<string, Interpolation<Theme>>;
+75 -98
View File
@@ -1,18 +1,18 @@
import { makeStyles } from "@mui/styles";
import { AlphaBadge } from "components/DeploySettingsLayout/Badges";
import {
FormFooterProps as BaseFormFooterProps,
FormFooter as BaseFormFooter,
} from "components/FormFooter/FormFooter";
import { Stack } from "components/Stack/Stack";
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import {
createContext,
FC,
HTMLProps,
PropsWithChildren,
type FC,
type HTMLProps,
type PropsWithChildren,
useContext,
} from "react";
import { combineClasses } from "utils/combineClasses";
import { AlphaBadge } from "components/DeploySettingsLayout/Badges";
import { Stack } from "components/Stack/Stack";
import {
FormFooter as BaseFormFooter,
FormFooterProps,
type FormFooterStyles,
} from "../FormFooter/FormFooter";
type FormContextValue = { direction?: "horizontal" | "vertical" };
@@ -24,14 +24,22 @@ type FormProps = HTMLProps<HTMLFormElement> & {
direction?: FormContextValue["direction"];
};
export const Form: FC<FormProps> = ({ direction, className, ...formProps }) => {
const styles = useStyles({ direction });
export const Form: FC<FormProps> = ({ direction, ...formProps }) => {
const theme = useTheme();
return (
<FormContext.Provider value={{ direction }}>
<form
{...formProps}
className={combineClasses([styles.form, className])}
css={{
display: "flex",
flexDirection: "column",
gap: theme.spacing(direction === "horizontal" ? 10 : 5),
[theme.breakpoints.down("md")]: {
gap: theme.spacing(8),
},
}}
/>
</FormContext.Provider>
);
@@ -71,28 +79,50 @@ export const FormSection: FC<
alpha?: boolean;
}
> = ({ children, title, description, classes = {}, alpha = false }) => {
const formContext = useContext(FormContext);
const styles = useStyles(formContext);
const { direction } = useContext(FormContext);
const theme = useTheme();
return (
<div className={combineClasses([styles.formSection, classes.root])}>
<div
css={{
display: "flex",
alignItems: "flex-start",
flexDirection: direction === "horizontal" ? "row" : "column",
gap: theme.spacing(direction === "horizontal" ? 15 : 3),
[theme.breakpoints.down("md")]: {
flexDirection: "column",
gap: theme.spacing(2),
},
}}
className={classes.root}
>
<div
className={combineClasses([
classes.sectionInfo,
styles.formSectionInfo,
])}
css={{
width: "100%",
maxWidth: direction === "horizontal" ? 312 : undefined,
flexShrink: 0,
position: direction === "horizontal" ? "sticky" : undefined,
top: theme.spacing(3),
[theme.breakpoints.down("md")]: {
width: "100%",
position: "initial" as const,
},
}}
className={classes.sectionInfo}
>
<h2
className={combineClasses([
css={[
styles.formSectionInfoTitle,
alpha && styles.formSectionInfoTitleAlpha,
classes.infoTitle,
])}
]}
className={classes.infoTitle}
>
{title}
{alpha && <AlphaBadge />}
</h2>
<div className={styles.formSectionInfoDescription}>{description}</div>
<div css={styles.formSectionInfoDescription}>{description}</div>
</div>
{children}
@@ -101,108 +131,55 @@ export const FormSection: FC<
};
export const FormFields: FC<PropsWithChildren> = ({ children }) => {
const styles = useStyles();
return (
<Stack
direction="column"
spacing={2.5}
className={styles.formSectionFields}
>
<Stack direction="column" spacing={2.5} css={styles.formSectionFields}>
{children}
</Stack>
);
};
export const FormFooter: FC<BaseFormFooterProps> = (props) => {
const formFooterStyles = useFormFooterStyles();
return (
<BaseFormFooter
{...props}
styles={{ ...formFooterStyles, ...props.styles }}
/>
);
};
const getFlexDirection = ({ direction }: FormContextValue = {}):
| "row"
| "column" =>
direction === "horizontal" ? ("row" as const) : ("column" as const);
const useStyles = makeStyles((theme) => ({
form: {
display: "flex",
flexDirection: "column",
gap: ({ direction }: FormContextValue = {}) =>
direction === "horizontal" ? theme.spacing(10) : theme.spacing(5),
[theme.breakpoints.down("md")]: {
gap: theme.spacing(8),
},
},
formSection: {
display: "flex",
alignItems: "flex-start",
gap: ({ direction }: FormContextValue = {}) =>
direction === "horizontal" ? theme.spacing(15) : theme.spacing(3),
flexDirection: getFlexDirection,
[theme.breakpoints.down("md")]: {
flexDirection: "column",
gap: theme.spacing(2),
},
},
formSectionInfo: {
width: "100%",
maxWidth: ({ direction }: FormContextValue = {}) =>
direction === "horizontal" ? 312 : undefined,
flexShrink: 0,
position: ({ direction }: FormContextValue = {}) =>
direction === "horizontal" ? "sticky" : undefined,
top: theme.spacing(3),
[theme.breakpoints.down("md")]: {
width: "100%",
position: "initial" as const,
},
},
formSectionInfoTitle: {
const styles = {
formSectionInfoTitle: (theme) => ({
fontSize: 20,
color: theme.palette.text.primary,
fontWeight: 400,
margin: 0,
marginBottom: theme.spacing(1),
},
}),
formSectionInfoTitleAlpha: {
formSectionInfoTitleAlpha: (theme) => ({
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: theme.spacing(1.5),
},
}),
formSectionInfoDescription: {
formSectionInfoDescription: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
lineHeight: "160%",
margin: 0,
},
}),
formSectionFields: {
width: "100%",
},
}));
} satisfies Record<string, Interpolation<Theme>>;
const useFormFooterStyles = makeStyles((theme) => ({
button: {
export const FormFooter = (props: Exclude<FormFooterProps, "styles">) => (
<BaseFormFooter {...props} styles={footerStyles} />
);
const footerStyles = {
button: (theme) => ({
minWidth: theme.spacing(23),
[theme.breakpoints.down("md")]: {
width: "100%",
},
},
footer: {
}),
footer: (theme) => ({
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
@@ -213,5 +190,5 @@ const useFormFooterStyles = makeStyles((theme) => ({
flexDirection: "column",
gap: theme.spacing(1),
},
},
}));
}),
} satisfies FormFooterStyles;
+15 -12
View File
@@ -1,15 +1,18 @@
import Button from "@mui/material/Button";
import { makeStyles } from "@mui/styles";
import { ClassNameMap } from "@mui/styles/withStyles";
import { FC } from "react";
import { type FC } from "react";
import { LoadingButton } from "../LoadingButton/LoadingButton";
import { Interpolation, Theme } from "@emotion/react";
export const Language = {
cancelLabel: "Cancel",
defaultSubmitLabel: "Submit",
};
type FormFooterStyles = ClassNameMap<"footer" | "button">;
export interface FormFooterStyles {
footer: Interpolation<Theme>;
button: Interpolation<Theme>;
}
export interface FormFooterProps {
onCancel: () => void;
isLoading: boolean;
@@ -23,15 +26,15 @@ export const FormFooter: FC<FormFooterProps> = ({
isLoading,
submitDisabled,
submitLabel = Language.defaultSubmitLabel,
styles = defaultStyles(),
styles = defaultStyles,
}) => {
return (
<div className={styles.footer}>
<div css={styles.footer}>
<LoadingButton
size="large"
tabIndex={0}
loading={isLoading}
className={styles.button}
css={styles.button}
variant="contained"
color="primary"
type="submit"
@@ -43,7 +46,7 @@ export const FormFooter: FC<FormFooterProps> = ({
<Button
size="large"
type="button"
className={styles.button}
css={styles.button}
onClick={onCancel}
tabIndex={0}
>
@@ -53,8 +56,8 @@ export const FormFooter: FC<FormFooterProps> = ({
);
};
const defaultStyles = makeStyles((theme) => ({
footer: {
const defaultStyles = {
footer: (theme) => ({
display: "flex",
flex: "0",
// The first button is the submit so it is the first element to be focused
@@ -63,8 +66,8 @@ const defaultStyles = makeStyles((theme) => ({
gap: theme.spacing(1.5),
alignItems: "center",
marginTop: theme.spacing(3),
},
}),
button: {
width: "100%",
},
}));
} satisfies FormFooterStyles;
+15 -19
View File
@@ -1,26 +1,22 @@
import CircularProgress from "@mui/material/CircularProgress";
import { makeStyles } from "@mui/styles";
import { FC } from "react";
export const useStyles = makeStyles((theme) => ({
root: {
position: "absolute",
top: "0",
left: "0",
right: "0",
bottom: "0",
display: "flex",
justifyContent: "center",
alignItems: "center",
background: theme.palette.background.default,
},
}));
import type { FC } from "react";
export const FullScreenLoader: FC = () => {
const styles = useStyles();
return (
<div className={styles.root} data-testid="loader">
<div
css={(theme) => ({
position: "absolute",
top: "0",
left: "0",
right: "0",
bottom: "0",
display: "flex",
justifyContent: "center",
alignItems: "center",
background: theme.palette.background.default,
})}
data-testid="loader"
>
<CircularProgress />
</div>
);
+8 -14
View File
@@ -1,6 +1,4 @@
import { makeStyles } from "@mui/styles";
import { FC } from "react";
import { combineClasses } from "utils/combineClasses";
import { type FC } from "react";
import {
containerWidth,
containerWidthMedium,
@@ -15,24 +13,20 @@ const widthBySize: Record<Size, number> = {
small: containerWidth / 3,
};
const useStyles = makeStyles(() => ({
margins: {
margin: "0 auto",
maxWidth: ({ maxWidth }: { maxWidth: number }) => maxWidth,
padding: `0 ${sidePadding}px`,
width: "100%",
},
}));
export const Margins: FC<JSX.IntrinsicElements["div"] & { size?: Size }> = ({
size = "regular",
...divProps
}) => {
const styles = useStyles({ maxWidth: widthBySize[size] });
const maxWidth = widthBySize[size];
return (
<div
{...divProps}
className={combineClasses([styles.margins, divProps.className])}
css={{
margin: "0 auto",
maxWidth: maxWidth,
padding: `0 ${sidePadding}px`,
width: "100%",
}}
/>
);
};
+3 -11
View File
@@ -1,6 +1,5 @@
import { useRef, useState, FC } from "react";
import { makeStyles } from "@mui/styles";
import { WorkspaceAgent } from "api/typesGenerated";
import { type FC, useRef, useState } from "react";
import type { WorkspaceAgent } from "api/typesGenerated";
import { getDisplayVersionStatus } from "utils/workspace";
import { AgentOutdatedTooltip } from "./AgentOutdatedTooltip";
@@ -9,7 +8,6 @@ export const AgentVersion: FC<{
serverVersion: string;
onUpdate: () => void;
}> = ({ agent, serverVersion, onUpdate }) => {
const styles = useStyles();
const anchorRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState(false);
const id = isOpen ? "version-outdated-popover" : undefined;
@@ -27,7 +25,7 @@ export const AgentVersion: FC<{
ref={anchorRef}
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
className={styles.trigger}
css={{ cursor: "pointer" }}
>
Outdated
</span>
@@ -44,9 +42,3 @@ export const AgentVersion: FC<{
</>
);
};
const useStyles = makeStyles(() => ({
trigger: {
cursor: "pointer",
},
}));
+17 -27
View File
@@ -3,12 +3,10 @@
* verbatim port from `@coder/ui`.
*/
import { makeStyles } from "@mui/styles";
import MuiTypography, {
TypographyProps as MuiTypographyProps,
} from "@mui/material/Typography";
import * as React from "react";
import { appendCSSString, combineClasses } from "utils/combineClasses";
export interface TypographyProps extends MuiTypographyProps {
short?: boolean;
@@ -20,29 +18,21 @@ export interface TypographyProps extends MuiTypographyProps {
*
* See original component's Material UI documentation here: https://material-ui.com/components/typography/
*/
export const Typography: React.FC<TypographyProps> = ({
className,
short,
...rest
}) => {
const styles = useStyles();
let classes = combineClasses({ [styles.short]: short });
if (className) {
classes = appendCSSString(classes ?? "", className);
}
return <MuiTypography {...rest} className={classes} />;
export const Typography: React.FC<TypographyProps> = ({ short, ...attrs }) => {
return (
<MuiTypography
css={[
short && {
"&.MuiTypography-body1": {
lineHeight: "21px",
},
"&.MuiTypography-body2": {
lineHeight: "18px",
letterSpacing: 0.2,
},
},
]}
{...attrs}
/>
);
};
const useStyles = makeStyles({
short: {
"&.MuiTypography-body1": {
lineHeight: "21px",
},
"&.MuiTypography-body2": {
lineHeight: "18px",
letterSpacing: 0.2,
},
},
});
@@ -1,14 +1,14 @@
import CircularProgress from "@mui/material/CircularProgress";
import { makeStyles } from "@mui/styles";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";
import { useMachine } from "@xstate/react";
import { Group, User } from "api/typesGenerated";
import Box from "@mui/material/Box";
import { type ChangeEvent, useState } from "react";
import { css } from "@emotion/react";
import type { Group, User } from "api/typesGenerated";
import { AvatarData } from "components/AvatarData/AvatarData";
import { ChangeEvent, useState } from "react";
import { getGroupSubtitle } from "utils/groups";
import { searchUsersAndGroupsMachine } from "xServices/template/searchUsersAndGroupsXService";
import Box from "@mui/material/Box";
import { useDebouncedFunction } from "hooks/debounce";
export type UserOrGroupAutocompleteValue = User | Group | null;
@@ -25,10 +25,21 @@ export type UserOrGroupAutocompleteProps = {
exclude: UserOrGroupAutocompleteValue[];
};
const autoCompleteStyles = css`
width: 300px;
& .MuiFormControl-root {
width: 100%;
}
& .MuiInputBase-root {
width: 100%;
}
`;
export const UserOrGroupAutocomplete: React.FC<
UserOrGroupAutocompleteProps
> = ({ value, onChange, organizationId, templateID, exclude }) => {
const styles = useStyles();
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false);
const [searchState, sendSearch] = useMachine(searchUsersAndGroupsMachine, {
context: {
@@ -92,7 +103,7 @@ export const UserOrGroupAutocomplete: React.FC<
}}
options={options}
loading={searchState.matches("searching")}
className={styles.autocomplete}
css={autoCompleteStyles}
renderInput={(params) => (
<>
<TextField
@@ -118,19 +129,3 @@ export const UserOrGroupAutocomplete: React.FC<
/>
);
};
export const useStyles = makeStyles(() => {
return {
autocomplete: {
width: "300px",
"& .MuiFormControl-root": {
width: "100%",
},
"& .MuiInputBase-root": {
width: "100%",
},
},
};
});
@@ -1,5 +1,12 @@
import RefreshIcon from "@mui/icons-material/Refresh";
import { FC } from "react";
import { type FC } from "react";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { useQuery } from "react-query";
import Box from "@mui/material/Box";
import Skeleton from "@mui/material/Skeleton";
import Link from "@mui/material/Link";
import { css } from "@emotion/css";
import { templateVersion } from "api/queries/templates";
import {
HelpTooltip,
HelpTooltipAction,
@@ -7,14 +14,7 @@ import {
HelpTooltipText,
HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { makeStyles } from "@mui/styles";
import { colors } from "theme/colors";
import { useQuery } from "react-query";
import { templateVersion } from "api/queries/templates";
import Box from "@mui/material/Box";
import Skeleton from "@mui/material/Skeleton";
import Link from "@mui/material/Link";
export const Language = {
outdatedLabel: "Outdated",
@@ -36,15 +36,22 @@ export const WorkspaceOutdatedTooltip: FC<TooltipProps> = ({
latestVersionId,
templateName,
}) => {
const styles = useStyles();
const { data: activeVersion } = useQuery(templateVersion(latestVersionId));
return (
<HelpTooltip
size="small"
icon={InfoIcon}
iconClassName={styles.icon}
buttonClassName={styles.button}
iconClassName={css`
color: ${colors.yellow[5]};
`}
buttonClassName={css`
opacity: 1;
&:hover {
opacity: 1;
}
`}
>
<HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle>
<HelpTooltipText>{Language.versionTooltipText}</HelpTooltipText>
@@ -117,17 +124,3 @@ export const WorkspaceOutdatedTooltip: FC<TooltipProps> = ({
</HelpTooltip>
);
};
const useStyles = makeStyles(() => ({
icon: {
color: colors.yellow[5],
},
button: {
opacity: 1,
"&:hover": {
opacity: 1,
},
},
}));
@@ -1,25 +1,25 @@
import { FC, useState, useEffect } from "react";
import { type FC, useState, useEffect } from "react";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import type { FormikContextType } from "formik";
import dayjs from "dayjs";
import { useNavigate } from "react-router-dom";
import {
FormFields,
FormSection,
FormFooter,
HorizontalForm,
} from "components/Form/Form";
import makeStyles from "@mui/styles/makeStyles";
import { Stack } from "components/Stack/Stack";
import { onChangeTrimmed, getFormHelpers } from "utils/formUtils";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import {
NANO_HOUR,
CreateTokenData,
type CreateTokenData,
determineDefaultLtValue,
filterByMaxTokenLifetime,
customLifetimeDay,
} from "./utils";
import { FormikContextType } from "formik";
import dayjs from "dayjs";
import { useNavigate } from "react-router-dom";
import { Stack } from "components/Stack/Stack";
import { css } from "@emotion/css";
interface CreateTokenFormProps {
form: FormikContextType<CreateTokenData>;
@@ -30,6 +30,10 @@ interface CreateTokenFormProps {
creationFailed: boolean;
}
const sectionInfoStyles = css`
min-width: 300px;
`;
export const CreateTokenForm: FC<CreateTokenFormProps> = ({
form,
maxTokenLifetime,
@@ -38,7 +42,6 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
isCreating,
creationFailed,
}) => {
const styles = useStyles();
const navigate = useNavigate();
const [expDays, setExpDays] = useState<number>(1);
@@ -62,7 +65,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
<FormSection
title="Name"
description="What is this token for?"
classes={{ sectionInfo: styles.formSectionInfo }}
classes={{ sectionInfo: sectionInfoStyles }}
>
<FormFields>
<TextField
@@ -85,7 +88,7 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
.format("MMMM DD, YYYY")}`
: "Please set a token expiration."
}
classes={{ sectionInfo: styles.formSectionInfo }}
classes={{ sectionInfo: sectionInfoStyles }}
>
<FormFields>
<Stack direction="row">
@@ -149,9 +152,3 @@ export const CreateTokenForm: FC<CreateTokenFormProps> = ({
</HorizontalForm>
);
};
const useStyles = makeStyles(() => ({
formSectionInfo: {
minWidth: "300px",
},
}));
@@ -2,10 +2,9 @@ import ReplayIcon from "@mui/icons-material/Replay";
import Button from "@mui/material/Button";
import FormHelperText from "@mui/material/FormHelperText";
import Tooltip from "@mui/material/Tooltip";
import { makeStyles } from "@mui/styles";
import { type FC } from "react";
import { LoadingButton } from "components/LoadingButton/LoadingButton";
import { Stack } from "components/Stack/Stack";
import { FC } from "react";
import { type ExternalAuthPollingState } from "./CreateWorkspacePage";
export interface ExternalAuthProps {
@@ -29,10 +28,6 @@ export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
error,
} = props;
const styles = useStyles({
error: typeof error !== "undefined",
});
return (
<Tooltip
title={authenticated && `${displayName} has already been connected.`}
@@ -52,7 +47,7 @@ export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
/>
}
disabled={authenticated}
className={styles.button}
css={{ height: 52 }}
color={error ? "error" : undefined}
fullWidth
onClick={(event) => {
@@ -80,9 +75,3 @@ export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
</Tooltip>
);
};
const useStyles = makeStyles(() => ({
button: {
height: 52,
},
}));
+3 -11
View File
@@ -1,20 +1,19 @@
import Box from "@mui/material/Box";
import Checkbox from "@mui/material/Checkbox";
import { makeStyles } from "@mui/styles";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import { LoadingButton } from "components/LoadingButton/LoadingButton";
import { SignInLayout } from "components/SignInLayout/SignInLayout";
import { Stack } from "components/Stack/Stack";
import { Welcome } from "components/Welcome/Welcome";
import { FormikContextType, useFormik } from "formik";
import { type FormikContextType, useFormik } from "formik";
import {
getFormHelpers,
nameValidator,
onChangeTrimmed,
} from "utils/formUtils";
import * as Yup from "yup";
import * as TypesGen from "api/typesGenerated";
import type * as TypesGen from "api/typesGenerated";
export const Language = {
emailLabel: "Email",
@@ -62,7 +61,6 @@ export const SetupPageView: React.FC<SetupPageViewProps> = ({
form,
error,
);
const styles = useStyles();
return (
<SignInLayout>
@@ -91,7 +89,7 @@ export const SetupPageView: React.FC<SetupPageViewProps> = ({
label={Language.passwordLabel}
type="password"
/>
<div className={styles.callout}>
<div css={{ borderRadius: 16 }}>
<Box display="flex">
<div>
<Checkbox
@@ -128,9 +126,3 @@ export const SetupPageView: React.FC<SetupPageViewProps> = ({
</SignInLayout>
);
};
const useStyles = makeStyles(() => ({
callout: {
borderRadius: 16,
},
}));
@@ -1,8 +1,7 @@
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { ComponentProps, FC } from "react";
import { TemplateSettingsForm } from "./TemplateSettingsForm";
import { type ComponentProps, type FC } from "react";
import type { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { makeStyles } from "@mui/styles";
import { TemplateSettingsForm } from "./TemplateSettingsForm";
export interface TemplateSettingsPageViewProps {
template: Template;
@@ -23,11 +22,9 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
submitError,
initialTouched,
}) => {
const styles = useStyles();
return (
<>
<PageHeader className={styles.pageHeader}>
<PageHeader css={{ paddingTop: 0 }}>
<PageHeaderTitle>General Settings</PageHeaderTitle>
</PageHeader>
@@ -42,9 +39,3 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
</>
);
};
const useStyles = makeStyles(() => ({
pageHeader: {
paddingTop: 0,
},
}));
@@ -1,6 +1,5 @@
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectProps } from "@mui/material/Select";
import { makeStyles } from "@mui/styles";
import Select, { type SelectProps } from "@mui/material/Select";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
@@ -8,7 +7,8 @@ import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import PersonAdd from "@mui/icons-material/PersonAdd";
import {
import { type Interpolation, type Theme } from "@emotion/react";
import type {
Group,
TemplateACL,
TemplateGroup,
@@ -26,7 +26,7 @@ import {
UserOrGroupAutocomplete,
UserOrGroupAutocompleteValue,
} from "components/UserOrGroupAutocomplete/UserOrGroupAutocomplete";
import { FC, useState } from "react";
import { type FC, useState } from "react";
import { GroupAvatar } from "components/GroupAvatar/GroupAvatar";
import { getGroupSubtitle } from "utils/groups";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
@@ -50,7 +50,6 @@ const AddTemplateUserOrGroup: React.FC<AddTemplateUserOrGroupProps> = ({
templateID,
templateACL,
}) => {
const styles = useStyles();
const [selectedOption, setSelectedOption] =
useState<UserOrGroupAutocompleteValue>(null);
const [selectedRole, setSelectedRole] = useState<TemplateRole>("use");
@@ -94,7 +93,7 @@ const AddTemplateUserOrGroup: React.FC<AddTemplateUserOrGroupProps> = ({
<Select
defaultValue="use"
size="small"
className={styles.select}
css={styles.select}
disabled={isLoading}
onChange={(event) => {
setSelectedRole(event.target.value as TemplateRole);
@@ -122,26 +121,24 @@ const AddTemplateUserOrGroup: React.FC<AddTemplateUserOrGroupProps> = ({
};
const RoleSelect: FC<SelectProps> = (props) => {
const styles = useStyles();
return (
<Select
renderValue={(value) => <div className={styles.role}>{`${value}`}</div>}
className={styles.updateSelect}
renderValue={(value) => <div css={styles.role}>{`${value}`}</div>}
css={styles.updateSelect}
{...props}
>
<MenuItem key="use" value="use" className={styles.menuItem}>
<MenuItem key="use" value="use" css={styles.menuItem}>
<div>
<div>Use</div>
<div className={styles.menuItemSecondary}>
<div css={styles.menuItemSecondary}>
Can read and use this template to create workspaces.
</div>
</div>
</MenuItem>
<MenuItem key="admin" value="admin" className={styles.menuItem}>
<MenuItem key="admin" value="admin" css={styles.menuItem}>
<div>
<div>Admin</div>
<div className={styles.menuItemSecondary}>
<div css={styles.menuItemSecondary}>
Can modify all aspects of this template including permissions,
metadata, and template versions.
</div>
@@ -198,7 +195,6 @@ export const TemplatePermissionsPageView: FC<
onUpdateGroup,
onRemoveGroup,
}) => {
const styles = useStyles();
const isEmpty = Boolean(
templateACL &&
templateACL.users.length === 0 &&
@@ -207,7 +203,7 @@ export const TemplatePermissionsPageView: FC<
return (
<>
<PageHeader className={styles.pageHeader}>
<PageHeader css={styles.pageHeader}>
<PageHeaderTitle>Permissions</PageHeaderTitle>
</PageHeader>
@@ -281,7 +277,7 @@ export const TemplatePermissionsPageView: FC<
/>
</Cond>
<Cond>
<div className={styles.role}>{group.role}</div>
<div css={styles.role}>{group.role}</div>
</Cond>
</ChooseOne>
</TableCell>
@@ -329,7 +325,7 @@ export const TemplatePermissionsPageView: FC<
/>
</Cond>
<Cond>
<div className={styles.role}>{user.role}</div>
<div css={styles.role}>{user.role}</div>
</Cond>
</ChooseOne>
</TableCell>
@@ -360,14 +356,14 @@ export const TemplatePermissionsPageView: FC<
);
};
export const useStyles = makeStyles((theme) => ({
const styles = {
select: {
// Match button small height
fontSize: 14,
width: 100,
},
updateSelect: {
updateSelect: (theme) => ({
margin: 0,
// Set a fixed width for the select. It avoids selects having different sizes
// depending on how many roles they have selected.
@@ -382,26 +378,26 @@ export const useStyles = makeStyles((theme) => ({
display: "none",
},
},
},
}),
role: {
textTransform: "capitalize",
},
menuItem: {
menuItem: (theme) => ({
lineHeight: "140%",
paddingTop: theme.spacing(1.5),
paddingBottom: theme.spacing(1.5),
whiteSpace: "normal",
inlineSize: "250px",
},
}),
menuItemSecondary: {
menuItemSecondary: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
},
}),
pageHeader: {
paddingTop: 0,
},
}));
} satisfies Record<string, Interpolation<Theme>>;
@@ -0,0 +1,174 @@
import DialogActions from "@mui/material/DialogActions";
import { type FC, type PropsWithChildren } from "react";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import { Stack } from "@mui/system";
import { type Interpolation, type Theme } from "@emotion/react";
import { Dialog, DialogActionButtons } from "components/Dialogs/Dialog";
import { type ConfirmDialogProps } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
export interface ScheduleDialogProps extends ConfirmDialogProps {
readonly inactiveWorkspacesToGoDormant: number;
readonly inactiveWorkspacesToGoDormantInWeek: number;
readonly dormantWorkspacesToBeDeleted: number;
readonly dormantWorkspacesToBeDeletedInWeek: number;
readonly updateDormantWorkspaces: (confirm: boolean) => void;
readonly updateInactiveWorkspaces: (confirm: boolean) => void;
readonly dormantValueChanged: boolean;
readonly deletionValueChanged: boolean;
}
export const ScheduleDialog: FC<PropsWithChildren<ScheduleDialogProps>> = ({
cancelText,
confirmLoading,
disabled = false,
hideCancel,
onClose,
onConfirm,
open = false,
title,
inactiveWorkspacesToGoDormant,
inactiveWorkspacesToGoDormantInWeek,
dormantWorkspacesToBeDeleted,
dormantWorkspacesToBeDeletedInWeek,
updateDormantWorkspaces,
updateInactiveWorkspaces,
dormantValueChanged,
deletionValueChanged,
}) => {
const defaults = {
confirmText: "Delete",
hideCancel: false,
};
if (typeof hideCancel === "undefined") {
hideCancel = defaults.hideCancel;
}
const showDormancyWarning =
dormantValueChanged &&
(inactiveWorkspacesToGoDormant > 0 ||
inactiveWorkspacesToGoDormantInWeek > 0);
const showDeletionWarning =
deletionValueChanged &&
(dormantWorkspacesToBeDeleted > 0 ||
dormantWorkspacesToBeDeletedInWeek > 0);
return (
<Dialog
css={styles.dialogWrapper}
onClose={onClose}
open={open}
data-testid="dialog"
>
<div css={styles.dialogContent}>
<h3 css={styles.dialogTitle}>{title}</h3>
<>
{showDormancyWarning && (
<>
<h4>{"Dormancy Threshold"}</h4>
<Stack direction="row" spacing={5}>
<div css={styles.dialogDescription}>{`
This change will result in ${inactiveWorkspacesToGoDormant} workspaces being immediately transitioned to the dormant state and ${inactiveWorkspacesToGoDormantInWeek} over the next seven days. To prevent this, do you want to reset the inactivity period for all template workspaces?`}</div>
<FormControlLabel
sx={{
marginTop: 2,
}}
control={
<Checkbox
size="small"
onChange={(e) => {
updateInactiveWorkspaces(e.target.checked);
}}
/>
}
label="Reset"
/>
</Stack>
</>
)}
{showDeletionWarning && (
<>
<h4>{"Dormancy Auto-Deletion"}</h4>
<Stack direction="row" spacing={5}>
<div
css={styles.dialogDescription}
>{`This change will result in ${dormantWorkspacesToBeDeleted} workspaces being immediately deleted and ${dormantWorkspacesToBeDeletedInWeek} over the next 7 days. To prevent this, do you want to reset the dormancy period for all template workspaces?`}</div>
<FormControlLabel
sx={{
marginTop: 2,
}}
control={
<Checkbox
size="small"
onChange={(e) => {
updateDormantWorkspaces(e.target.checked);
}}
/>
}
label="Reset"
/>
</Stack>
</>
)}
</>
</div>
<DialogActions>
<DialogActionButtons
cancelText={cancelText}
confirmDialog
confirmLoading={confirmLoading}
confirmText="Submit"
disabled={disabled}
onCancel={!hideCancel ? onClose : undefined}
onConfirm={onConfirm || onClose}
type="delete"
/>
</DialogActions>
</Dialog>
);
};
const styles = {
dialogWrapper: (theme) => ({
"& .MuiPaper-root": {
background: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
width: "100%",
maxWidth: theme.spacing(125),
},
"& .MuiDialogActions-spacing": {
padding: `0 ${theme.spacing(5)} ${theme.spacing(5)}`,
},
}),
dialogContent: (theme) => ({
color: theme.palette.text.secondary,
padding: theme.spacing(5),
}),
dialogTitle: (theme) => ({
margin: 0,
marginBottom: theme.spacing(2),
color: theme.palette.text.primary,
fontWeight: 400,
fontSize: theme.spacing(2.5),
}),
dialogDescription: (theme) => ({
color: theme.palette.text.secondary,
lineHeight: "160%",
fontSize: 16,
"& strong": {
color: theme.palette.text.primary,
},
"& p:not(.MuiFormHelperText-root)": {
margin: 0,
},
"& > p": {
margin: theme.spacing(1, 0),
},
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,8 +1,15 @@
import TextField from "@mui/material/TextField";
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
import MenuItem from "@mui/material/MenuItem";
import Link from "@mui/material/Link";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { FormikTouched, useFormik } from "formik";
import { FC, ChangeEvent, useState, useEffect } from "react";
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { getFormHelpers } from "utils/formUtils";
import { docs } from "utils/docs";
import { calculateAutostopRequirementDaysValue } from "utils/schedule";
import {
FormSection,
HorizontalForm,
@@ -10,11 +17,6 @@ import {
FormFields,
} from "components/Form/Form";
import { Stack } from "components/Stack/Stack";
import { makeStyles } from "@mui/styles";
import Link from "@mui/material/Link";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import {
useWorkspacesToGoDormant,
useWorkspacesToBeDeleted,
@@ -27,15 +29,13 @@ import {
FailureTTLHelperText,
MaxTTLHelperText,
} from "./TTLHelperText";
import { docs } from "utils/docs";
import { ScheduleDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import MenuItem from "@mui/material/MenuItem";
import { ScheduleDialog } from "./ScheduleDialog";
import {
AutostopRequirementDaysHelperText,
AutostopRequirementWeeksHelperText,
convertAutostopRequirementDaysValue,
} from "./AutostopRequirementHelperText";
import { calculateAutostopRequirementDaysValue } from "utils/schedule";
import { useTheme } from "@emotion/react";
const MS_HOUR_CONVERSION = 3600000;
const MS_DAY_CONVERSION = 86400000;
@@ -143,7 +143,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
form,
error,
);
const styles = useStyles();
const theme = useTheme();
const now = new Date();
const weekFromNow = new Date(now);
@@ -313,7 +313,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
title="Schedule"
description="Define when workspaces created from this template are stopped."
>
<Stack direction="row" className={styles.ttlFields}>
<Stack direction="row" css={styles.ttlFields}>
<TextField
{...getFieldHelpers(
"default_ttl_ms",
@@ -354,7 +354,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
title="Autostop Requirement"
description="Define when workspaces created from this template are stopped periodically to enforce template updates and ensure idle workspaces are stopped."
>
<Stack direction="row" className={styles.ttlFields}>
<Stack direction="row" css={styles.ttlFields}>
<TextField
{...getFieldHelpers(
"autostop_requirement_days_of_week",
@@ -448,7 +448,12 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
<strong>
Allow users to customize autostop duration for workspaces.
</strong>
<span className={styles.optionDescription}>
<span
css={{
fontSize: 12,
color: theme.palette.text.secondary,
}}
>
Workspaces will always use the default TTL if this is set.
Regardless of this setting, workspaces can only stay on for the
max lifetime.
@@ -614,12 +619,8 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
);
};
const useStyles = makeStyles((theme) => ({
const styles = {
ttlFields: {
width: "100%",
},
optionDescription: {
fontSize: 12,
color: theme.palette.text.secondary,
},
}));
};
@@ -1,8 +1,7 @@
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { ComponentProps, FC } from "react";
import { TemplateScheduleForm } from "./TemplateScheduleForm";
import { type ComponentProps, type FC } from "react";
import type { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { makeStyles } from "@mui/styles";
import { TemplateScheduleForm } from "./TemplateScheduleForm";
export interface TemplateSchedulePageViewProps {
template: Template;
@@ -27,11 +26,9 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
submitError,
initialTouched,
}) => {
const styles = useStyles();
return (
<>
<PageHeader className={styles.pageHeader}>
<PageHeader css={{ paddingTop: 0 }}>
<PageHeaderTitle>Template schedule</PageHeaderTitle>
</PageHeader>
@@ -48,9 +45,3 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
</>
);
};
const useStyles = makeStyles(() => ({
pageHeader: {
paddingTop: 0,
},
}));
@@ -1,22 +1,23 @@
import { DialogProps } from "components/Dialogs/Dialog";
import { FC, useRef, useState } from "react";
import { FormFields } from "components/Form/Form";
import { type FC, useRef, useState } from "react";
import TextField from "@mui/material/TextField";
import { makeStyles } from "@mui/styles";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { Stack } from "components/Stack/Stack";
import { Template, TemplateVersion } from "api/typesGenerated";
import { Loader } from "components/Loader/Loader";
import Autocomplete from "@mui/material/Autocomplete";
import { createDayString } from "utils/createDayString";
import CircularProgress from "@mui/material/CircularProgress";
import Box from "@mui/material/Box";
import AlertTitle from "@mui/material/AlertTitle";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { css } from "@emotion/css";
import { useTheme } from "@emotion/react";
import type { Template, TemplateVersion } from "api/typesGenerated";
import { Alert, AlertDetail } from "components/Alert/Alert";
import type { DialogProps } from "components/Dialogs/Dialog";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { FormFields } from "components/Form/Form";
import { Stack } from "components/Stack/Stack";
import { Loader } from "components/Loader/Loader";
import { AvatarData } from "components/AvatarData/AvatarData";
import { Pill } from "components/Pill/Pill";
import { Avatar } from "components/Avatar/Avatar";
import CircularProgress from "@mui/material/CircularProgress";
import Box from "@mui/material/Box";
import { Alert, AlertDetail } from "components/Alert/Alert";
import AlertTitle from "@mui/material/AlertTitle";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { createDayString } from "utils/createDayString";
export type ChangeVersionDialogProps = DialogProps & {
template: Template | undefined;
@@ -37,8 +38,7 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false);
const selectedTemplateVersion = useRef<TemplateVersion | undefined>();
const version = selectedTemplateVersion.current;
const styles = useStyles();
const theme = useTheme();
return (
<ConfirmDialog
@@ -136,7 +136,10 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
</>
),
classes: {
root: styles.inputRoot,
// Same `padding-left` as input
root: css`
padding-left: ${theme.spacing(1.75)} !important;
`,
},
}}
/>
@@ -163,9 +166,3 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
/>
);
};
export const useStyles = makeStyles((theme) => ({
inputRoot: {
paddingLeft: `${theme.spacing(1.75)} !important`, // Same padding left as input
},
}));
+25 -30
View File
@@ -1,43 +1,44 @@
import { makeStyles, useTheme } from "@mui/styles";
import { FC } from "react";
import { type FC } from "react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { colors } from "theme/colors";
import { useTheme } from "@emotion/react";
import { Stack } from "components/Stack/Stack";
import { Theme } from "@mui/material/styles";
import { colors } from "theme/colors";
dayjs.extend(relativeTime);
type CircleProps = { color: string; variant?: "solid" | "outlined" };
const Circle: FC<CircleProps> = ({ color, variant = "solid" }) => {
const styles = useCircleStyles({ color, variant });
return <div className={styles.root} />;
type CircleProps = {
color: string;
variant?: "solid" | "outlined";
};
const useCircleStyles = makeStyles((theme) => ({
root: {
width: theme.spacing(1),
height: theme.spacing(1),
backgroundColor: (props: CircleProps) =>
props.variant === "solid" ? props.color : undefined,
border: (props: CircleProps) =>
props.variant === "outlined" ? `1px solid ${props.color}` : undefined,
borderRadius: 9999,
},
}));
const Circle: FC<CircleProps> = ({ color, variant = "solid" }) => {
const theme = useTheme();
return (
<div
aria-hidden
css={{
width: theme.spacing(1),
height: theme.spacing(1),
backgroundColor: variant === "solid" ? color : undefined,
border: variant === "outlined" ? `1px solid ${color}` : undefined,
borderRadius: 9999,
}}
/>
);
};
interface LastUsedProps {
lastUsedAt: string;
}
export const LastUsed: FC<LastUsedProps> = ({ lastUsedAt }) => {
const theme: Theme = useTheme();
const styles = useStyles();
const theme = useTheme();
const t = dayjs(lastUsedAt);
const now = dayjs();
let message = t.fromNow();
let circle: JSX.Element = (
let circle = (
<Circle color={theme.palette.text.secondary} variant="outlined" />
);
@@ -59,7 +60,7 @@ export const LastUsed: FC<LastUsedProps> = ({ lastUsedAt }) => {
return (
<Stack
className={styles.root}
css={{ color: theme.palette.text.secondary }}
direction="row"
spacing={1}
alignItems="center"
@@ -69,9 +70,3 @@ export const LastUsed: FC<LastUsedProps> = ({ lastUsedAt }) => {
</Stack>
);
};
const useStyles = makeStyles((theme) => ({
root: {
color: theme.palette.text.secondary,
},
}));