diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index dcdd25800f126..0e6d1334839bc 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -21,6 +21,8 @@ import WorkspacesPage from "./pages/WorkspacesPage/WorkspacesPage"; import UserSettingsLayout from "./pages/UserSettingsPage/Layout"; import { TemplateSettingsLayout } from "./pages/TemplateSettingsPage/TemplateSettingsLayout"; import { WorkspaceSettingsLayout } from "./pages/WorkspaceSettingsPage/WorkspaceSettingsLayout"; +import { ThemeOverride } from "contexts/ThemeProvider"; +import themes from "theme"; // Lazy load pages // - Pages that are secondary, not in the main navigation or not usually accessed @@ -384,7 +386,11 @@ export const AppRouter: FC = () => { /> } + element={ + + + + } /> } /> } /> diff --git a/site/src/components/Dashboard/DeploymentBanner/DeploymentBannerView.tsx b/site/src/components/Dashboard/DeploymentBanner/DeploymentBannerView.tsx index 46efd3712ca55..ec9dcb18f87c1 100644 --- a/site/src/components/Dashboard/DeploymentBanner/DeploymentBannerView.tsx +++ b/site/src/components/Dashboard/DeploymentBanner/DeploymentBannerView.tsx @@ -36,6 +36,7 @@ import { TerminalIcon } from "components/Icons/TerminalIcon"; import { RocketIcon } from "components/Icons/RocketIcon"; import ErrorIcon from "@mui/icons-material/ErrorOutline"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; +import colors from "theme/tailwindColors"; import { getDisplayWorkspaceStatus } from "utils/workspace"; import { HelpTooltipTitle } from "components/HelpTooltip/HelpTooltip"; import { Stack } from "components/Stack/Stack"; @@ -439,9 +440,9 @@ const styles = { height: 16px; } `, - unhealthy: (theme) => css` - background-color: ${theme.colors.red[10]}; - `, + unhealthy: { + backgroundColor: colors.red[700], + }, group: css` display: flex; align-items: center; diff --git a/site/src/components/IconField/IconField.tsx b/site/src/components/IconField/IconField.tsx index 834556ea56be9..ea578d07734b9 100644 --- a/site/src/components/IconField/IconField.tsx +++ b/site/src/components/IconField/IconField.tsx @@ -102,8 +102,8 @@ const IconField: FC = ({ onPickEmoji, ...textFieldProps }) => { styles={css` em-emoji-picker { --rgb-background: ${theme.palette.background.paper}; - --rgb-input: ${theme.colors.gray[17]}; - --rgb-color: ${theme.colors.gray[4]}; + --rgb-input: ${theme.palette.primary.main}; + --rgb-color: ${theme.palette.text.primary}; // Hack to prevent the right side from being cut off width: 350px; diff --git a/site/src/components/Markdown/Markdown.tsx b/site/src/components/Markdown/Markdown.tsx index e6defa3733288..e52a307112470 100644 --- a/site/src/components/Markdown/Markdown.tsx +++ b/site/src/components/Markdown/Markdown.tsx @@ -11,7 +11,7 @@ import { type FC, memo } from "react"; import ReactMarkdown, { type Options } from "react-markdown"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import gfm from "remark-gfm"; -import colors from "theme/tailwind"; +import colors from "theme/tailwindColors"; import { dracula } from "react-syntax-highlighter/dist/cjs/styles/prism"; interface MarkdownProps { diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/AuditLogDiff.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/AuditLogDiff.tsx index 3fcb4dc524845..50fc7fd44eb3a 100644 --- a/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/AuditLogDiff.tsx +++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/AuditLogDiff.tsx @@ -1,7 +1,7 @@ import { type Interpolation, type Theme } from "@emotion/react"; import { type FC } from "react"; import type { AuditLog } from "api/typesGenerated"; -import colors from "theme/tailwind"; +import colors from "theme/tailwindColors"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; const getDiffValue = (value: unknown): string => { diff --git a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx index be6092f0b0b88..1e6a5777d2b5b 100644 --- a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx @@ -19,7 +19,7 @@ import { import { Fieldset } from "components/DeploySettingsLayout/Fieldset"; import { Stack } from "components/Stack/Stack"; import { getFormHelpers } from "utils/formUtils"; -import colors from "theme/tailwind"; +import colors from "theme/tailwindColors"; export type AppearanceSettingsPageViewProps = { appearance: UpdateAppearanceConfig; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index ed4ddc9d3d35a..7788caee5b0f1 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -420,7 +420,10 @@ const TemplateUsagePanel: FC = ({ const totalInSeconds = validUsage?.reduce((total, usage) => total + usage.seconds, 0) ?? 1; const usageColors = chroma - .scale([theme.colors.green[8], theme.colors.blue[8]]) + .scale([ + theme.experimental.roles.success.fill, + theme.experimental.roles.notice.fill, + ]) .mode("lch") .colors(validUsage?.length ?? 0); // The API returns a row for each app, even if the user didn't use it. diff --git a/site/src/pages/TerminalPage/TerminalPage.tsx b/site/src/pages/TerminalPage/TerminalPage.tsx index 7130abfa57219..0271c1ec245dc 100644 --- a/site/src/pages/TerminalPage/TerminalPage.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.tsx @@ -108,7 +108,7 @@ const TerminalPage: FC = () => { fontFamily: MONOSPACE_FONT_FAMILY, fontSize: 16, theme: { - background: theme.colors.gray[16], + background: theme.palette.background.default, }, }); if (renderer === "webgl") { diff --git a/site/src/pages/WorkspacesPage/LastUsed.tsx b/site/src/pages/WorkspacesPage/LastUsed.tsx index c048a6f726a17..7395151113a91 100644 --- a/site/src/pages/WorkspacesPage/LastUsed.tsx +++ b/site/src/pages/WorkspacesPage/LastUsed.tsx @@ -40,7 +40,7 @@ export const LastUsed: FC = ({ lastUsedAt }) => { ); if (t.isAfter(now.subtract(1, "hour"))) { - circle = ; + circle = ; // Since the agent reports on a 10m interval, // the last_used_at can be inaccurate when recent. message = "Now"; @@ -49,15 +49,14 @@ export const LastUsed: FC = ({ lastUsedAt }) => { } else if (t.isAfter(now.subtract(1, "month"))) { circle = ; } else if (t.isAfter(now.subtract(100, "year"))) { - circle = ; + circle = ; } else { - // color = theme.palette.error.light message = "Never"; } return ( ({ - textTransform: "none", - letterSpacing: "normal", - fontWeight: 500, - height: BUTTON_MD_HEIGHT, - padding: "8px 16px", - borderRadius: "6px", - fontSize: 14, - - whiteSpace: "nowrap", - ":focus-visible": { - outline: `2px solid ${theme.palette.primary.main}`, - }, - - "& .MuiLoadingButton-loadingIndicator": { - width: 14, - height: 14, - }, - - "& .MuiLoadingButton-loadingIndicator .MuiCircularProgress-root": { - width: "inherit !important", - height: "inherit !important", - }, - }), - sizeSmall: { - height: BUTTON_SM_HEIGHT, - }, - sizeLarge: { - height: BUTTON_LG_HEIGHT, - }, - sizeXlarge: { - height: BUTTON_XL_HEIGHT, - }, - outlined: { - ":hover": { - border: `1px solid ${colors.gray[11]}`, - }, - }, - outlinedNeutral: { - borderColor: colors.gray[12], - - "&.Mui-disabled": { - borderColor: colors.gray[13], - color: colors.gray[11], - - "& > .MuiLoadingButton-loadingIndicator": { - color: colors.gray[11], - }, - }, - }, - containedNeutral: { - backgroundColor: colors.gray[14], - - "&:hover": { - backgroundColor: colors.gray[13], - }, - }, - iconSizeMedium: { - "& > .MuiSvgIcon-root": { - fontSize: 14, - }, - }, - iconSizeSmall: { - "& > .MuiSvgIcon-root": { - fontSize: 13, - }, - }, - startIcon: { - marginLeft: "-2px", - }, - }, - }, - MuiButtonGroup: { - styleOverrides: { - root: { - ">button:hover+button": { - // The !important is unfortunate, but necessary for the border. - borderLeftColor: `${colors.gray[11]} !important`, - }, - }, - }, - }, - MuiLoadingButton: { - defaultProps: { - variant: "outlined", - color: "neutral", - }, - }, - MuiTableContainer: { - styleOverrides: { - root: { - borderRadius, - border: `1px solid ${muiTheme.palette.divider}`, - }, - }, - }, - MuiTable: { - styleOverrides: { - root: ({ theme }) => ({ - borderCollapse: "unset", - border: "none", - boxShadow: `0 0 0 1px ${muiTheme.palette.background.default} inset`, - overflow: "hidden", - - "& td": { - paddingTop: 16, - paddingBottom: 16, - background: "transparent", - }, - - [theme.breakpoints.down("md")]: { - minWidth: 1000, - }, - }), - }, - }, - MuiTableCell: { - styleOverrides: { - head: { - fontSize: 14, - color: muiTheme.palette.text.secondary, - fontWeight: 600, - background: muiTheme.palette.background.paper, - }, - root: { - fontSize: 16, - background: muiTheme.palette.background.paper, - borderBottom: `1px solid ${muiTheme.palette.divider}`, - padding: "12px 8px", - // This targets the first+last td elements, and also the first+last elements - // of a TableCellLink. - "&:not(:only-child):first-of-type, &:not(:only-child):first-of-type > a": - { - paddingLeft: 32, - }, - "&:not(:only-child):last-child, &:not(:only-child):last-child > a": { - paddingRight: 32, - }, - }, - }, - }, - MuiTableRow: { - styleOverrides: { - root: { - "&:last-child .MuiTableCell-body": { - borderBottom: 0, - }, - }, - }, - }, - MuiLink: { - defaultProps: { - underline: "hover", - }, - }, - MuiPaper: { - defaultProps: { - elevation: 0, - }, - styleOverrides: { - root: { - border: `1px solid ${muiTheme.palette.divider}`, - backgroundImage: "none", - }, - }, - }, - MuiSkeleton: { - styleOverrides: { - root: { - backgroundColor: muiTheme.palette.divider, - }, - }, - }, - MuiLinearProgress: { - styleOverrides: { - root: { - borderRadius: 999, - }, - }, - }, - MuiChip: { - styleOverrides: { - root: { - backgroundColor: colors.gray[12], - }, - }, - }, - MuiMenu: { - defaultProps: { - anchorOrigin: { - vertical: "bottom", - horizontal: "right", - }, - transformOrigin: { - vertical: "top", - horizontal: "right", - }, - }, - styleOverrides: { - paper: { - marginTop: 8, - borderRadius: 4, - padding: "4px 0", - minWidth: 160, - }, - root: { - // It should be the same as the menu padding - "& .MuiDivider-root": { - marginTop: `4px !important`, - marginBottom: `4px !important`, - }, - }, - }, - }, - MuiMenuItem: { - styleOverrides: { - root: { - gap: 12, - - "& .MuiSvgIcon-root": { - fontSize: 20, - }, - }, - }, - }, - MuiSnackbar: { - styleOverrides: { - anchorOriginBottomRight: { - bottom: `${24 + 36}px !important`, // 36 is the bottom bar height - }, - }, - }, - MuiSnackbarContent: { - styleOverrides: { - root: { - borderRadius: "4px !important", - }, - }, - }, - MuiTextField: { - defaultProps: { - InputLabelProps: { - shrink: true, - }, - }, - }, - MuiInputBase: { - defaultProps: { - color: "primary", - }, - styleOverrides: { - root: { - height: BUTTON_LG_HEIGHT, - }, - sizeSmall: { - height: BUTTON_MD_HEIGHT, - fontSize: 14, - }, - multiline: { - height: "auto", - }, - colorPrimary: { - // Same as button - "& .MuiOutlinedInput-notchedOutline": { - borderColor: colors.gray[12], - }, - // The default outlined input color is white, which seemed jarring. - "&:hover:not(.Mui-error):not(.Mui-focused) .MuiOutlinedInput-notchedOutline": - { - borderColor: colors.gray[11], - }, - }, - }, - }, - MuiFormHelperText: { - defaultProps: { - sx: { - marginLeft: 0, - marginTop: 1, - }, - }, - }, - MuiRadio: { - defaultProps: { - disableRipple: true, - }, - }, - MuiCheckbox: { - styleOverrides: { - root: { - /** - * Adds focus styling to checkboxes (which doesn't exist normally, for - * some reason?). - * - * The checkbox component is a root span with a checkbox input inside - * it. MUI does not allow you to use selectors like (& input) to - * target the inner checkbox (even though you can use & td to style - * tables). Tried every combination of selector possible (including - * lots of !important), and the main issue seems to be that the - * styling just never gets processed for it to get injected into the - * CSSOM. - * - * Had to settle for adding styling to the span itself (which does - * make the styling more obvious, even if there's not much room for - * customization). - */ - "&.Mui-focusVisible": { - boxShadow: `0 0 0 2px ${colors.blue[7]}`, - }, - - "&.Mui-disabled": { - color: colors.gray[11], - }, - }, - }, - }, - MuiSwitch: { - defaultProps: { color: "primary" }, - styleOverrides: { - root: { - ".Mui-focusVisible .MuiSwitch-thumb": { - // Had to thicken outline to make sure that the focus color didn't - // bleed into the thumb and was still easily-visible - boxShadow: `0 0 0 3px ${colors.blue[7]}`, - }, - }, - }, - }, - MuiAutocomplete: { - styleOverrides: { - root: { - // Not sure why but since the input has padding we don't need it here - "& .MuiInputBase-root": { - padding: 0, - }, - }, - }, - }, - MuiList: { - defaultProps: { - disablePadding: true, - }, - }, - MuiTabs: { - defaultProps: { - textColor: "primary", - indicatorColor: "primary", - }, - }, - MuiTooltip: { - styleOverrides: { - tooltip: { - lineHeight: "150%", - borderRadius: 4, - background: muiTheme.palette.divider, - padding: "8px 16px", - }, - }, - }, - MuiAlert: { - defaultProps: { - variant: "outlined", - }, - styleOverrides: { - root: ({ theme }) => ({ - background: theme.palette.background.paper, - }), - action: { - paddingTop: 2, // Idk why it is not aligned as expected - }, - icon: { - fontSize: 16, - marginTop: "4px", // The size of text is 24 so (24 - 16)/2 = 4 - }, - message: ({ theme }) => ({ - color: theme.palette.text.primary, - }), - outlinedWarning: { - [`& .${alertClasses.icon}`]: { - color: muiTheme.palette.warning.light, - }, - }, - outlinedInfo: { - [`& .${alertClasses.icon}`]: { - color: muiTheme.palette.primary.light, - }, - }, - outlinedError: { - [`& .${alertClasses.icon}`]: { - color: muiTheme.palette.error.light, - }, - }, - }, - }, - MuiAlertTitle: { - styleOverrides: { - root: { - fontSize: "inherit", - marginBottom: 0, - }, - }, - }, - - MuiIconButton: { - styleOverrides: { - root: { - "&.Mui-focusVisible": { - boxShadow: `0 0 0 2px ${colors.blue[7]}`, - }, - }, - }, - }, - }, -} as ThemeOptions); - export default muiTheme; diff --git a/site/src/theme/darkBlue/colors.ts b/site/src/theme/darkBlue/colors.ts deleted file mode 100644 index 694d86388f785..0000000000000 --- a/site/src/theme/darkBlue/colors.ts +++ /dev/null @@ -1,62 +0,0 @@ -import tw from "../tailwind"; - -export default { - white: "#fff", - - gray: { - 17: tw.slate[900], - 16: tw.slate[900], - 14: tw.slate[800], - 13: tw.slate[700], - 12: tw.slate[600], - 11: tw.slate[500], - 9: tw.slate[400], - 6: tw.slate[300], - 4: tw.slate[200], - 2: tw.slate[100], - 1: tw.slate[50], - }, - - red: { - 15: tw.red[950], - 12: tw.red[800], - 10: tw.red[700], - 9: tw.red[600], - 8: tw.red[500], - 6: tw.red[400], - 2: tw.red[50], - }, - - orange: { - 15: tw.amber[950], - 14: tw.amber[900], - 12: tw.amber[800], - 11: tw.amber[700], - 10: tw.amber[600], - 9: tw.amber[500], - 7: tw.amber[400], - }, - - yellow: { - 5: tw.yellow[300], - }, - - green: { - 15: tw.green[950], - 13: tw.green[700], - 12: tw.green[600], - 11: tw.green[500], - 9: tw.green[400], - 8: tw.green[300], - }, - - blue: { - 14: tw.blue[950], - 9: tw.blue[600], - 8: tw.blue[500], - 7: tw.blue[400], - 6: tw.blue[300], - 3: tw.blue[200], - 1: tw.blue[50], - }, -}; diff --git a/site/src/theme/darkBlue/experimental.ts b/site/src/theme/darkBlue/experimental.ts index 9b15df75a932b..954d0fa6e9a95 100644 --- a/site/src/theme/darkBlue/experimental.ts +++ b/site/src/theme/darkBlue/experimental.ts @@ -1,5 +1,5 @@ import { type NewTheme } from "../experimental"; -import colors from "../tailwind"; +import colors from "../tailwindColors"; export default { l1: { diff --git a/site/src/theme/darkBlue/index.ts b/site/src/theme/darkBlue/index.ts index 4a1d9094c6bc2..7c487ee146132 100644 --- a/site/src/theme/darkBlue/index.ts +++ b/site/src/theme/darkBlue/index.ts @@ -1,11 +1,9 @@ -import colors from "./colors"; import experimental from "./experimental"; import monaco from "./monaco"; import muiTheme from "./mui"; export default { ...muiTheme, - colors, experimental, monaco, }; diff --git a/site/src/theme/darkBlue/mui.ts b/site/src/theme/darkBlue/mui.ts index f9308dc00044b..ecf5022323005 100644 --- a/site/src/theme/darkBlue/mui.ts +++ b/site/src/theme/darkBlue/mui.ts @@ -1,66 +1,58 @@ -import colors from "./colors"; -import { createTheme, type ThemeOptions } from "@mui/material/styles"; -import { - BODY_FONT_FAMILY, - borderRadius, - BUTTON_LG_HEIGHT, - BUTTON_MD_HEIGHT, - BUTTON_SM_HEIGHT, - BUTTON_XL_HEIGHT, -} from "../constants"; -// eslint-disable-next-line no-restricted-imports -- We need MUI here -import { alertClasses } from "@mui/material/Alert"; +import { createTheme } from "@mui/material/styles"; +import { BODY_FONT_FAMILY, borderRadius } from "../constants"; +import tw from "../tailwindColors"; +import { components } from "../mui"; -let muiTheme = createTheme({ +const muiTheme = createTheme({ palette: { mode: "dark", primary: { - main: colors.blue[7], - contrastText: colors.blue[1], - light: colors.blue[6], - dark: colors.blue[9], + main: tw.sky[500], + contrastText: tw.sky[50], + light: tw.sky[300], + dark: tw.sky[400], }, secondary: { - main: colors.gray[11], - contrastText: colors.gray[4], - dark: colors.gray[9], + main: tw.gray[500], + contrastText: tw.gray[200], + dark: tw.gray[400], }, background: { - default: colors.gray[17], - paper: colors.gray[16], + default: tw.gray[900], + paper: tw.gray[900], }, text: { - primary: colors.gray[1], - secondary: colors.gray[6], - disabled: colors.gray[9], + primary: tw.gray[50], + secondary: tw.gray[300], + disabled: tw.gray[400], }, - divider: colors.gray[13], + divider: tw.gray[700], warning: { - light: colors.orange[9], - main: colors.orange[12], - dark: colors.orange[15], + light: tw.amber[500], + main: tw.amber[800], + dark: tw.amber[950], }, success: { - main: colors.green[11], - dark: colors.green[12], + main: tw.green[500], + dark: tw.green[600], }, info: { - light: colors.blue[7], - main: colors.blue[9], - dark: colors.blue[14], - contrastText: colors.gray[4], + light: tw.blue[400], + main: tw.blue[600], + dark: tw.blue[950], + contrastText: tw.gray[200], }, error: { - light: colors.red[6], - main: colors.red[8], - dark: colors.red[15], - contrastText: colors.gray[4], + light: tw.red[400], + main: tw.red[500], + dark: tw.red[950], + contrastText: tw.gray[200], }, action: { - hover: colors.gray[14], + hover: tw.gray[800], }, neutral: { - main: colors.gray[1], + main: tw.gray[50], }, }, typography: { @@ -79,474 +71,7 @@ let muiTheme = createTheme({ shape: { borderRadius, }, + components, }); -muiTheme = createTheme(muiTheme, { - components: { - MuiCssBaseline: { - styleOverrides: ` - html, body, #root, #storybook-root { - height: 100%; - } - - button, input { - font-family: ${BODY_FONT_FAMILY}; - } - - input:-webkit-autofill, - input:-webkit-autofill:hover, - input:-webkit-autofill:focus, - input:-webkit-autofill:active { - -webkit-box-shadow: 0 0 0 100px ${muiTheme.palette.background.default} inset !important; - } - - ::placeholder { - color: ${muiTheme.palette.text.disabled}; - } - `, - }, - MuiAvatar: { - styleOverrides: { - root: { - width: 36, - height: 36, - fontSize: 18, - - "& .MuiSvgIcon-root": { - width: "50%", - }, - }, - colorDefault: { - backgroundColor: colors.gray[6], - }, - }, - }, - // Button styles are based on - // https://tailwindui.com/components/application-ui/elements/buttons - MuiButtonBase: { - defaultProps: { - disableRipple: true, - }, - }, - MuiButton: { - defaultProps: { - variant: "outlined", - color: "neutral", - }, - styleOverrides: { - root: ({ theme }) => ({ - textTransform: "none", - letterSpacing: "normal", - fontWeight: 500, - height: BUTTON_MD_HEIGHT, - padding: "8px 16px", - borderRadius: "6px", - fontSize: 14, - - whiteSpace: "nowrap", - ":focus-visible": { - outline: `2px solid ${theme.palette.primary.main}`, - }, - - "& .MuiLoadingButton-loadingIndicator": { - width: 14, - height: 14, - }, - - "& .MuiLoadingButton-loadingIndicator .MuiCircularProgress-root": { - width: "inherit !important", - height: "inherit !important", - }, - }), - sizeSmall: { - height: BUTTON_SM_HEIGHT, - }, - sizeLarge: { - height: BUTTON_LG_HEIGHT, - }, - sizeXlarge: { - height: BUTTON_XL_HEIGHT, - }, - outlined: { - ":hover": { - border: `1px solid ${colors.gray[11]}`, - }, - }, - outlinedNeutral: { - borderColor: colors.gray[12], - - "&.Mui-disabled": { - borderColor: colors.gray[13], - color: colors.gray[11], - - "& > .MuiLoadingButton-loadingIndicator": { - color: colors.gray[11], - }, - }, - }, - containedNeutral: { - backgroundColor: colors.gray[14], - - "&:hover": { - backgroundColor: colors.gray[13], - }, - }, - iconSizeMedium: { - "& > .MuiSvgIcon-root": { - fontSize: 14, - }, - }, - iconSizeSmall: { - "& > .MuiSvgIcon-root": { - fontSize: 13, - }, - }, - startIcon: { - marginLeft: "-2px", - }, - }, - }, - MuiButtonGroup: { - styleOverrides: { - root: { - ">button:hover+button": { - // The !important is unfortunate, but necessary for the border. - borderLeftColor: `${colors.gray[11]} !important`, - }, - }, - }, - }, - MuiLoadingButton: { - defaultProps: { - variant: "outlined", - color: "neutral", - }, - }, - MuiTableContainer: { - styleOverrides: { - root: { - borderRadius, - border: `1px solid ${muiTheme.palette.divider}`, - }, - }, - }, - MuiTable: { - styleOverrides: { - root: ({ theme }) => ({ - borderCollapse: "unset", - border: "none", - boxShadow: `0 0 0 1px ${muiTheme.palette.background.default} inset`, - overflow: "hidden", - - "& td": { - paddingTop: 16, - paddingBottom: 16, - background: "transparent", - }, - - [theme.breakpoints.down("md")]: { - minWidth: 1000, - }, - }), - }, - }, - MuiTableCell: { - styleOverrides: { - head: { - fontSize: 14, - color: muiTheme.palette.text.secondary, - fontWeight: 600, - background: muiTheme.palette.background.paper, - }, - root: { - fontSize: 16, - background: muiTheme.palette.background.paper, - borderBottom: `1px solid ${muiTheme.palette.divider}`, - padding: "12px 8px", - // This targets the first+last td elements, and also the first+last elements - // of a TableCellLink. - "&:not(:only-child):first-of-type, &:not(:only-child):first-of-type > a": - { - paddingLeft: 32, - }, - "&:not(:only-child):last-child, &:not(:only-child):last-child > a": { - paddingRight: 32, - }, - }, - }, - }, - MuiTableRow: { - styleOverrides: { - root: { - "&:last-child .MuiTableCell-body": { - borderBottom: 0, - }, - }, - }, - }, - MuiLink: { - defaultProps: { - underline: "hover", - }, - }, - MuiPaper: { - defaultProps: { - elevation: 0, - }, - styleOverrides: { - root: { - border: `1px solid ${muiTheme.palette.divider}`, - backgroundImage: "none", - }, - }, - }, - MuiSkeleton: { - styleOverrides: { - root: { - backgroundColor: muiTheme.palette.divider, - }, - }, - }, - MuiLinearProgress: { - styleOverrides: { - root: { - borderRadius: 999, - }, - }, - }, - MuiChip: { - styleOverrides: { - root: { - backgroundColor: colors.gray[12], - }, - }, - }, - MuiMenu: { - defaultProps: { - anchorOrigin: { - vertical: "bottom", - horizontal: "right", - }, - transformOrigin: { - vertical: "top", - horizontal: "right", - }, - }, - styleOverrides: { - paper: { - marginTop: 8, - borderRadius: 4, - padding: "4px 0", - minWidth: 160, - }, - root: { - // It should be the same as the menu padding - "& .MuiDivider-root": { - marginTop: `4px !important`, - marginBottom: `4px !important`, - }, - }, - }, - }, - MuiMenuItem: { - styleOverrides: { - root: { - gap: 12, - - "& .MuiSvgIcon-root": { - fontSize: 20, - }, - }, - }, - }, - MuiSnackbar: { - styleOverrides: { - anchorOriginBottomRight: { - bottom: `${24 + 36}px !important`, // 36 is the bottom bar height - }, - }, - }, - MuiSnackbarContent: { - styleOverrides: { - root: { - borderRadius: "4px !important", - }, - }, - }, - MuiTextField: { - defaultProps: { - InputLabelProps: { - shrink: true, - }, - }, - }, - MuiInputBase: { - defaultProps: { - color: "primary", - }, - styleOverrides: { - root: { - height: BUTTON_LG_HEIGHT, - }, - sizeSmall: { - height: BUTTON_MD_HEIGHT, - fontSize: 14, - }, - multiline: { - height: "auto", - }, - colorPrimary: { - // Same as button - "& .MuiOutlinedInput-notchedOutline": { - borderColor: colors.gray[12], - }, - // The default outlined input color is white, which seemed jarring. - "&:hover:not(.Mui-error):not(.Mui-focused) .MuiOutlinedInput-notchedOutline": - { - borderColor: colors.gray[11], - }, - }, - }, - }, - MuiFormHelperText: { - defaultProps: { - sx: { - marginLeft: 0, - marginTop: 1, - }, - }, - }, - MuiRadio: { - defaultProps: { - disableRipple: true, - }, - }, - MuiCheckbox: { - styleOverrides: { - root: { - /** - * Adds focus styling to checkboxes (which doesn't exist normally, for - * some reason?). - * - * The checkbox component is a root span with a checkbox input inside - * it. MUI does not allow you to use selectors like (& input) to - * target the inner checkbox (even though you can use & td to style - * tables). Tried every combination of selector possible (including - * lots of !important), and the main issue seems to be that the - * styling just never gets processed for it to get injected into the - * CSSOM. - * - * Had to settle for adding styling to the span itself (which does - * make the styling more obvious, even if there's not much room for - * customization). - */ - "&.Mui-focusVisible": { - boxShadow: `0 0 0 2px ${colors.blue[7]}`, - }, - - "&.Mui-disabled": { - color: colors.gray[11], - }, - }, - }, - }, - MuiSwitch: { - defaultProps: { color: "primary" }, - styleOverrides: { - root: { - ".Mui-focusVisible .MuiSwitch-thumb": { - // Had to thicken outline to make sure that the focus color didn't - // bleed into the thumb and was still easily-visible - boxShadow: `0 0 0 3px ${colors.blue[7]}`, - }, - }, - }, - }, - MuiAutocomplete: { - styleOverrides: { - root: { - // Not sure why but since the input has padding we don't need it here - "& .MuiInputBase-root": { - padding: 0, - }, - }, - }, - }, - MuiList: { - defaultProps: { - disablePadding: true, - }, - }, - MuiTabs: { - defaultProps: { - textColor: "primary", - indicatorColor: "primary", - }, - }, - MuiTooltip: { - styleOverrides: { - tooltip: { - lineHeight: "150%", - borderRadius: 4, - background: muiTheme.palette.divider, - padding: "8px 16px", - }, - }, - }, - MuiAlert: { - defaultProps: { - variant: "outlined", - }, - styleOverrides: { - root: ({ theme }) => ({ - background: theme.palette.background.paper, - }), - action: { - paddingTop: 2, // Idk why it is not aligned as expected - }, - icon: { - fontSize: 16, - marginTop: "4px", // The size of text is 24 so (24 - 16)/2 = 4 - }, - message: ({ theme }) => ({ - color: theme.palette.text.primary, - }), - outlinedWarning: { - [`& .${alertClasses.icon}`]: { - color: muiTheme.palette.warning.light, - }, - }, - outlinedInfo: { - [`& .${alertClasses.icon}`]: { - color: muiTheme.palette.primary.light, - }, - }, - outlinedError: { - [`& .${alertClasses.icon}`]: { - color: muiTheme.palette.error.light, - }, - }, - }, - }, - MuiAlertTitle: { - styleOverrides: { - root: { - fontSize: "inherit", - marginBottom: 0, - }, - }, - }, - - MuiIconButton: { - styleOverrides: { - root: { - "&.Mui-focusVisible": { - boxShadow: `0 0 0 2px ${colors.blue[7]}`, - }, - }, - }, - }, - }, -} as ThemeOptions); - export default muiTheme; diff --git a/site/src/theme/index.ts b/site/src/theme/index.ts index 6f889785286f9..bb7a620582f58 100644 --- a/site/src/theme/index.ts +++ b/site/src/theme/index.ts @@ -4,10 +4,8 @@ import dark from "./dark"; import darkBlue from "./darkBlue"; import light from "./light"; import type { NewTheme } from "./experimental"; -import type { Colors } from "./colors"; export interface Theme extends MuiTheme { - colors: Colors; experimental: NewTheme; monaco: monaco.editor.IStandaloneThemeData; } diff --git a/site/src/theme/light/colors.ts b/site/src/theme/light/colors.ts index 374c120e8f289..74508b31db47b 100644 --- a/site/src/theme/light/colors.ts +++ b/site/src/theme/light/colors.ts @@ -1,4 +1,4 @@ -import tw from "../tailwind"; +import tw from "../tailwindColors"; export default { white: "#fff", diff --git a/site/src/theme/light/experimental.ts b/site/src/theme/light/experimental.ts index 87ea50527c9f9..c230da896c3bf 100644 --- a/site/src/theme/light/experimental.ts +++ b/site/src/theme/light/experimental.ts @@ -1,5 +1,5 @@ import { type NewTheme } from "../experimental"; -import colors from "../tailwind"; +import colors from "../tailwindColors"; export default { l1: { diff --git a/site/src/theme/light/mui.ts b/site/src/theme/light/mui.ts index 23040ee3f7a2f..82c8efdde2b14 100644 --- a/site/src/theme/light/mui.ts +++ b/site/src/theme/light/mui.ts @@ -9,7 +9,7 @@ import { BUTTON_SM_HEIGHT, BUTTON_XL_HEIGHT, } from "../constants"; -import tw from "../tailwind"; +import tw from "../tailwindColors"; let muiTheme = createTheme({ palette: { diff --git a/site/src/theme/mui.ts b/site/src/theme/mui.ts index 9e604024f98d6..899eda97820f9 100644 --- a/site/src/theme/mui.ts +++ b/site/src/theme/mui.ts @@ -1,3 +1,18 @@ +/* eslint-disable @typescript-eslint/no-explicit-any +-- we need to hack around the MUI types a little */ +import { type ThemeOptions } from "@mui/material/styles"; +// eslint-disable-next-line no-restricted-imports -- We need MUI here +import { alertClasses } from "@mui/material/Alert"; +import { + BODY_FONT_FAMILY, + borderRadius, + BUTTON_LG_HEIGHT, + BUTTON_MD_HEIGHT, + BUTTON_SM_HEIGHT, + BUTTON_XL_HEIGHT, +} from "./constants"; +import tw from "./tailwindColors"; + export type PaletteIndex = | "primary" | "secondary" @@ -9,3 +24,469 @@ export type PaletteIndex = | "success" | "action" | "neutral"; + +export const components: ThemeOptions["components"] = { + MuiCssBaseline: { + styleOverrides: (theme) => ` + html, body, #root, #storybook-root { + height: 100%; + } + + button, input { + font-family: ${BODY_FONT_FAMILY}; + } + + input:-webkit-autofill, + input:-webkit-autofill:hover, + input:-webkit-autofill:focus, + input:-webkit-autofill:active { + -webkit-box-shadow: 0 0 0 100px ${theme.palette.background.default} inset !important; + } + + ::placeholder { + color: ${theme.palette.text.disabled}; + } + `, + }, + MuiAvatar: { + styleOverrides: { + root: { + width: 36, + height: 36, + fontSize: 18, + + "& .MuiSvgIcon-root": { + width: "50%", + }, + }, + colorDefault: ({ theme }) => ({ + backgroundColor: theme.palette.primary.light, + }), + }, + }, + // Button styles are based on + // https://tailwindui.com/components/application-ui/elements/buttons + MuiButtonBase: { + defaultProps: { + disableRipple: true, + }, + }, + MuiButton: { + defaultProps: { + variant: "outlined", + color: "neutral", + }, + styleOverrides: { + root: ({ theme }) => ({ + textTransform: "none", + letterSpacing: "normal", + fontWeight: 500, + height: BUTTON_MD_HEIGHT, + padding: "8px 16px", + borderRadius: "6px", + fontSize: 14, + + whiteSpace: "nowrap", + ":focus-visible": { + outline: `2px solid ${theme.palette.primary.main}`, + }, + + "& .MuiLoadingButton-loadingIndicator": { + width: 14, + height: 14, + }, + + "& .MuiLoadingButton-loadingIndicator .MuiCircularProgress-root": { + width: "inherit !important", + height: "inherit !important", + }, + }), + sizeSmall: { + height: BUTTON_SM_HEIGHT, + }, + sizeLarge: { + height: BUTTON_LG_HEIGHT, + }, + ["sizeXlarge" as any]: { + height: BUTTON_XL_HEIGHT, + }, + outlined: ({ theme }) => ({ + ":hover": { + border: `1px solid ${theme.palette.secondary.main}`, + }, + }), + ["outlinedNeutral" as any]: { + borderColor: tw.zinc[600], + + "&.Mui-disabled": { + borderColor: tw.zinc[700], + color: tw.zinc[500], + + "& > .MuiLoadingButton-loadingIndicator": { + color: tw.zinc[500], + }, + }, + }, + ["containedNeutral" as any]: { + backgroundColor: tw.zinc[800], + + "&:hover": { + backgroundColor: tw.zinc[700], + }, + }, + iconSizeMedium: { + "& > .MuiSvgIcon-root": { + fontSize: 14, + }, + }, + iconSizeSmall: { + "& > .MuiSvgIcon-root": { + fontSize: 13, + }, + }, + startIcon: { + marginLeft: "-2px", + }, + }, + }, + MuiButtonGroup: { + styleOverrides: { + root: ({ theme }) => ({ + ">button:hover+button": { + // The !important is unfortunate, but necessary for the border. + borderLeftColor: `${theme.palette.secondary.main} !important`, + }, + }), + }, + }, + ["MuiLoadingButton" as any]: { + defaultProps: { + variant: "outlined", + color: "neutral", + }, + }, + MuiTableContainer: { + styleOverrides: { + root: ({ theme }) => ({ + borderRadius, + border: `1px solid ${theme.palette.divider}`, + }), + }, + }, + MuiTable: { + styleOverrides: { + root: ({ theme }) => ({ + borderCollapse: "unset", + border: "none", + boxShadow: `0 0 0 1px ${theme.palette.background.default} inset`, + overflow: "hidden", + + "& td": { + paddingTop: 16, + paddingBottom: 16, + background: "transparent", + }, + + [theme.breakpoints.down("md")]: { + minWidth: 1000, + }, + }), + }, + }, + MuiTableCell: { + styleOverrides: { + head: ({ theme }) => ({ + fontSize: 14, + color: theme.palette.text.secondary, + fontWeight: 600, + background: theme.palette.background.paper, + }), + root: ({ theme }) => ({ + fontSize: 16, + background: theme.palette.background.paper, + borderBottom: `1px solid ${theme.palette.divider}`, + padding: "12px 8px", + // This targets the first+last td elements, and also the first+last elements + // of a TableCellLink. + "&:not(:only-child):first-of-type, &:not(:only-child):first-of-type > a": + { + paddingLeft: 32, + }, + "&:not(:only-child):last-child, &:not(:only-child):last-child > a": { + paddingRight: 32, + }, + }), + }, + }, + MuiTableRow: { + styleOverrides: { + root: { + "&:last-child .MuiTableCell-body": { + borderBottom: 0, + }, + }, + }, + }, + MuiLink: { + defaultProps: { + underline: "hover", + }, + }, + MuiPaper: { + defaultProps: { + elevation: 0, + }, + styleOverrides: { + root: ({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + backgroundImage: "none", + }), + }, + }, + MuiSkeleton: { + styleOverrides: { + root: ({ theme }) => ({ + backgroundColor: theme.palette.divider, + }), + }, + }, + MuiLinearProgress: { + styleOverrides: { + root: { + borderRadius: 999, + }, + }, + }, + MuiChip: { + styleOverrides: { + root: { + backgroundColor: tw.zinc[600], + }, + }, + }, + MuiMenu: { + defaultProps: { + anchorOrigin: { + vertical: "bottom", + horizontal: "right", + }, + transformOrigin: { + vertical: "top", + horizontal: "right", + }, + }, + styleOverrides: { + paper: { + marginTop: 8, + borderRadius: 4, + padding: "4px 0", + minWidth: 160, + }, + root: { + // It should be the same as the menu padding + "& .MuiDivider-root": { + marginTop: `4px !important`, + marginBottom: `4px !important`, + }, + }, + }, + }, + MuiMenuItem: { + styleOverrides: { + root: { + gap: 12, + + "& .MuiSvgIcon-root": { + fontSize: 20, + }, + }, + }, + }, + MuiSnackbar: { + styleOverrides: { + anchorOriginBottomRight: { + bottom: `${24 + 36}px !important`, // 36 is the bottom bar height + }, + }, + }, + MuiSnackbarContent: { + styleOverrides: { + root: { + borderRadius: "4px !important", + }, + }, + }, + MuiTextField: { + defaultProps: { + InputLabelProps: { + shrink: true, + }, + }, + }, + MuiInputBase: { + defaultProps: { + color: "primary", + }, + styleOverrides: { + root: { + height: BUTTON_LG_HEIGHT, + }, + sizeSmall: { + height: BUTTON_MD_HEIGHT, + fontSize: 14, + }, + multiline: { + height: "auto", + }, + ["colorPrimary" as any]: { + // Same as button + "& .MuiOutlinedInput-notchedOutline": { + borderColor: tw.zinc[600], + }, + // The default outlined input color is white, which seemed jarring. + "&:hover:not(.Mui-error):not(.Mui-focused) .MuiOutlinedInput-notchedOutline": + { + borderColor: tw.zinc[500], + }, + }, + }, + }, + MuiFormHelperText: { + defaultProps: { + sx: { + marginLeft: 0, + marginTop: 1, + }, + }, + }, + MuiRadio: { + defaultProps: { + disableRipple: true, + }, + }, + MuiCheckbox: { + styleOverrides: { + root: { + /** + * Adds focus styling to checkboxes (which doesn't exist normally, for + * some reason?). + * + * The checkbox component is a root span with a checkbox input inside + * it. MUI does not allow you to use selectors like (& input) to + * target the inner checkbox (even though you can use & td to style + * tables). Tried every combination of selector possible (including + * lots of !important), and the main issue seems to be that the + * styling just never gets processed for it to get injected into the + * CSSOM. + * + * Had to settle for adding styling to the span itself (which does + * make the styling more obvious, even if there's not much room for + * customization). + */ + "&.Mui-focusVisible": { + boxShadow: `0 0 0 2px ${tw.blue[400]}`, + }, + + "&.Mui-disabled": { + color: tw.zinc[500], + }, + }, + }, + }, + MuiSwitch: { + defaultProps: { color: "primary" }, + styleOverrides: { + root: { + ".Mui-focusVisible .MuiSwitch-thumb": { + // Had to thicken outline to make sure that the focus color didn't + // bleed into the thumb and was still easily-visible + boxShadow: `0 0 0 3px ${tw.blue[400]}`, + }, + }, + }, + }, + MuiAutocomplete: { + styleOverrides: { + root: { + // Not sure why but since the input has padding we don't need it here + "& .MuiInputBase-root": { + padding: 0, + }, + }, + }, + }, + MuiList: { + defaultProps: { + disablePadding: true, + }, + }, + MuiTabs: { + defaultProps: { + textColor: "primary", + indicatorColor: "primary", + }, + }, + MuiTooltip: { + styleOverrides: { + tooltip: ({ theme }) => ({ + lineHeight: "150%", + borderRadius: 4, + background: theme.palette.divider, + padding: "8px 16px", + }), + }, + }, + MuiAlert: { + defaultProps: { + variant: "outlined", + }, + styleOverrides: { + root: ({ theme }) => ({ + background: theme.palette.background.paper, + }), + action: { + paddingTop: 2, // Idk why it is not aligned as expected + }, + icon: { + fontSize: 16, + marginTop: "4px", // The size of text is 24 so (24 - 16)/2 = 4 + }, + message: ({ theme }) => ({ + color: theme.palette.text.primary, + }), + outlinedWarning: ({ theme }) => ({ + [`& .${alertClasses.icon}`]: { + color: theme.palette.warning.light, + }, + }), + outlinedInfo: ({ theme }) => ({ + [`& .${alertClasses.icon}`]: { + color: theme.palette.primary.light, + }, + }), + outlinedError: ({ theme }) => ({ + [`& .${alertClasses.icon}`]: { + color: theme.palette.error.light, + }, + }), + }, + }, + MuiAlertTitle: { + styleOverrides: { + root: { + fontSize: "inherit", + marginBottom: 0, + }, + }, + }, + + MuiIconButton: { + styleOverrides: { + root: { + "&.Mui-focusVisible": { + boxShadow: `0 0 0 2px ${tw.blue[400]}`, + }, + }, + }, + }, +}; diff --git a/site/src/theme/tailwind.ts b/site/src/theme/tailwindColors.ts similarity index 100% rename from site/src/theme/tailwind.ts rename to site/src/theme/tailwindColors.ts