site: new dark theme (#10331)

This commit is contained in:
Kayla Washburn
2023-11-15 16:39:26 -05:00
committed by GitHub
parent bd17290ff4
commit 8ddc8b3447
50 changed files with 338 additions and 559 deletions

View File

@ -7,14 +7,21 @@ import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
import { withRouter } from "storybook-addon-react-router-v6"; import { withRouter } from "storybook-addon-react-router-v6";
import { HelmetProvider } from "react-helmet-async"; import { HelmetProvider } from "react-helmet-async";
import { dark } from "theme/mui"; import { dark } from "theme/mui";
import { dark as experimental } from "theme/experimental";
import colors from "theme/tailwind";
import "theme/globalFonts"; import "theme/globalFonts";
import { QueryClient, QueryClientProvider } from "react-query"; import { QueryClient, QueryClientProvider } from "react-query";
const theme = {
...dark,
experimental,
};
export const decorators = [ export const decorators = [
(Story) => ( (Story) => (
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
<MuiThemeProvider theme={dark}> <MuiThemeProvider theme={theme}>
<EmotionThemeProvider theme={dark}> <EmotionThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<Story /> <Story />
</EmotionThemeProvider> </EmotionThemeProvider>
@ -39,6 +46,19 @@ export const decorators = [
]; ];
export const parameters = { export const parameters = {
backgrounds: {
default: "dark",
values: [
{
name: "dark",
value: colors.gray[950],
},
{
name: "light",
value: colors.gray[50],
},
],
},
actions: { actions: {
argTypesRegex: "^(on|handler)[A-Z].*", argTypesRegex: "^(on|handler)[A-Z].*",
}, },

View File

@ -1,7 +1,7 @@
import CssBaseline from "@mui/material/CssBaseline"; import CssBaseline from "@mui/material/CssBaseline";
import { QueryClient, QueryClientProvider } from "react-query"; import { QueryClient, QueryClientProvider } from "react-query";
import { AuthProvider } from "components/AuthProvider/AuthProvider"; import { AuthProvider } from "components/AuthProvider/AuthProvider";
import { FC, PropsWithChildren, ReactNode } from "react"; import type { FC, PropsWithChildren, ReactNode } from "react";
import { HelmetProvider } from "react-helmet-async"; import { HelmetProvider } from "react-helmet-async";
import { AppRouter } from "./AppRouter"; import { AppRouter } from "./AppRouter";
import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary"; import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary";

View File

@ -10,62 +10,50 @@ const meta: Meta<typeof Avatar> = {
export default meta; export default meta;
type Story = StoryObj<typeof Avatar>; type Story = StoryObj<typeof Avatar>;
export const Letter: Story = { export const WithLetter: Story = {
args: { args: {
children: "Coder", children: "Coder",
}, },
}; };
export const LetterXL = { export const WithLetterXL = {
args: { args: {
children: "Coder", children: "Coder",
size: "xl", size: "xl",
}, },
}; };
export const LetterDarken = { export const WithImage = {
args: {
children: "Coder",
colorScheme: "darken",
},
};
export const Image = {
args: { args: {
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4", src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
}, },
}; };
export const ImageXL = { export const WithImageXL = {
args: { args: {
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4", src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
size: "xl", size: "xl",
}, },
}; };
export const MuiIcon = { export const WithMuiIcon = {
args: { args: {
background: true,
children: <PauseIcon />, children: <PauseIcon />,
}, },
}; };
export const MuiIconDarken = { export const WithMuiIconXL = {
args: {
children: <PauseIcon />,
colorScheme: "darken",
},
};
export const MuiIconXL = {
args: { args: {
background: true,
children: <PauseIcon />, children: <PauseIcon />,
size: "xl", size: "xl",
}, },
}; };
export const AvatarIconDarken = { export const WithAvatarIcon = {
args: { args: {
background: true,
children: <AvatarIcon src="/icon/database.svg" alt="Database" />, children: <AvatarIcon src="/icon/database.svg" alt="Database" />,
colorScheme: "darken",
}, },
}; };

View File

@ -8,7 +8,7 @@ import { visuallyHidden } from "@mui/utils";
export type AvatarProps = MuiAvatarProps & { export type AvatarProps = MuiAvatarProps & {
size?: "xs" | "sm" | "md" | "xl"; size?: "xs" | "sm" | "md" | "xl";
colorScheme?: "light" | "darken"; background?: boolean;
fitImage?: boolean; fitImage?: boolean;
}; };
@ -33,14 +33,6 @@ const sizeStyles = {
}, },
} satisfies Record<string, Interpolation<Theme>>; } satisfies Record<string, Interpolation<Theme>>;
const colorStyles = {
light: {},
darken: (theme) => ({
background: theme.palette.divider,
color: theme.palette.text.primary,
}),
} satisfies Record<string, Interpolation<Theme>>;
const fitImageStyles = css` const fitImageStyles = css`
& .MuiAvatar-img { & .MuiAvatar-img {
object-fit: contain; object-fit: contain;
@ -49,18 +41,24 @@ const fitImageStyles = css`
export const Avatar: FC<AvatarProps> = ({ export const Avatar: FC<AvatarProps> = ({
size = "md", size = "md",
colorScheme = "light",
fitImage, fitImage,
children, children,
background,
...muiProps ...muiProps
}) => { }) => {
const fromName = !muiProps.src && typeof children === "string";
return ( return (
<MuiAvatar <MuiAvatar
{...muiProps} {...muiProps}
css={[ css={[
sizeStyles[size], sizeStyles[size],
colorStyles[colorScheme],
fitImage && fitImageStyles, fitImage && fitImageStyles,
(theme) => ({
background:
background || fromName ? theme.palette.divider : undefined,
color: theme.palette.text.primary,
}),
]} ]}
> >
{typeof children === "string" ? firstLetter(children) : children} {typeof children === "string" ? firstLetter(children) : children}

View File

@ -1,12 +1,12 @@
import { type ReactNode } from "react"; import { type ReactNode } from "react";
import { Avatar } from "components/Avatar/Avatar"; import { Avatar } from "components/Avatar/Avatar";
import { type CSSObject, useTheme } from "@emotion/react"; import { type CSSObject, useTheme } from "@emotion/react";
import { colors } from "theme/colors";
type AvatarCardProps = { type AvatarCardProps = {
header: string; header: string;
imgUrl: string; imgUrl: string;
altText: string; altText: string;
background?: boolean;
subtitle?: ReactNode; subtitle?: ReactNode;
maxWidth?: number | "none"; maxWidth?: number | "none";
@ -16,6 +16,7 @@ export function AvatarCard({
header, header,
imgUrl, imgUrl,
altText, altText,
background,
subtitle, subtitle,
maxWidth = "none", maxWidth = "none",
}: AvatarCardProps) { }: AvatarCardProps) {
@ -71,12 +72,7 @@ export function AvatarCard({
)} )}
</div> </div>
<Avatar <Avatar background={background} src={imgUrl} alt={altText} size="md">
src={imgUrl}
alt={altText}
size="md"
css={{ backgroundColor: colors.gray[7] }}
>
{header} {header}
</Avatar> </Avatar>
</div> </div>

View File

@ -0,0 +1,38 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
Badges,
AlphaBadge,
EnabledBadge,
EntitledBadge,
EnterpriseBadge,
} from "./Badges";
const meta: Meta<typeof Badges> = {
title: "components/Badges",
component: Badges,
args: {},
};
export default meta;
type Story = StoryObj<typeof Badges>;
export const Enabled: Story = {
args: {
children: <EnabledBadge />,
},
};
export const Entitled: Story = {
args: {
children: <EntitledBadge />,
},
};
export const Enterprise: Story = {
args: {
children: <EnterpriseBadge />,
},
};
export const Alpha: Story = {
args: {
children: <AlphaBadge />,
},
};

View File

@ -2,7 +2,7 @@ import type { PropsWithChildren, FC } from "react";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import { type Interpolation, type Theme } from "@emotion/react"; import { type Interpolation, type Theme } from "@emotion/react";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { colors } from "theme/colors"; import colors from "theme/tailwind";
const styles = { const styles = {
badge: { badge: {
@ -20,16 +20,16 @@ const styles = {
}, },
enabledBadge: (theme) => ({ enabledBadge: (theme) => ({
border: `1px solid ${theme.palette.success.light}`, border: `1px solid ${theme.experimental.roles.success.outline}`,
backgroundColor: theme.palette.success.dark, backgroundColor: theme.experimental.roles.success.background,
}), }),
errorBadge: (theme) => ({ errorBadge: (theme) => ({
border: `1px solid ${theme.palette.error.light}`, border: `1px solid ${theme.experimental.roles.error.outline}`,
backgroundColor: theme.palette.error.dark, backgroundColor: theme.experimental.roles.error.background,
}), }),
warnBadge: (theme) => ({ warnBadge: (theme) => ({
border: `1px solid ${theme.palette.warning.light}`, border: `1px solid ${theme.experimental.roles.warning.outline}`,
backgroundColor: theme.palette.warning.dark, backgroundColor: theme.experimental.roles.warning.background,
}), }),
} satisfies Record<string, Interpolation<Theme>>; } satisfies Record<string, Interpolation<Theme>>;
@ -111,9 +111,9 @@ export const AlphaBadge: FC = () => {
css={[ css={[
styles.badge, styles.badge,
{ {
border: `1px solid ${colors.violet[10]}`, border: `1px solid ${colors.violet[600]}`,
backgroundColor: colors.violet[14], backgroundColor: colors.violet[950],
color: colors.violet[1], color: colors.violet[50],
}, },
]} ]}
> >

View File

@ -46,7 +46,7 @@ export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
}} }}
badgeContent={<div></div>} badgeContent={<div></div>}
> >
<Avatar size={size} colorScheme="darken"> <Avatar background size={size}>
<BuildIcon transition={build.transition} /> <BuildIcon transition={build.transition} />
</Avatar> </Avatar>
</StyledBadge> </StyledBadge>

View File

@ -48,15 +48,13 @@ export const LicenseBannerView: React.FC<LicenseBannerViewProps> = ({
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px; padding: 12px;
background-color: ${type === "error" background-color: ${type === "error" ? colors.red[10] : colors.orange[10]};
? colors.red[12]
: theme.palette.warning.main};
`; `;
if (messages.length === 1) { if (messages.length === 1) {
return ( return (
<div css={containerStyles}> <div css={containerStyles}>
<Pill text={Language.licenseIssue} type={type} lightBorder /> <Pill text={Language.licenseIssue} type={type} />
<div css={styles.leftContent}> <div css={styles.leftContent}>
<span>{messages[0]}</span> <span>{messages[0]}</span>
&nbsp; &nbsp;
@ -70,11 +68,7 @@ export const LicenseBannerView: React.FC<LicenseBannerViewProps> = ({
return ( return (
<div css={containerStyles}> <div css={containerStyles}>
<Pill <Pill text={Language.licenseIssues(messages.length)} type={type} />
text={Language.licenseIssues(messages.length)}
type={type}
lightBorder
/>
<div css={styles.leftContent}> <div css={styles.leftContent}>
<div> <div>
{Language.exceeded} {Language.exceeded}

View File

@ -1,8 +1,7 @@
import { Pill } from "components/Pill/Pill"; import { Pill } from "components/Pill/Pill";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import { colors } from "theme/colors"; import { colors } from "theme/colors";
import { useTheme } from "@mui/system"; import { css, useTheme } from "@emotion/react";
import { css } from "@emotion/react";
import { readableForegroundColor } from "utils/colors"; import { readableForegroundColor } from "utils/colors";
export interface ServiceBannerViewProps { export interface ServiceBannerViewProps {
@ -43,7 +42,7 @@ export const ServiceBannerView: React.FC<ServiceBannerViewProps> = ({
} }
`} `}
> >
{isPreview && <Pill text="Preview" type="info" lightBorder />} {isPreview && <Pill text="Preview" type="info" />}
<div <div
css={css` css={css`
margin-right: auto; margin-right: auto;

View File

@ -3,20 +3,12 @@ import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined";
import { css, useTheme } from "@emotion/react"; import { css, useTheme } from "@emotion/react";
import type { PropsWithChildren, FC } from "react"; import type { PropsWithChildren, FC } from "react";
import { MONOSPACE_FONT_FAMILY } from "theme/constants"; import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { DisabledBadge, EnabledBadge } from "./Badges"; import { DisabledBadge, EnabledBadge } from "../Badges/Badges";
export const OptionName: FC<PropsWithChildren> = (props) => { export const OptionName: FC<PropsWithChildren> = (props) => {
const { children } = props; const { children } = props;
return ( return <span css={{ display: "block" }}>{children}</span>;
<span
css={{
display: "block",
}}
>
{children}
</span>
);
}; };
export const OptionDescription: FC<PropsWithChildren> = (props) => { export const OptionDescription: FC<PropsWithChildren> = (props) => {

View File

@ -12,8 +12,8 @@ import { Stack } from "components/Stack/Stack";
import type { ElementType, FC, PropsWithChildren, ReactNode } from "react"; import type { ElementType, FC, PropsWithChildren, ReactNode } from "react";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { useDashboard } from "components/Dashboard/DashboardProvider"; import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useTheme } from "@mui/system";
import { css } from "@emotion/css"; import { css } from "@emotion/css";
import { useTheme } from "@emotion/react";
const SidebarNavItem: FC< const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }> PropsWithChildren<{ href: string; icon: ReactNode }>
@ -74,11 +74,7 @@ export const Sidebar: React.FC = () => {
const dashboard = useDashboard(); const dashboard = useDashboard();
return ( return (
<nav <nav css={{ width: 245 }}>
css={{
width: 245,
}}
>
<SidebarNavItem <SidebarNavItem
href="general" href="general"
icon={<SidebarNavItemIcon icon={LaunchOutlined} />} icon={<SidebarNavItemIcon icon={LaunchOutlined} />}

View File

@ -97,14 +97,14 @@ const styles = {
}), }),
successButton: (theme) => ({ successButton: (theme) => ({
"&.MuiButton-contained": { "&.MuiButton-contained": {
backgroundColor: theme.palette.success.main, backgroundColor: theme.palette.success.dark,
"&:not(.MuiLoadingButton-loading)": { "&:not(.MuiLoadingButton-loading)": {
color: theme.palette.primary.contrastText, color: theme.palette.primary.contrastText,
}, },
"&:hover": { "&:hover": {
backgroundColor: theme.palette.success.dark, backgroundColor: theme.palette.success.main,
"@media (hover: none)": { "@media (hover: none)": {
backgroundColor: "transparent", backgroundColor: "transparent",

View File

@ -6,7 +6,7 @@ import {
type PropsWithChildren, type PropsWithChildren,
useContext, useContext,
} from "react"; } from "react";
import { AlphaBadge } from "components/DeploySettingsLayout/Badges"; import { AlphaBadge } from "components/Badges/Badges";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { import {
FormFooter as BaseFormFooter, FormFooter as BaseFormFooter,

View File

@ -37,7 +37,9 @@ export const GroupAvatar: FC<GroupAvatarProps> = ({ name, avatarURL }) => {
}} }}
badgeContent={<Group />} badgeContent={<Group />}
> >
<Avatar src={avatarURL}>{name}</Avatar> <Avatar src={avatarURL} background>
{name}
</Avatar>
</StyledBadge> </StyledBadge>
); );
}; };

View File

@ -15,6 +15,14 @@ export default meta;
type Story = StoryObj<typeof InfoTooltip>; type Story = StoryObj<typeof InfoTooltip>;
export const Example: Story = {}; export const Example: Story = {};
export const Notice: Story = {
args: {
type: "notice",
message: "Unfortunately, there's a radio connected to my brain",
},
};
export const Warning: Story = { export const Warning: Story = {
args: { args: {
type: "warning", type: "warning",

View File

@ -6,10 +6,11 @@ import {
} from "components/HelpTooltip/HelpTooltip"; } from "components/HelpTooltip/HelpTooltip";
import InfoIcon from "@mui/icons-material/InfoOutlined"; import InfoIcon from "@mui/icons-material/InfoOutlined";
import { css } from "@emotion/css"; import { css } from "@emotion/css";
import { colors } from "theme/colors"; import { useTheme } from "@emotion/react";
interface InfoTooltipProps { interface InfoTooltipProps {
type?: "warning" | "info"; // TODO: use a `ThemeRole` type or something
type?: "warning" | "notice" | "info";
title: ReactNode; title: ReactNode;
message: ReactNode; message: ReactNode;
} }
@ -17,12 +18,15 @@ interface InfoTooltipProps {
export const InfoTooltip: FC<InfoTooltipProps> = (props) => { export const InfoTooltip: FC<InfoTooltipProps> = (props) => {
const { title, message, type = "info" } = props; const { title, message, type = "info" } = props;
const theme = useTheme();
const iconColor = theme.experimental.roles[type].outline;
return ( return (
<HelpTooltip <HelpTooltip
size="small" size="small"
icon={InfoIcon} icon={InfoIcon}
iconClassName={css` iconClassName={css`
color: ${type === "info" ? colors.blue[5] : colors.yellow[5]}; color: ${iconColor};
`} `}
buttonClassName={css` buttonClassName={css`
opacity: 1; opacity: 1;

View File

@ -11,7 +11,7 @@ import { type FC, memo } from "react";
import ReactMarkdown, { type Options } from "react-markdown"; import ReactMarkdown, { type Options } from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import gfm from "remark-gfm"; import gfm from "remark-gfm";
import { colors } from "theme/colors"; import colors from "theme/tailwind";
import { darcula } from "react-syntax-highlighter/dist/cjs/styles/prism"; import { darcula } from "react-syntax-highlighter/dist/cjs/styles/prism";
interface MarkdownProps { interface MarkdownProps {
@ -227,7 +227,7 @@ const markdownStyles: Interpolation<Theme> = (theme: Theme) => ({
}, },
"& .key, & .property, & .inserted, .keyword": { "& .key, & .property, & .inserted, .keyword": {
color: colors.turquoise[7], color: colors.teal[300],
}, },
"& .deleted": { "& .deleted": {

View File

@ -1,8 +1,8 @@
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { type FC, type ReactNode } from "react"; import { type FC, type ReactNode } from "react";
import { type Interpolation, type Theme } from "@emotion/react"; import { type Interpolation, type Theme } from "@emotion/react";
import { EnterpriseBadge } from "components/Badges/Badges";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
export interface PaywallProps { export interface PaywallProps {
@ -21,12 +21,7 @@ export const Paywall: FC<React.PropsWithChildren<PaywallProps>> = (props) => {
<Typography variant="h5" css={styles.title}> <Typography variant="h5" css={styles.title}>
{message} {message}
</Typography> </Typography>
<Chip <EnterpriseBadge />
css={styles.enterpriseChip}
label="Enterprise"
size="small"
color="primary"
/>
</Stack> </Stack>
{description && ( {description && (

View File

@ -9,38 +9,16 @@ const meta: Meta<typeof Pill> = {
export default meta; export default meta;
type Story = StoryObj<typeof Pill>; type Story = StoryObj<typeof Pill>;
export const Primary: Story = { export const Default: Story = {
args: { args: {
text: "Primary", text: "Default",
type: "primary",
}, },
}; };
export const Secondary: Story = { export const Danger: Story = {
args: { args: {
text: "Secondary", text: "Danger",
type: "secondary", type: "danger",
},
};
export const Success: Story = {
args: {
text: "Success",
type: "success",
},
};
export const Info: Story = {
args: {
text: "Information",
type: "info",
},
};
export const Warning: Story = {
args: {
text: "Warning",
type: "warning",
}, },
}; };
@ -51,16 +29,37 @@ export const Error: Story = {
}, },
}; };
export const Default: Story = { export const Warning: Story = {
args: {
text: "Default",
},
};
export const WarningLight: Story = {
args: { args: {
text: "Warning", text: "Warning",
type: "warning", type: "warning",
lightBorder: true, },
};
export const Notice: Story = {
args: {
text: "Notice",
type: "notice",
},
};
export const Info: Story = {
args: {
text: "Information",
type: "info",
},
};
export const Success: Story = {
args: {
text: "Success",
type: "success",
},
};
export const Active: Story = {
args: {
text: "Active",
type: "active",
}, },
}; };

View File

@ -1,65 +1,43 @@
import { type FC, type ReactNode, useMemo, forwardRef } from "react"; import { type FC, type ReactNode, useMemo, forwardRef } from "react";
import { css, type Interpolation, type Theme } from "@emotion/react"; import { css, type Interpolation, type Theme } from "@emotion/react";
import { colors } from "theme/colors"; import { colors } from "theme/colors";
import type { ThemeRole } from "theme/experimental";
export type PillType = export type PillType = ThemeRole | keyof typeof themeOverrides;
| "primary"
| "secondary"
| "error"
| "warning"
| "info"
| "success"
| "neutral";
export interface PillProps { export interface PillProps {
className?: string; className?: string;
icon?: ReactNode; icon?: ReactNode;
text: ReactNode; text: ReactNode;
type?: PillType; type?: PillType;
lightBorder?: boolean;
title?: string; title?: string;
} }
const themeOverrides = { const themeOverrides = {
primary: (lightBorder) => ({ neutral: {
backgroundColor: colors.blue[13],
borderColor: lightBorder ? colors.blue[5] : colors.blue[7],
}),
secondary: (lightBorder) => ({
backgroundColor: colors.indigo[13],
borderColor: lightBorder ? colors.indigo[6] : colors.indigo[8],
}),
neutral: (lightBorder) => ({
backgroundColor: colors.gray[13], backgroundColor: colors.gray[13],
borderColor: lightBorder ? colors.gray[6] : colors.gray[8], borderColor: colors.gray[6],
}), },
} satisfies Record<string, (lightBorder?: boolean) => Interpolation<Theme>>; } satisfies Record<string, Interpolation<Theme>>;
const themeStyles = const themeStyles = (type: ThemeRole) => (theme: Theme) => {
(type: PillType, lightBorder?: boolean) => (theme: Theme) => { const palette = theme.experimental.roles[type];
const palette = theme.palette[type]; return {
return { backgroundColor: palette.background,
backgroundColor: palette.dark, borderColor: palette.outline,
borderColor: lightBorder ? palette.light : palette.main,
};
}; };
};
export const Pill: FC<PillProps> = forwardRef<HTMLDivElement, PillProps>( export const Pill: FC<PillProps> = forwardRef<HTMLDivElement, PillProps>(
(props, ref) => { (props, ref) => {
const { const { icon, text = null, type = "neutral", ...attrs } = props;
lightBorder,
icon,
text = null,
type = "neutral",
...attrs
} = props;
const typeStyles = useMemo(() => { const typeStyles = useMemo(() => {
if (type in themeOverrides) { if (type in themeOverrides) {
return themeOverrides[type as keyof typeof themeOverrides](lightBorder); return themeOverrides[type as keyof typeof themeOverrides];
} }
return themeStyles(type, lightBorder); return themeStyles(type as ThemeRole);
}, [type, lightBorder]); }, [type]);
return ( return (
<div <div

View File

@ -39,7 +39,7 @@ export const ResourceAvatar: FC<ResourceAvatarProps> = ({ resource }) => {
: getIconPathResource(resource.type); : getIconPathResource(resource.type);
return ( return (
<Avatar colorScheme="darken"> <Avatar background>
<AvatarIcon src={avatarSrc} alt={resource.name} /> <AvatarIcon src={avatarSrc} alt={resource.name} />
</Avatar> </Avatar>
); );

View File

@ -9,7 +9,6 @@ import { type FC } from "react";
import { TemplateVersionParameter } from "api/typesGenerated"; import { TemplateVersionParameter } from "api/typesGenerated";
import { MemoizedMarkdown } from "components/Markdown/Markdown"; import { MemoizedMarkdown } from "components/Markdown/Markdown";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { colors } from "theme/colors";
import { MultiTextField } from "./MultiTextField"; import { MultiTextField } from "./MultiTextField";
const isBoolean = (parameter: TemplateVersionParameter) => { const isBoolean = (parameter: TemplateVersionParameter) => {
@ -43,11 +42,6 @@ const styles = {
fontSize: 14, fontSize: 14,
}, },
}), }),
labelImmutable: {
marginTop: 4,
marginBottom: 4,
color: colors.yellow[7],
},
textField: { textField: {
".small & .MuiInputBase-root": { ".small & .MuiInputBase-root": {
height: 36, height: 36,

View File

@ -1,9 +1,7 @@
import { useTheme } from "@mui/styles";
import { useMonaco } from "@monaco-editor/react"; import { useMonaco } from "@monaco-editor/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { hslToHex } from "utils/colors";
import { editor } from "monaco-editor"; import { editor } from "monaco-editor";
import { Theme } from "@mui/material/styles"; import { type Theme, useTheme } from "@emotion/react";
// Theme based on https://github.com/brijeshb42/monaco-themes/blob/master/themes/Dracula.json // Theme based on https://github.com/brijeshb42/monaco-themes/blob/master/themes/Dracula.json
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- The theme is not typed // eslint-disable-next-line @typescript-eslint/no-explicit-any -- The theme is not typed
@ -208,12 +206,11 @@ export const coderTheme = (theme: Theme): editor.IStandaloneThemeData => ({
}, },
], ],
colors: { colors: {
"editor.foreground": hslToHex(theme.palette.text.primary), "editor.foreground": theme.palette.text.primary,
"editor.background": hslToHex(theme.palette.background.paper), "editor.background": theme.palette.background.paper,
"editor.selectionBackground": hslToHex(theme.palette.action.hover), "editor.selectionBackground": theme.palette.action.hover,
"editor.lineHighlightBackground": hslToHex( "editor.lineHighlightBackground": theme.palette.background.paperLight,
theme.palette.background.paperLight,
),
"editorCursor.foreground": "#f8f8f0", "editorCursor.foreground": "#f8f8f0",
"editorWhitespace.foreground": "#3B3A32", "editorWhitespace.foreground": "#3B3A32",
"editorIndentGuide.activeBackground": "#9D550FB0", "editorIndentGuide.activeBackground": "#9D550FB0",
@ -224,7 +221,7 @@ export const coderTheme = (theme: Theme): editor.IStandaloneThemeData => ({
export const useCoderTheme = (): { isLoading: boolean; name: string } => { export const useCoderTheme = (): { isLoading: boolean; name: string } => {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const monaco = useMonaco(); const monaco = useMonaco();
const theme = useTheme<Theme>(); const theme = useTheme();
const name = "coder"; const name = "coder";
useEffect(() => { useEffect(() => {

View File

@ -3,7 +3,7 @@ import { NavLink, NavLinkProps } from "react-router-dom";
import { combineClasses } from "utils/combineClasses"; import { combineClasses } from "utils/combineClasses";
import { Margins } from "components/Margins/Margins"; import { Margins } from "components/Margins/Margins";
import { css } from "@emotion/css"; import { css } from "@emotion/css";
import { useTheme } from "@mui/material/styles"; import { useTheme } from "@emotion/react";
export const Tabs = ({ children }: { children: ReactNode }) => { export const Tabs = ({ children }: { children: ReactNode }) => {
return ( return (

View File

@ -1,5 +1,5 @@
import { Avatar, AvatarProps } from "components/Avatar/Avatar"; import { Avatar, type AvatarProps } from "components/Avatar/Avatar";
import { FC } from "react"; import { type FC } from "react";
export type UserAvatarProps = { export type UserAvatarProps = {
username: string; username: string;
@ -12,7 +12,7 @@ export const UserAvatar: FC<UserAvatarProps> = ({
...avatarProps ...avatarProps
}) => { }) => {
return ( return (
<Avatar title={username} src={avatarURL} {...avatarProps}> <Avatar background title={username} src={avatarURL} {...avatarProps}>
{username} {username}
</Avatar> </Avatar>
); );

View File

@ -1,11 +1,12 @@
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";
import { type FC } from "react";
import InfoIcon from "@mui/icons-material/InfoOutlined"; import InfoIcon from "@mui/icons-material/InfoOutlined";
import { useQuery } from "react-query";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Skeleton from "@mui/material/Skeleton"; import Skeleton from "@mui/material/Skeleton";
import Link from "@mui/material/Link"; import Link from "@mui/material/Link";
import { type FC } from "react";
import { useQuery } from "react-query";
import { css } from "@emotion/css"; import { css } from "@emotion/css";
import { useTheme } from "@emotion/react";
import { templateVersion } from "api/queries/templates"; import { templateVersion } from "api/queries/templates";
import { import {
HelpTooltip, HelpTooltip,
@ -14,7 +15,6 @@ import {
HelpTooltipText, HelpTooltipText,
HelpTooltipTitle, HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip"; } from "components/HelpTooltip/HelpTooltip";
import { colors } from "theme/colors";
export const Language = { export const Language = {
outdatedLabel: "Outdated", outdatedLabel: "Outdated",
@ -37,13 +37,14 @@ export const WorkspaceOutdatedTooltip: FC<TooltipProps> = ({
templateName, templateName,
}) => { }) => {
const { data: activeVersion } = useQuery(templateVersion(latestVersionId)); const { data: activeVersion } = useQuery(templateVersion(latestVersionId));
const theme = useTheme();
return ( return (
<HelpTooltip <HelpTooltip
size="small" size="small"
icon={InfoIcon} icon={InfoIcon}
iconClassName={css` iconClassName={css`
color: ${colors.yellow[5]}; color: ${theme.experimental.roles.notice.outline};
`} `}
buttonClassName={css` buttonClassName={css`
opacity: 1; opacity: 1;

View File

@ -73,7 +73,14 @@ export const WorkspaceStatusText: FC<
role="status" role="status"
data-testid="build-status" data-testid="build-status"
className={className} className={className}
css={[styles.root, styles[`type-${type}`]]} css={[
styles.root,
(theme) => ({
color: type
? theme.experimental.roles[type].fill
: theme.experimental.l1.text,
}),
]}
> >
{text} {text}
</span> </span>

View File

@ -1,7 +1,7 @@
import { type Interpolation, type Theme } from "@emotion/react"; import { type Interpolation, type Theme } from "@emotion/react";
import { type FC } from "react"; import { type FC } from "react";
import type { AuditLog } from "api/typesGenerated"; import type { AuditLog } from "api/typesGenerated";
import { colors } from "theme/colors"; import colors from "theme/tailwind";
import { MONOSPACE_FONT_FAMILY } from "theme/constants"; import { MONOSPACE_FONT_FAMILY } from "theme/constants";
const getDiffValue = (value: unknown): string => { const getDiffValue = (value: unknown): string => {
@ -79,10 +79,10 @@ const styles = {
overflowWrap: "anywhere", overflowWrap: "anywhere",
}, },
diffOld: (theme) => ({ diffOld: {
backgroundColor: theme.palette.error.dark, backgroundColor: colors.red[950],
color: theme.palette.error.contrastText, color: colors.red[50],
}), },
diffRow: { diffRow: {
display: "flex", display: "flex",
@ -103,10 +103,10 @@ const styles = {
flexShrink: 0, flexShrink: 0,
}), }),
diffNew: (theme) => ({ diffNew: {
backgroundColor: theme.palette.success.dark, backgroundColor: colors.green[950],
color: theme.palette.success.contrastText, color: colors.green[50],
}), },
diffValue: { diffValue: {
padding: 1, padding: 1,
@ -114,10 +114,10 @@ const styles = {
}, },
diffValueOld: { diffValueOld: {
backgroundColor: colors.red[12], backgroundColor: colors.red[800],
}, },
diffValueNew: { diffValueNew: {
backgroundColor: colors.green[12], backgroundColor: colors.green[800],
}, },
} satisfies Record<string, Interpolation<Theme>>; } satisfies Record<string, Interpolation<Theme>>;

View File

@ -10,7 +10,7 @@ export const AuditPaywall: FC = () => {
return ( return (
<Paywall <Paywall
message="Audit logs" message="Audit logs"
description="Audit Logs allows Auditors to monitor user operations in their deployment. To use this feature, you have to upgrade your account." description="Audit Logs allows Auditors to monitor user operations in their deployment. To use this feature, you need an Enterprise license."
cta={ cta={
<Stack direction="row" alignItems="center"> <Stack direction="row" alignItems="center">
<Link href={docs("/admin/upgrade")} target="_blank" rel="noreferrer"> <Link href={docs("/admin/upgrade")} target="_blank" rel="noreferrer">

View File

@ -1,26 +1,25 @@
import InputAdornment from "@mui/material/InputAdornment";
import Button from "@mui/material/Button";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import TextField from "@mui/material/TextField";
import Link from "@mui/material/Link";
import { useTheme } from "@emotion/react";
import { useState } from "react"; import { useState } from "react";
import { BlockPicker } from "react-color";
import { useFormik } from "formik";
import type { UpdateAppearanceConfig } from "api/typesGenerated";
import { Header } from "components/DeploySettingsLayout/Header"; import { Header } from "components/DeploySettingsLayout/Header";
import { import {
Badges, Badges,
DisabledBadge, DisabledBadge,
EnterpriseBadge, EnterpriseBadge,
EntitledBadge, EntitledBadge,
} from "components/DeploySettingsLayout/Badges"; } from "components/Badges/Badges";
import InputAdornment from "@mui/material/InputAdornment";
import { Fieldset } from "components/DeploySettingsLayout/Fieldset"; import { Fieldset } from "components/DeploySettingsLayout/Fieldset";
import { getFormHelpers } from "utils/formUtils";
import Button from "@mui/material/Button";
import FormControlLabel from "@mui/material/FormControlLabel";
import { BlockPicker } from "react-color";
import Switch from "@mui/material/Switch";
import TextField from "@mui/material/TextField";
import type { UpdateAppearanceConfig } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { useFormik } from "formik"; import { getFormHelpers } from "utils/formUtils";
import Link from "@mui/material/Link";
import { colors } from "theme/colors"; import { colors } from "theme/colors";
import { hslToHex } from "utils/colors";
import { useTheme } from "@emotion/react";
export type AppearanceSettingsPageViewProps = { export type AppearanceSettingsPageViewProps = {
appearance: UpdateAppearanceConfig; appearance: UpdateAppearanceConfig;
@ -31,7 +30,7 @@ export type AppearanceSettingsPageViewProps = {
) => void; ) => void;
}; };
const fallbackBgColor = hslToHex(colors.blue[7]); const fallbackBgColor = colors.blue[7];
export const AppearanceSettingsPageView = ({ export const AppearanceSettingsPageView = ({
appearance, appearance,

View File

@ -7,7 +7,7 @@ import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import type { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated"; import type { DeploymentValues, ExternalAuthConfig } from "api/typesGenerated";
import { Alert } from "components/Alert/Alert"; import { Alert } from "components/Alert/Alert";
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges"; import { EnterpriseBadge } from "components/Badges/Badges";
import { Header } from "components/DeploySettingsLayout/Header"; import { Header } from "components/DeploySettingsLayout/Header";
import { docs } from "utils/docs"; import { docs } from "utils/docs";

View File

@ -1,6 +1,5 @@
import { type Interpolation, type Theme } from "@emotion/react"; import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { useTheme } from "@mui/styles";
import Skeleton from "@mui/material/Skeleton"; import Skeleton from "@mui/material/Skeleton";
import AddIcon from "@mui/icons-material/AddOutlined"; import AddIcon from "@mui/icons-material/AddOutlined";
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";

View File

@ -1,9 +1,5 @@
import { ClibaseOption } from "api/typesGenerated"; import type { ClibaseOption } from "api/typesGenerated";
import { import { Badges, EnabledBadge, DisabledBadge } from "components/Badges/Badges";
Badges,
EnabledBadge,
DisabledBadge,
} from "components/DeploySettingsLayout/Badges";
import { Header } from "components/DeploySettingsLayout/Header"; import { Header } from "components/DeploySettingsLayout/Header";
import OptionsTable from "components/DeploySettingsLayout/OptionsTable"; import OptionsTable from "components/DeploySettingsLayout/OptionsTable";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";

View File

@ -1,10 +1,10 @@
import { ClibaseOption } from "api/typesGenerated"; import type { ClibaseOption } from "api/typesGenerated";
import { import {
Badges, Badges,
DisabledBadge, DisabledBadge,
EnabledBadge, EnabledBadge,
EnterpriseBadge, EnterpriseBadge,
} from "components/DeploySettingsLayout/Badges"; } from "components/Badges/Badges";
import { Header } from "components/DeploySettingsLayout/Header"; import { Header } from "components/DeploySettingsLayout/Header";
import OptionsTable from "components/DeploySettingsLayout/OptionsTable"; import OptionsTable from "components/DeploySettingsLayout/OptionsTable";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";

View File

@ -1,10 +1,10 @@
import { ClibaseOption } from "api/typesGenerated"; import type { ClibaseOption } from "api/typesGenerated";
import { import {
Badges, Badges,
DisabledBadge, DisabledBadge,
EnabledBadge, EnabledBadge,
EnterpriseBadge, EnterpriseBadge,
} from "components/DeploySettingsLayout/Badges"; } from "components/Badges/Badges";
import { Header } from "components/DeploySettingsLayout/Header"; import { Header } from "components/DeploySettingsLayout/Header";
import OptionsTable from "components/DeploySettingsLayout/OptionsTable"; import OptionsTable from "components/DeploySettingsLayout/OptionsTable";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";

View File

@ -1,9 +1,5 @@
import { ClibaseOption } from "api/typesGenerated"; import type { ClibaseOption } from "api/typesGenerated";
import { import { Badges, DisabledBadge, EnabledBadge } from "components/Badges/Badges";
Badges,
DisabledBadge,
EnabledBadge,
} from "components/DeploySettingsLayout/Badges";
import { Header } from "components/DeploySettingsLayout/Header"; import { Header } from "components/DeploySettingsLayout/Header";
import OptionsTable from "components/DeploySettingsLayout/OptionsTable"; import OptionsTable from "components/DeploySettingsLayout/OptionsTable";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";

View File

@ -86,11 +86,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
<Avatar <Avatar size="sm" src={install.account.avatar_url}>
size="sm"
src={install.account.avatar_url}
colorScheme="darken"
>
{install.account.login} {install.account.login}
</Avatar> </Avatar>
</Link> </Link>

View File

@ -1,6 +1,6 @@
import LinearProgress from "@mui/material/LinearProgress"; import LinearProgress from "@mui/material/LinearProgress";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { styled, useTheme } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
import { BoxProps } from "@mui/system"; import { BoxProps } from "@mui/system";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { import {
@ -20,7 +20,7 @@ import { colors } from "theme/colors";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { getTemplatePageTitle } from "../utils"; import { getTemplatePageTitle } from "../utils";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { import type {
Entitlements, Entitlements,
Template, Template,
TemplateAppUsage, TemplateAppUsage,
@ -30,7 +30,8 @@ import {
UserActivityInsightsResponse, UserActivityInsightsResponse,
UserLatencyInsightsResponse, UserLatencyInsightsResponse,
} from "api/typesGenerated"; } from "api/typesGenerated";
import { ComponentProps, ReactNode } from "react"; import { useTheme } from "@emotion/react";
import { type ComponentProps, type ReactNode } from "react";
import { subDays, addWeeks, format } from "date-fns"; import { subDays, addWeeks, format } from "date-fns";
import "react-date-range/dist/styles.css"; import "react-date-range/dist/styles.css";
import "react-date-range/dist/theme/default.css"; import "react-date-range/dist/theme/default.css";

View File

@ -80,13 +80,13 @@ export const VersionRow: React.FC<VersionRowProps> = ({
{isLatest && <Pill text="Newest" type="info" />} {isLatest && <Pill text="Newest" type="info" />}
{jobStatus === "pending" && ( {jobStatus === "pending" && (
<Pill text={<>Pending&hellip;</>} type="warning" lightBorder /> <Pill text={<>Pending&hellip;</>} type="warning" />
)} )}
{jobStatus === "running" && ( {jobStatus === "running" && (
<Pill text={<>Building&hellip;</>} type="warning" lightBorder /> <Pill text={<>Building&hellip;</>} type="warning" />
)} )}
{(jobStatus === "canceling" || jobStatus === "canceled") && ( {(jobStatus === "canceling" || jobStatus === "canceled") && (
<Pill text="Canceled" type="neutral" lightBorder /> <Pill text="Canceled" type="neutral" />
)} )}
{jobStatus === "failed" && <Pill text="Failed" type="error" />} {jobStatus === "failed" && <Pill text="Failed" type="error" />}

View File

@ -1,9 +1,8 @@
import { useTheme } from "@mui/styles"; import { useTheme } from "@emotion/react";
import Editor, { loader } from "@monaco-editor/react"; import Editor, { loader } from "@monaco-editor/react";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { FC, useLayoutEffect, useMemo, useState } from "react"; import { FC, useLayoutEffect, useMemo, useState } from "react";
import { MONOSPACE_FONT_FAMILY } from "theme/constants"; import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { hslToHex } from "utils/colors";
import type { editor } from "monaco-editor"; import type { editor } from "monaco-editor";
loader.config({ monaco }); loader.config({ monaco });
@ -124,8 +123,8 @@ export const MonacoEditor: FC<{
}, },
], ],
colors: { colors: {
"editor.foreground": hslToHex(theme.palette.text.primary), "editor.foreground": theme.palette.text.primary,
"editor.background": hslToHex(theme.palette.background.default), "editor.background": theme.palette.background.default,
}, },
}); });
editor.updateOptions({ editor.updateOptions({

View File

@ -39,9 +39,9 @@ export const getStatus = (
}; };
case "pending": case "pending":
return { return {
type: "info",
text: "Pending", text: "Pending",
icon: <LoadingIcon />, icon: <LoadingIcon />,
type: "info",
}; };
case "canceling": case "canceling":
return { return {

View File

@ -51,6 +51,7 @@ export function AccountUserGroups({
{groups.map((group) => ( {groups.map((group) => (
<Grid item key={group.id} xs={1}> <Grid item key={group.id} xs={1}>
<AvatarCard <AvatarCard
background
imgUrl={group.avatar_url} imgUrl={group.avatar_url}
altText={group.display_name || group.name} altText={group.display_name || group.name}
header={group.display_name || group.name} header={group.display_name || group.name}

View File

@ -1,16 +1,16 @@
import { Region, WorkspaceProxy } from "api/typesGenerated"; import type { Region, WorkspaceProxy } from "api/typesGenerated";
import { AvatarData } from "components/AvatarData/AvatarData"; import { AvatarData } from "components/AvatarData/AvatarData";
import { Avatar } from "components/Avatar/Avatar"; import { Avatar } from "components/Avatar/Avatar";
import TableCell from "@mui/material/TableCell"; import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import { FC, ReactNode } from "react"; import type { FC, ReactNode } from "react";
import { import {
HealthyBadge, HealthyBadge,
NotHealthyBadge, NotHealthyBadge,
NotReachableBadge, NotReachableBadge,
NotRegisteredBadge, NotRegisteredBadge,
} from "components/DeploySettingsLayout/Badges"; } from "components/Badges/Badges";
import { ProxyLatencyReport } from "contexts/useProxyLatency"; import type { ProxyLatencyReport } from "contexts/useProxyLatency";
import { getLatencyColor } from "utils/latency"; import { getLatencyColor } from "utils/latency";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";

View File

@ -15,7 +15,7 @@ import {
TableLoaderSkeleton, TableLoaderSkeleton,
TableRowSkeleton, TableRowSkeleton,
} from "components/TableLoader/TableLoader"; } from "components/TableLoader/TableLoader";
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges"; import { EnterpriseBadge } from "components/Badges/Badges";
import HideSourceOutlined from "@mui/icons-material/HideSourceOutlined"; import HideSourceOutlined from "@mui/icons-material/HideSourceOutlined";
import KeyOutlined from "@mui/icons-material/KeyOutlined"; import KeyOutlined from "@mui/icons-material/KeyOutlined";
import GitHub from "@mui/icons-material/GitHub"; import GitHub from "@mui/icons-material/GitHub";

View File

@ -8,12 +8,7 @@ import {
import ScheduleIcon from "@mui/icons-material/TimerOutlined"; import ScheduleIcon from "@mui/icons-material/TimerOutlined";
import type { Workspace } from "api/typesGenerated"; import type { Workspace } from "api/typesGenerated";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { import { type FC, type PropsWithChildren, type ReactNode } from "react";
type FC,
type ComponentType,
type PropsWithChildren,
type ReactNode,
} from "react";
import { Link, NavLink } from "react-router-dom"; import { Link, NavLink } from "react-router-dom";
import { combineClasses } from "utils/combineClasses"; import { combineClasses } from "utils/combineClasses";
import GeneralIcon from "@mui/icons-material/SettingsOutlined"; import GeneralIcon from "@mui/icons-material/SettingsOutlined";
@ -74,13 +69,7 @@ const SidebarNavItem: FC<
); );
}; };
const SidebarNavItemIcon: FC<{ export const Sidebar: FC<{ username: string; workspace: Workspace }> = ({
icon: ComponentType<{ className?: string }>;
}> = ({ icon: Icon }) => {
return <Icon css={{ width: 16, height: 16 }} />;
};
export const Sidebar: React.FC<{ username: string; workspace: Workspace }> = ({
username, username,
workspace, workspace,
}) => { }) => {
@ -98,18 +87,21 @@ export const Sidebar: React.FC<{ username: string; workspace: Workspace }> = ({
</Stack> </Stack>
</Stack> </Stack>
<SidebarNavItem href="" icon={<SidebarNavItemIcon icon={GeneralIcon} />}> <SidebarNavItem
href=""
icon={<GeneralIcon css={styles.sidebarItemIcon} />}
>
General General
</SidebarNavItem> </SidebarNavItem>
<SidebarNavItem <SidebarNavItem
href="parameters" href="parameters"
icon={<SidebarNavItemIcon icon={ParameterIcon} />} icon={<ParameterIcon css={styles.sidebarItemIcon} />}
> >
Parameters Parameters
</SidebarNavItem> </SidebarNavItem>
<SidebarNavItem <SidebarNavItem
href="schedule" href="schedule"
icon={<SidebarNavItemIcon icon={ScheduleIcon} />} icon={<ScheduleIcon css={styles.sidebarItemIcon} />}
> >
Schedule Schedule
</SidebarNavItem> </SidebarNavItem>
@ -122,6 +114,10 @@ const styles = {
width: 245, width: 245,
flexShrink: 0, flexShrink: 0,
}, },
sidebarItemIcon: {
width: 16,
height: 16,
},
workspaceInfo: (theme) => ({ workspaceInfo: (theme) => ({
...(theme.typography.body2 as CSSObject), ...(theme.typography.body2 as CSSObject),
marginBottom: 16, marginBottom: 16,

View File

@ -1,274 +1,62 @@
// Based on https://codepen.io/hkfoster/pen/YzeYRwR import tw from "./tailwind";
import { getMetadataAsJSON } from "utils/metadata";
// When in development mode the experiments meta tag won't exist,
// so you can just set this to true.
export const experimentalTheme =
typeof document !== "undefined" &&
(getMetadataAsJSON<string[]>("experiments")?.includes("dashboard_theme") ??
false);
export const colors = { export const colors = {
white: "hsl(0, 0%, 100%)", white: "#fff",
gray: experimentalTheme gray: {
? { 17: tw.gray[950],
17: "hsl(0, 0%, 4%)", 16: tw.gray[900],
16: "hsl(0, 0%, 7%)", 14: tw.gray[800],
15: "hsl(0, 0%, 10%)", 13: tw.gray[700],
14: "hsl(0, 0%, 14%)", 12: tw.gray[600],
13: "hsl(0, 0%, 17%)", 11: tw.gray[500],
12: "hsl(0, 0%, 20%)", 9: tw.gray[400],
11: "hsl(0, 0%, 23%)", 6: tw.gray[300],
10: "hsl(0, 0%, 27%)", 4: tw.gray[200],
9: "hsl(0, 0%, 31%)", 2: tw.gray[100],
8: "hsl(0, 0%, 35%)", 1: tw.gray[50],
7: "hsl(0, 0%, 62%)", },
6: "hsl(0, 0%, 69%)",
5: "hsl(0, 0%, 75%)",
4: "hsl(0, 0%, 82%)",
3: "hsl(0, 0%, 90%)",
2: "hsl(0, 0%, 93%)",
1: "hsl(0, 0%, 96%)",
}
: {
17: "hsl(220, 50%, 3%)",
16: "hsl(223, 44%, 9%)",
15: "hsl(222, 38%, 14%)",
14: "hsl(222, 32%, 19%)",
13: "hsl(222, 31%, 25%)",
12: "hsl(222, 30%, 31%)",
11: "hsl(219, 29%, 38%)",
10: "hsl(219, 28%, 45%)",
9: "hsl(219, 28%, 52%)",
8: "hsl(218, 29%, 58%)",
7: "hsl(219, 30%, 64%)",
6: "hsl(219, 31%, 71%)",
5: "hsl(218, 32%, 77%)",
4: "hsl(223, 38%, 84%)",
3: "hsl(218, 44%, 92%)",
2: "hsl(220, 50%, 95%)",
1: "hsl(220, 55%, 98%)",
},
red: experimentalTheme red: {
? { 15: tw.red[950],
17: "hsl(355, 95%, 3%)", 12: tw.red[800],
16: "hsl(355, 88%, 8%)", 10: tw.red[700],
15: "hsl(355, 86%, 13%)", 9: tw.red[600],
14: "hsl(355, 84%, 18%)", 8: tw.red[500],
13: "hsl(355, 82%, 23%)", 6: tw.red[400],
12: "hsl(355, 74%, 28%)", 2: tw.red[50],
11: "hsl(355, 70%, 33%)", },
10: "hsl(355, 66%, 38%)",
9: "hsl(355, 69%, 43%)",
8: "hsl(355, 73%, 48%)",
7: "hsl(355, 76%, 53%)",
6: "hsl(355, 78%, 58%)",
5: "hsl(355, 79%, 63%)",
4: "hsl(355, 85%, 68%)",
3: "hsl(355, 88%, 73%)",
2: "hsl(355, 95%, 78%)",
1: "hsl(355, 100%, 83%) ",
}
: {
17: "hsl(355, 95%, 3%)",
16: "hsl(355, 88%, 9%)",
15: "hsl(355, 86%, 14%)",
14: "hsl(355, 84%, 19%)",
13: "hsl(355, 82%, 25%)",
12: "hsl(355, 74%, 31%)",
11: "hsl(355, 70%, 38%)",
10: "hsl(355, 66%, 45%)",
9: "hsl(355, 69%, 52%)",
8: "hsl(355, 73%, 58%)",
7: "hsl(355, 76%, 64%)",
6: "hsl(355, 78%, 71%)",
5: "hsl(355, 79%, 77%)",
4: "hsl(355, 85%, 84%)",
3: "hsl(355, 88%, 92%)",
2: "hsl(355, 95%, 96%)",
1: "hsl(355, 100%, 98%) ",
},
orange: { orange: {
17: "hsl(20, 100%, 3%)", 15: tw.amber[950],
16: "hsl(20, 98%, 9%)", 14: tw.amber[900],
15: "hsl(20, 97%, 14%)", 12: tw.amber[800],
14: "hsl(20, 93%, 19%)", 11: tw.amber[700],
13: "hsl(20, 88%, 25%)", 10: tw.amber[600],
12: "hsl(20, 84%, 32%)", 9: tw.amber[500],
11: "hsl(20, 80%, 38%)", 7: tw.amber[400],
10: "hsl(20, 76%, 46%)",
9: "hsl(20, 79%, 53%)",
8: "hsl(20, 83%, 59%)",
7: "hsl(20, 86%, 65%)",
6: "hsl(20, 88%, 72%)",
5: "hsl(20, 93%, 78%)",
4: "hsl(20, 97%, 84%)",
3: "hsl(20, 98%, 91%)",
2: "hsl(20, 99%, 96%)",
1: "hsl(20, 100%, 98%)",
}, },
yellow: { yellow: {
17: "hsl(48, 100%, 3%)", 5: tw.yellow[300],
16: "hsl(48, 87%, 9%)",
15: "hsl(48, 83%, 14%)",
14: "hsl(48, 88%, 19%)",
13: "hsl(48, 82%, 25%)",
12: "hsl(48, 74%, 32%)",
11: "hsl(48, 70%, 38%)",
10: "hsl(48, 66%, 46%)",
9: "hsl(48, 69%, 53%)",
8: "hsl(48, 73%, 59%)",
7: "hsl(48, 76%, 65%)",
6: "hsl(48, 78%, 72%)",
5: "hsl(48, 79%, 78%)",
4: "hsl(48, 85%, 84%)",
3: "hsl(48, 90%, 89%)",
2: "hsl(46, 95%, 93%)",
1: "hsl(46, 100%, 97%)",
}, },
green: { green: {
17: "hsl(132, 98%, 3%)", 15: tw.green[950],
16: "hsl(140, 94%, 8%)", 13: tw.green[700],
15: "hsl(144, 93%, 12%)", 12: tw.green[600],
14: "hsl(145, 99%, 16%)", 11: tw.green[500],
13: "hsl(143, 83%, 22%)", 9: tw.green[400],
12: "hsl(143, 74%, 27%)", 8: tw.green[300],
11: "hsl(142, 64%, 34%)",
10: "hsl(141, 57%, 41%)",
9: "hsl(140, 50%, 49%)",
8: "hsl(140, 51%, 54%)",
7: "hsl(138, 54%, 61%)",
6: "hsl(137, 56%, 68%)",
5: "hsl(137, 55%, 75%)",
4: "hsl(137, 60%, 82%)",
3: "hsl(136, 74%, 91%)",
2: "hsl(136, 82%, 95%)",
1: "hsl(136, 100%, 97%)",
},
turquoise: {
17: "hsl(162, 88%, 3%)",
16: "hsl(164, 84%, 8%)",
15: "hsl(165, 76%, 14%)",
14: "hsl(167, 85%, 18%)",
13: "hsl(166, 74%, 25%)",
12: "hsl(166, 65%, 32%)",
11: "hsl(165, 61%, 38%)",
10: "hsl(165, 56%, 46%)",
9: "hsl(165, 56%, 53%)",
8: "hsl(165, 60%, 57%)",
7: "hsl(164, 65%, 64%)",
6: "hsl(163, 66%, 70%)",
5: "hsl(163, 66%, 77%)",
4: "hsl(162, 74%, 83%)",
3: "hsl(162, 97%, 91%)",
2: "hsl(163, 93%, 94%)",
1: "hsl(163, 100%, 97%)",
},
cyan: {
17: "hsl(186, 100%, 3%)",
16: "hsl(185, 86%, 8%)",
15: "hsl(186, 80%, 14%)",
14: "hsl(185, 83%, 18%)",
13: "hsl(185, 76%, 25%)",
12: "hsl(185, 68%, 31%)",
11: "hsl(185, 64%, 37%)",
10: "hsl(185, 59%, 45%)",
9: "hsl(185, 57%, 52%)",
8: "hsl(185, 61%, 57%)",
7: "hsl(185, 64%, 63%)",
6: "hsl(185, 66%, 70%)",
5: "hsl(185, 66%, 77%)",
4: "hsl(185, 72%, 83%)",
3: "hsl(185, 91%, 91%)",
2: "hsl(182, 96%, 94%)",
1: "hsl(182, 100%, 97%)",
}, },
blue: { blue: {
17: "hsl(215, 100%, 3%)", 14: tw.blue[950],
16: "hsl(215, 92%, 7%)", 9: tw.blue[600],
15: "hsl(215, 88%, 12%)", 8: tw.blue[500],
14: "hsl(215, 93%, 17%)", 7: tw.blue[400],
13: "hsl(215, 87%, 23%)", 6: tw.blue[300],
12: "hsl(215, 79%, 30%)", 3: tw.blue[200],
11: "hsl(215, 75%, 36%)", 1: tw.blue[50],
10: "hsl(215, 71%, 44%)",
9: "hsl(215, 74%, 51%)",
8: "hsl(215, 78%, 57%)",
7: "hsl(215, 81%, 63%)",
6: "hsl(215, 83%, 70%)",
5: "hsl(215, 84%, 76%)",
4: "hsl(215, 90%, 82%)",
3: "hsl(215, 93%, 89%)",
2: "hsl(215, 97%, 95%)",
1: "hsl(215, 100%, 98%)",
},
indigo: {
17: "hsl(256, 100%, 3%)",
16: "hsl(254, 87%, 9%)",
15: "hsl(252, 83%, 14%)",
14: "hsl(250, 88%, 19%)",
13: "hsl(248, 82%, 25%)",
12: "hsl(246, 74%, 32%)",
11: "hsl(244, 70%, 38%)",
10: "hsl(242, 66%, 44%)",
9: "hsl(240, 67%, 53%)",
8: "hsl(238, 70%, 59%)",
7: "hsl(232, 74%, 63%)",
6: "hsl(236, 78%, 72%)",
5: "hsl(234, 79%, 78%)",
4: "hsl(232, 85%, 84%)",
3: "hsl(230, 90%, 91%)",
2: "hsl(228, 95%, 96%)",
1: "hsl(226, 100%, 98%)",
},
violet: {
17: "hsl(264, 100%, 3%)",
16: "hsl(265, 87%, 9%)",
15: "hsl(265, 83%, 14%)",
14: "hsl(265, 88%, 19%)",
13: "hsl(265, 82%, 25%)",
12: "hsl(265, 74%, 32%)",
11: "hsl(265, 70%, 38%)",
10: "hsl(265, 66%, 46%)",
9: "hsl(265, 69%, 53%)",
8: "hsl(265, 73%, 59%)",
7: "hsl(265, 76%, 65%)",
6: "hsl(265, 78%, 72%)",
5: "hsl(265, 79%, 78%)",
4: "hsl(264, 85%, 84%)",
3: "hsl(265, 90%, 91%)",
2: "hsl(265, 95%, 96%)",
1: "hsl(265, 100%, 98%)",
},
magenta: {
17: "hsl(316, 100%, 3%)",
16: "hsl(316, 86%, 9%)",
15: "hsl(314, 83%, 14%)",
14: "hsl(315, 85%, 19%)",
13: "hsl(315, 80%, 25%)",
12: "hsl(315, 71%, 31%)",
11: "hsl(315, 68%, 38%)",
10: "hsl(315, 62%, 45%)",
9: "hsl(315, 63%, 52%)",
8: "hsl(315, 67%, 58%)",
7: "hsl(315, 70%, 64%)",
6: "hsl(315, 71%, 71%)",
5: "hsl(315, 72%, 77%)",
4: "hsl(315, 79%, 84%)",
3: "hsl(316, 88%, 92%)",
2: "hsl(315, 95%, 96%)",
1: "hsl(315, 100%, 98%)",
}, },
}; };

View File

@ -1,5 +1,7 @@
import colors from "./tailwind"; import colors from "./tailwind";
export type ThemeRole = keyof NewTheme["roles"];
export interface NewTheme { export interface NewTheme {
l1: Role; // page background, things which sit at the "root level" l1: Role; // page background, things which sit at the "root level"
l2: InteractiveRole; // sidebars, table headers, navigation l2: InteractiveRole; // sidebars, table headers, navigation
@ -34,7 +36,7 @@ export const dark: NewTheme = {
l1: { l1: {
background: colors.gray[950], background: colors.gray[950],
outline: colors.gray[700], outline: colors.gray[700],
fill: "#f00", fill: colors.gray[600],
text: colors.white, text: colors.white,
}, },
@ -60,7 +62,7 @@ export const dark: NewTheme = {
l3: { l3: {
background: colors.gray[800], background: colors.gray[800],
outline: colors.gray[700], outline: colors.gray[700],
fill: "#f00", fill: colors.gray[600],
text: colors.white, text: colors.white,
disabled: { disabled: {
background: "#f00", background: "#f00",
@ -117,7 +119,7 @@ export const dark: NewTheme = {
notice: { notice: {
background: colors.yellow[950], background: colors.yellow[950],
outline: colors.yellow[200], outline: colors.yellow[200],
fill: "#f00", fill: colors.yellow[500],
text: colors.yellow[50], text: colors.yellow[50],
}, },
info: { info: {
@ -147,7 +149,7 @@ export const dark: NewTheme = {
active: { active: {
background: colors.sky[950], background: colors.sky[950],
outline: colors.sky[500], outline: colors.sky[500],
fill: "#f00", fill: colors.sky[600],
text: colors.sky[50], text: colors.sky[50],
}, },
}, },

View File

@ -1,4 +1,4 @@
import { colors, experimentalTheme } from "./colors"; import { colors } from "./colors";
import { createTheme, type ThemeOptions } from "@mui/material/styles"; import { createTheme, type ThemeOptions } from "@mui/material/styles";
import { import {
BODY_FONT_FAMILY, BODY_FONT_FAMILY,
@ -32,27 +32,27 @@ export let dark = createTheme({
secondary: { secondary: {
main: colors.gray[11], main: colors.gray[11],
contrastText: colors.gray[4], contrastText: colors.gray[4],
dark: colors.indigo[7], dark: colors.gray[9],
}, },
background: { background: {
default: colors.gray[17], default: colors.gray[17],
paper: colors.gray[16], paper: colors.gray[16],
paperLight: colors.gray[15], paperLight: colors.gray[14],
}, },
text: { text: {
primary: colors.gray[1], primary: colors.gray[1],
secondary: colors.gray[5], secondary: colors.gray[4],
disabled: colors.gray[7], disabled: colors.gray[9],
}, },
divider: colors.gray[13], divider: colors.gray[13],
warning: { warning: {
light: experimentalTheme ? colors.orange[9] : colors.orange[7], light: colors.orange[9],
main: experimentalTheme ? colors.orange[11] : colors.orange[9], main: colors.orange[12],
dark: colors.orange[15], dark: colors.orange[15],
}, },
success: { success: {
main: colors.green[11], main: colors.green[11],
dark: colors.green[15], dark: colors.green[12],
}, },
info: { info: {
light: colors.blue[7], light: colors.blue[7],
@ -172,7 +172,7 @@ dark = createTheme(dark, {
}, },
outlined: { outlined: {
":hover": { ":hover": {
border: `1px solid ${colors.gray[10]}`, border: `1px solid ${colors.gray[11]}`,
}, },
}, },
outlinedNeutral: { outlinedNeutral: {
@ -214,7 +214,7 @@ dark = createTheme(dark, {
root: { root: {
">button:hover+button": { ">button:hover+button": {
// The !important is unfortunate, but necessary for the border. // The !important is unfortunate, but necessary for the border.
borderLeftColor: `${colors.gray[10]} !important`, borderLeftColor: `${colors.gray[11]} !important`,
}, },
}, },
}, },
@ -407,7 +407,7 @@ dark = createTheme(dark, {
// The default outlined input color is white, which seemed jarring. // The default outlined input color is white, which seemed jarring.
"&:hover:not(.Mui-error):not(.Mui-focused) .MuiOutlinedInput-notchedOutline": "&:hover:not(.Mui-error):not(.Mui-focused) .MuiOutlinedInput-notchedOutline":
{ {
borderColor: colors.gray[10], borderColor: colors.gray[11],
}, },
}, },
}, },

View File

@ -173,43 +173,43 @@ export const getDisplayWorkspaceStatus = (
} as const; } as const;
case "starting": case "starting":
return { return {
type: "success", type: "active",
text: "Starting", text: "Starting",
icon: <LoadingIcon />, icon: <LoadingIcon />,
} as const; } as const;
case "stopping": case "stopping":
return { return {
type: "warning", type: "notice",
text: "Stopping", text: "Stopping",
icon: <LoadingIcon />, icon: <LoadingIcon />,
} as const; } as const;
case "stopped": case "stopped":
return { return {
type: "warning", type: "notice",
text: "Stopped", text: "Stopped",
icon: <StopIcon />, icon: <StopIcon />,
} as const; } as const;
case "deleting": case "deleting":
return { return {
type: "warning", type: "danger",
text: "Deleting", text: "Deleting",
icon: <LoadingIcon />, icon: <LoadingIcon />,
} as const; } as const;
case "deleted": case "deleted":
return { return {
type: "error", type: "danger",
text: "Deleted", text: "Deleted",
icon: <ErrorIcon />, icon: <ErrorIcon />,
} as const; } as const;
case "canceling": case "canceling":
return { return {
type: "warning", type: "notice",
text: "Canceling", text: "Canceling",
icon: <LoadingIcon />, icon: <LoadingIcon />,
} as const; } as const;
case "canceled": case "canceled":
return { return {
type: "warning", type: "notice",
text: "Canceled", text: "Canceled",
icon: <ErrorIcon />, icon: <ErrorIcon />,
} as const; } as const;
@ -221,7 +221,7 @@ export const getDisplayWorkspaceStatus = (
} as const; } as const;
case "pending": case "pending":
return { return {
type: "info", type: undefined,
text: getPendingWorkspaceStatusText(provisionerJob), text: getPendingWorkspaceStatusText(provisionerJob),
icon: <QueuedIcon />, icon: <QueuedIcon />,
} as const; } as const;