From 95e9688edda01cdb4a3dd9d3114e1e2ea5fd93fe Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Mon, 9 Oct 2023 20:27:15 +0000 Subject: [PATCH 01/23] emotion: `TemplateScheduleForm` --- .../Dialogs/ConfirmDialog/ConfirmDialog.tsx | 207 ++---------------- .../TemplateSchedulePage/ScheduleDialog.tsx | 174 +++++++++++++++ .../TemplateScheduleForm.tsx | 41 ++-- 3 files changed, 212 insertions(+), 210 deletions(-) create mode 100644 site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx diff --git a/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx b/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx index 053cde5b48886..50f0c248509d6 100644 --- a/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx +++ b/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx @@ -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>; /** * Quick-use version of the Dialog component with slightly alternative styles, @@ -117,8 +114,6 @@ export const ConfirmDialog: FC> = ({ 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> = ({ return ( -
-

{title}

- {description && ( -
{description}
- )} +
+

{title}

+ {description &&
{description}
}
@@ -154,169 +147,3 @@ export const ConfirmDialog: FC> = ({
); }; - -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> = ({ - 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 ( - -
-

{title}

- <> - {showDormancyWarning && ( - <> -

{"Dormancy Threshold"}

- -
{` - 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?`}
- { - updateInactiveWorkspaces(e.target.checked); - }} - /> - } - label="Reset" - /> -
- - )} - - {showDeletionWarning && ( - <> -

{"Dormancy Auto-Deletion"}

- -
{`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?`}
- { - updateDormantWorkspaces(e.target.checked); - }} - /> - } - label="Reset" - /> -
- - )} - -
- - - - -
- ); -}; - -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), - }, - }, -})); diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx new file mode 100644 index 0000000000000..04c1a70e092e2 --- /dev/null +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx @@ -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> = ({ + 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 ( + +
+

{title}

+ <> + {showDormancyWarning && ( + <> +

{"Dormancy Threshold"}

+ +
{` + 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?`}
+ { + updateInactiveWorkspaces(e.target.checked); + }} + /> + } + label="Reset" + /> +
+ + )} + + {showDeletionWarning && ( + <> +

{"Dormancy Auto-Deletion"}

+ +
{`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?`}
+ { + updateDormantWorkspaces(e.target.checked); + }} + /> + } + label="Reset" + /> +
+ + )} + +
+ + + + +
+ ); +}; + +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>; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx index cf595ba19a7f9..7d98f9de12eda 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx @@ -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 = ({ 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 = ({ title="Schedule" description="Define when workspaces created from this template are stopped." > - + = ({ title="Autostop Requirement" description="Define when workspaces created from this template are stopped periodically to enforce template updates and ensure idle workspaces are stopped." > - + = ({ Allow users to customize autostop duration for workspaces. - + 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 = ({ ); }; -const useStyles = makeStyles((theme) => ({ +const styles = { ttlFields: { width: "100%", }, - optionDescription: { - fontSize: 12, - color: theme.palette.text.secondary, - }, -})); +}; From b0cdf3fcce40817f601fa5033604cb72dd37f77c Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Mon, 9 Oct 2023 21:07:47 +0000 Subject: [PATCH 02/23] emotion: `Form` & `FormFooter` --- site/src/components/Form/Form.tsx | 173 ++++++++---------- site/src/components/FormFooter/FormFooter.tsx | 27 +-- 2 files changed, 90 insertions(+), 110 deletions(-) diff --git a/site/src/components/Form/Form.tsx b/site/src/components/Form/Form.tsx index b487050513484..92356b38ec1bc 100644 --- a/site/src/components/Form/Form.tsx +++ b/site/src/components/Form/Form.tsx @@ -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 & { direction?: FormContextValue["direction"]; }; -export const Form: FC = ({ direction, className, ...formProps }) => { - const styles = useStyles({ direction }); +export const Form: FC = ({ direction, ...formProps }) => { + const theme = useTheme(); return (
); @@ -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 ( -
+

{title} {alpha && }

-
{description}
+
{description}
{children} @@ -101,108 +131,55 @@ export const FormSection: FC< }; export const FormFields: FC = ({ children }) => { - const styles = useStyles(); return ( - + {children} ); }; -export const FormFooter: FC = (props) => { - const formFooterStyles = useFormFooterStyles(); - return ( - - ); -}; -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>; + +export const FormFooter = (props: Exclude) => ( + +); -const useFormFooterStyles = makeStyles((theme) => ({ - button: { +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; diff --git a/site/src/components/FormFooter/FormFooter.tsx b/site/src/components/FormFooter/FormFooter.tsx index 100cf6578785b..7221aaf6a217d 100644 --- a/site/src/components/FormFooter/FormFooter.tsx +++ b/site/src/components/FormFooter/FormFooter.tsx @@ -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; + button: Interpolation; +} + export interface FormFooterProps { onCancel: () => void; isLoading: boolean; @@ -23,15 +26,15 @@ export const FormFooter: FC = ({ isLoading, submitDisabled, submitLabel = Language.defaultSubmitLabel, - styles = defaultStyles(), + styles = defaultStyles, }) => { return ( -
+
= ({