mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: use emotion for styling (pt. 4) (#10149)
This commit is contained in:
@@ -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),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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%",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}));
|
||||
|
||||
+4
-13
@@ -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,
|
||||
},
|
||||
}));
|
||||
|
||||
+22
-26
@@ -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,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
+4
-13
@@ -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
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user