mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add light theme (#11266)
This commit is contained in:
@@ -351,6 +351,7 @@ jobs:
|
||||
- name: Install/Upgrade Helm chart
|
||||
run: |
|
||||
set -euo pipefail
|
||||
helm dependency update --skip-refresh ./helm/coder
|
||||
helm upgrade --install "pr${{ env.PR_NUMBER }}" ./helm/coder \
|
||||
--namespace "pr${{ env.PR_NUMBER }}" \
|
||||
--values ./pr-deploy-values.yaml \
|
||||
|
||||
@@ -3,10 +3,16 @@ import turbosnap from "vite-plugin-turbosnap";
|
||||
module.exports = {
|
||||
stories: ["../src/**/*.stories.tsx"],
|
||||
addons: [
|
||||
{
|
||||
name: "@storybook/addon-essentials",
|
||||
options: {
|
||||
backgrounds: false,
|
||||
},
|
||||
},
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-mdx-gfm",
|
||||
"@storybook/addon-actions",
|
||||
"@storybook/addon-themes",
|
||||
],
|
||||
staticDirs: ["../static"],
|
||||
framework: {
|
||||
|
||||
+41
-26
@@ -4,24 +4,33 @@ import {
|
||||
ThemeProvider as MuiThemeProvider,
|
||||
} from "@mui/material/styles";
|
||||
import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
|
||||
import { DecoratorHelpers } from "@storybook/addon-themes";
|
||||
import { withRouter } from "storybook-addon-react-router-v6";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
import theme from "theme";
|
||||
import colors from "theme/tailwind";
|
||||
import "theme/globalFonts";
|
||||
import { QueryClient, QueryClientProvider } from "react-query";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
import themes from "theme";
|
||||
import "theme/globalFonts";
|
||||
|
||||
DecoratorHelpers.initializeThemeState(Object.keys(themes), "dark");
|
||||
|
||||
export const decorators = [
|
||||
(Story) => (
|
||||
<StyledEngineProvider injectFirst>
|
||||
<MuiThemeProvider theme={theme.dark}>
|
||||
<EmotionThemeProvider theme={theme.dark}>
|
||||
<CssBaseline />
|
||||
<Story />
|
||||
</EmotionThemeProvider>
|
||||
</MuiThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
),
|
||||
(Story, context) => {
|
||||
const selectedTheme = DecoratorHelpers.pluckThemeFromContext(context);
|
||||
const { themeOverride } = DecoratorHelpers.useThemeParameters();
|
||||
|
||||
const selected = themeOverride || selectedTheme || "dark";
|
||||
|
||||
return (
|
||||
<StyledEngineProvider injectFirst>
|
||||
<MuiThemeProvider theme={themes[selected]}>
|
||||
<EmotionThemeProvider theme={themes[selected]}>
|
||||
<CssBaseline />
|
||||
<Story />
|
||||
</EmotionThemeProvider>
|
||||
</MuiThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
);
|
||||
},
|
||||
withRouter,
|
||||
(Story) => {
|
||||
return (
|
||||
@@ -50,18 +59,12 @@ export const decorators = [
|
||||
];
|
||||
|
||||
export const parameters = {
|
||||
backgrounds: {
|
||||
default: "dark",
|
||||
values: [
|
||||
{
|
||||
name: "dark",
|
||||
value: colors.gray[950],
|
||||
},
|
||||
{
|
||||
name: "light",
|
||||
value: colors.gray[50],
|
||||
},
|
||||
],
|
||||
options: {
|
||||
storySort: {
|
||||
method: "alphabetical",
|
||||
order: ["design", "pages", "components"],
|
||||
locales: "en-US",
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
argTypesRegex: "^(on|handler)[A-Z].*",
|
||||
@@ -73,4 +76,16 @@ export const parameters = {
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
viewport: {
|
||||
viewports: {
|
||||
ipad: {
|
||||
name: "iPad Mini",
|
||||
styles: {
|
||||
height: "1024px",
|
||||
width: "768px",
|
||||
},
|
||||
type: "tablet",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
"@storybook/addon-essentials": "7.5.2",
|
||||
"@storybook/addon-links": "7.5.2",
|
||||
"@storybook/addon-mdx-gfm": "7.5.2",
|
||||
"@storybook/addon-themes": "7.6.4",
|
||||
"@storybook/react": "7.5.2",
|
||||
"@storybook/react-vite": "7.5.2",
|
||||
"@swc/core": "1.3.38",
|
||||
|
||||
Generated
+9
@@ -233,6 +233,9 @@ devDependencies:
|
||||
'@storybook/addon-mdx-gfm':
|
||||
specifier: 7.5.2
|
||||
version: 7.5.2
|
||||
'@storybook/addon-themes':
|
||||
specifier: 7.6.4
|
||||
version: 7.6.4
|
||||
'@storybook/react':
|
||||
specifier: 7.5.2
|
||||
version: 7.5.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)
|
||||
@@ -4087,6 +4090,12 @@ packages:
|
||||
- '@types/react-dom'
|
||||
dev: true
|
||||
|
||||
/@storybook/addon-themes@7.6.4:
|
||||
resolution: {integrity: sha512-jz6/6LSRVgL9G5vknxROGOakXAsTIIl2sR7tkuC4gyGkCIGwvb9oO9jUaHkUHQ8rtcLXbVpFncQzUXmrwK6CGg==}
|
||||
dependencies:
|
||||
ts-dedent: 2.2.0
|
||||
dev: true
|
||||
|
||||
/@storybook/addon-toolbars@7.5.2(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-BXzb5NOpILFOM7EOBxcF2Qj/q6BicWZ1AvAddORWGmqSa/MxMIa4X52oKXFUTHKBkrTO1X0XqHmoF88qm3TUFg==}
|
||||
peerDependencies:
|
||||
|
||||
Vendored
-5
@@ -1,11 +1,6 @@
|
||||
import type { PaletteColor, PaletteColorOptions } from "@mui/material/styles";
|
||||
import type { NewTheme } from "theme/experimental";
|
||||
|
||||
declare module "@mui/material/styles" {
|
||||
interface Theme {
|
||||
experimental: NewTheme;
|
||||
}
|
||||
|
||||
interface Palette {
|
||||
neutral: PaletteColor;
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "react-query";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
import { AppRouter } from "./AppRouter";
|
||||
import { ThemeProviders } from "./contexts/ThemeProviders";
|
||||
import { ThemeProvider } from "./contexts/ThemeProvider";
|
||||
import { AuthProvider } from "./contexts/AuthProvider/AuthProvider";
|
||||
import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary";
|
||||
import { GlobalSnackbar } from "./components/GlobalSnackbar/GlobalSnackbar";
|
||||
@@ -30,10 +30,10 @@ export const AppProviders: FC<AppProvidersProps> = ({
|
||||
<HelmetProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<ThemeProviders>
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
<GlobalSnackbar />
|
||||
</ThemeProviders>
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
</HelmetProvider>
|
||||
|
||||
@@ -96,6 +96,7 @@ export const ActiveUserChart: FC<ActiveUserChartProps> = ({
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: { color: theme.palette.divider },
|
||||
suggestedMin: 0,
|
||||
ticks: {
|
||||
precision: 0,
|
||||
@@ -103,6 +104,7 @@ export const ActiveUserChart: FC<ActiveUserChartProps> = ({
|
||||
},
|
||||
|
||||
x: {
|
||||
grid: { color: theme.palette.divider },
|
||||
ticks: {
|
||||
stepSize: data.length > 10 ? 2 : undefined,
|
||||
},
|
||||
@@ -124,11 +126,9 @@ export const ActiveUserChart: FC<ActiveUserChartProps> = ({
|
||||
{
|
||||
label: `${interval === "day" ? "Daily" : "Weekly"} Active Users`,
|
||||
data: chartData,
|
||||
pointBackgroundColor: theme.palette.info.light,
|
||||
pointBorderColor: theme.palette.info.light,
|
||||
borderColor: theme.palette.info.light,
|
||||
backgroundColor: theme.palette.info.dark,
|
||||
fill: "origin",
|
||||
pointBackgroundColor: theme.experimental.roles.active.outline,
|
||||
pointBorderColor: theme.experimental.roles.active.outline,
|
||||
borderColor: theme.experimental.roles.active.outline,
|
||||
},
|
||||
],
|
||||
}}
|
||||
|
||||
@@ -2,9 +2,15 @@ import type { Meta, StoryObj } from "@storybook/react";
|
||||
import {
|
||||
Badges,
|
||||
AlphaBadge,
|
||||
BetaBadge,
|
||||
DisabledBadge,
|
||||
EnabledBadge,
|
||||
EntitledBadge,
|
||||
EnterpriseBadge,
|
||||
HealthyBadge,
|
||||
NotHealthyBadge,
|
||||
NotRegisteredBadge,
|
||||
NotReachableBadge,
|
||||
} from "./Badges";
|
||||
|
||||
const meta: Meta<typeof Badges> = {
|
||||
@@ -26,11 +32,34 @@ export const Entitled: Story = {
|
||||
children: <EntitledBadge />,
|
||||
},
|
||||
};
|
||||
export const ProxyStatus: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<HealthyBadge />
|
||||
<HealthyBadge derpOnly />
|
||||
<NotHealthyBadge />
|
||||
<NotRegisteredBadge />
|
||||
<NotReachableBadge />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
children: <DisabledBadge />,
|
||||
},
|
||||
};
|
||||
export const Enterprise: Story = {
|
||||
args: {
|
||||
children: <EnterpriseBadge />,
|
||||
},
|
||||
};
|
||||
export const Beta: Story = {
|
||||
args: {
|
||||
children: <BetaBadge />,
|
||||
},
|
||||
};
|
||||
export const Alpha: Story = {
|
||||
args: {
|
||||
children: <AlphaBadge />,
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { PropsWithChildren, FC } from "react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import colors from "theme/tailwind";
|
||||
|
||||
const styles = {
|
||||
badge: {
|
||||
@@ -22,14 +21,17 @@ const styles = {
|
||||
enabledBadge: (theme) => ({
|
||||
border: `1px solid ${theme.experimental.roles.success.outline}`,
|
||||
backgroundColor: theme.experimental.roles.success.background,
|
||||
color: theme.experimental.roles.success.text,
|
||||
}),
|
||||
errorBadge: (theme) => ({
|
||||
border: `1px solid ${theme.experimental.roles.error.outline}`,
|
||||
backgroundColor: theme.experimental.roles.error.background,
|
||||
color: theme.experimental.roles.error.text,
|
||||
}),
|
||||
warnBadge: (theme) => ({
|
||||
border: `1px solid ${theme.experimental.roles.warning.outline}`,
|
||||
backgroundColor: theme.experimental.roles.warning.background,
|
||||
color: theme.experimental.roles.warning.text,
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -42,10 +44,9 @@ export const EntitledBadge: FC = () => {
|
||||
};
|
||||
|
||||
interface HealthyBadge {
|
||||
derpOnly: boolean;
|
||||
derpOnly?: boolean;
|
||||
}
|
||||
export const HealthyBadge: FC<HealthyBadge> = (props) => {
|
||||
const { derpOnly } = props;
|
||||
export const HealthyBadge: FC<HealthyBadge> = ({ derpOnly }) => {
|
||||
return (
|
||||
<span css={[styles.badge, styles.enabledBadge]}>
|
||||
{derpOnly ? "Healthy (DERP only)" : "Healthy"}
|
||||
@@ -79,8 +80,9 @@ export const DisabledBadge: FC = () => {
|
||||
css={[
|
||||
styles.badge,
|
||||
(theme) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.experimental.l1.outline}`,
|
||||
backgroundColor: theme.experimental.l1.background,
|
||||
color: theme.experimental.l1.text,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
@@ -95,8 +97,9 @@ export const EnterpriseBadge: FC = () => {
|
||||
css={[
|
||||
styles.badge,
|
||||
(theme) => ({
|
||||
backgroundColor: theme.palette.info.dark,
|
||||
border: `1px solid ${theme.palette.info.light}`,
|
||||
backgroundColor: theme.experimental.roles.info.background,
|
||||
border: `1px solid ${theme.experimental.roles.info.outline}`,
|
||||
color: theme.experimental.roles.info.text,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
@@ -105,16 +108,33 @@ export const EnterpriseBadge: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const BetaBadge: FC = () => {
|
||||
return (
|
||||
<span
|
||||
css={[
|
||||
styles.badge,
|
||||
(theme) => ({
|
||||
border: `1px solid ${theme.experimental.roles.preview.outline}`,
|
||||
backgroundColor: theme.experimental.roles.preview.background,
|
||||
color: theme.experimental.roles.preview.text,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const AlphaBadge: FC = () => {
|
||||
return (
|
||||
<span
|
||||
css={[
|
||||
styles.badge,
|
||||
{
|
||||
border: `1px solid ${colors.violet[600]}`,
|
||||
backgroundColor: colors.violet[950],
|
||||
color: colors.violet[50],
|
||||
},
|
||||
(theme) => ({
|
||||
border: `1px solid ${theme.experimental.roles.preview.outline}`,
|
||||
backgroundColor: theme.experimental.roles.preview.background,
|
||||
color: theme.experimental.roles.preview.text,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
Alpha
|
||||
@@ -127,11 +147,11 @@ export const DeprecatedBadge: FC = () => {
|
||||
<span
|
||||
css={[
|
||||
styles.badge,
|
||||
{
|
||||
border: `1px solid ${colors.orange[600]}`,
|
||||
backgroundColor: colors.orange[950],
|
||||
color: colors.orange[50],
|
||||
},
|
||||
(theme) => ({
|
||||
border: `1px solid ${theme.experimental.roles.danger.outline}`,
|
||||
backgroundColor: theme.experimental.roles.danger.background,
|
||||
color: theme.experimental.roles.danger.text,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
Deprecated
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { CodeExample } from "./CodeExample";
|
||||
|
||||
const sampleCode = `echo "Hello, world"`;
|
||||
|
||||
const meta: Meta<typeof CodeExample> = {
|
||||
title: "components/CodeExample",
|
||||
component: CodeExample,
|
||||
argTypes: {
|
||||
code: { control: "string", defaultValue: sampleCode },
|
||||
args: {
|
||||
code: `echo "hello, friend!"`,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CodeExample>;
|
||||
|
||||
export const Example: Story = {
|
||||
export const Example: Story = {};
|
||||
|
||||
export const Secret: Story = {
|
||||
args: {
|
||||
code: sampleCode,
|
||||
secret: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
import { type FC } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
import { CopyButton } from "../CopyButton/CopyButton";
|
||||
|
||||
export interface CodeExampleProps {
|
||||
code: string;
|
||||
password?: boolean;
|
||||
secret?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to show single-line code examples, with a copy button
|
||||
*/
|
||||
export const CodeExample: FC<CodeExampleProps> = (props) => {
|
||||
const { code, password, className } = props;
|
||||
const theme = useTheme();
|
||||
|
||||
export const CodeExample: FC<CodeExampleProps> = ({
|
||||
code,
|
||||
secret,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
background: "rgb(0 0 0 / 30%)",
|
||||
color: theme.palette.primary.contrastText,
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
fontSize: 14,
|
||||
borderRadius: 8,
|
||||
padding: 8,
|
||||
lineHeight: "150%",
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
<code
|
||||
css={{
|
||||
padding: "0 8px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
wordBreak: "break-all",
|
||||
"-webkit-text-security": password ? "disc" : undefined,
|
||||
}}
|
||||
>
|
||||
{code}
|
||||
</code>
|
||||
<div css={styles.container} className={className}>
|
||||
<code css={[styles.code, secret && styles.secret]}>{code}</code>
|
||||
<CopyButton text={code} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: (theme) => ({
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
color: theme.experimental.l1.text,
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
fontSize: 14,
|
||||
borderRadius: 8,
|
||||
padding: 8,
|
||||
lineHeight: "150%",
|
||||
border: `1px solid ${theme.experimental.l1.outline}`,
|
||||
}),
|
||||
|
||||
code: {
|
||||
padding: "0 8px",
|
||||
flexGrow: 1,
|
||||
wordBreak: "break-all",
|
||||
},
|
||||
|
||||
secret: {
|
||||
"-webkit-text-security": "disc", // also supported by firefox
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -378,7 +378,7 @@ const HealthIssue: FC<PropsWithChildren> = ({ children }) => {
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<ErrorIcon
|
||||
css={{ width: 16, height: 16 }}
|
||||
htmlColor={theme.colors.red[10]}
|
||||
htmlColor={theme.experimental.roles.error.outline}
|
||||
/>
|
||||
{children}
|
||||
</Stack>
|
||||
@@ -426,13 +426,13 @@ const classNames = {
|
||||
} satisfies Record<string, ClassName>;
|
||||
|
||||
const styles = {
|
||||
statusBadge: css`
|
||||
statusBadge: (theme) => css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 12px;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
color: ${theme.experimental.l1.text};
|
||||
|
||||
& svg {
|
||||
width: 16px;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import { LicenseBannerView } from "./LicenseBannerView";
|
||||
|
||||
const meta: Meta<typeof LicenseBannerView> = {
|
||||
title: "components/LicenseBannerView",
|
||||
parameters: { chromatic },
|
||||
component: LicenseBannerView,
|
||||
};
|
||||
|
||||
|
||||
@@ -47,11 +47,11 @@ export const LicenseBannerView: FC<LicenseBannerViewProps> = ({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background-color: ${type === "error"
|
||||
? theme.colors.red[10]
|
||||
: theme.colors.orange[10]};
|
||||
background-color: ${theme.experimental.roles[type].background};
|
||||
`;
|
||||
|
||||
const textColor = theme.experimental.roles[type].text;
|
||||
|
||||
if (messages.length === 1) {
|
||||
return (
|
||||
<div css={containerStyles}>
|
||||
@@ -59,7 +59,11 @@ export const LicenseBannerView: FC<LicenseBannerViewProps> = ({
|
||||
<div css={styles.leftContent}>
|
||||
<span>{messages[0]}</span>
|
||||
|
||||
<Link color="white" fontWeight="medium" href="mailto:sales@coder.com">
|
||||
<Link
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
href="mailto:sales@coder.com"
|
||||
>
|
||||
{Language.upgrade}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -74,7 +78,11 @@ export const LicenseBannerView: FC<LicenseBannerViewProps> = ({
|
||||
<div>
|
||||
{Language.exceeded}
|
||||
|
||||
<Link color="white" fontWeight="medium" href="mailto:sales@coder.com">
|
||||
<Link
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
href="mailto:sales@coder.com"
|
||||
>
|
||||
{Language.upgrade}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromaticWithTablet } from "testHelpers/chromatic";
|
||||
import { MockUser, MockUser2 } from "testHelpers/entities";
|
||||
import { NavbarView } from "./NavbarView";
|
||||
|
||||
const meta: Meta<typeof NavbarView> = {
|
||||
title: "components/NavbarView",
|
||||
parameters: { chromatic: chromaticWithTablet, layout: "fullscreen" },
|
||||
component: NavbarView,
|
||||
args: {
|
||||
user: MockUser,
|
||||
@@ -23,12 +25,3 @@ export const ForMember: Story = {
|
||||
canViewAllUsers: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const SmallViewport: Story = {
|
||||
parameters: {
|
||||
viewport: {
|
||||
defaultViewport: "tablet",
|
||||
},
|
||||
chromatic: { viewports: [420] },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -456,7 +456,7 @@ const styles = {
|
||||
},
|
||||
link: (theme) => css`
|
||||
align-items: center;
|
||||
color: ${theme.colors.gray[6]};
|
||||
color: ${theme.palette.text.secondary};
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
@@ -470,7 +470,7 @@ const styles = {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.palette.action.hover};
|
||||
background-color: ${theme.experimental.l2.hover.background};
|
||||
}
|
||||
|
||||
${theme.breakpoints.up("md")} {
|
||||
|
||||
@@ -45,7 +45,7 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
||||
/>
|
||||
</Badge>
|
||||
<DropdownArrow
|
||||
color={theme.colors.gray[6]}
|
||||
color={theme.experimental.l2.fill}
|
||||
close={popover.isOpen}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css, type Interpolation, type Theme } from "@emotion/react";
|
||||
import { type FC } from "react";
|
||||
import { InlineMarkdown } from "components/Markdown/Markdown";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { css, useTheme } from "@emotion/react";
|
||||
import { readableForegroundColor } from "utils/colors";
|
||||
|
||||
export interface ServiceBannerViewProps {
|
||||
@@ -9,58 +10,39 @@ export interface ServiceBannerViewProps {
|
||||
isPreview: boolean;
|
||||
}
|
||||
|
||||
export const ServiceBannerView: React.FC<ServiceBannerViewProps> = ({
|
||||
export const ServiceBannerView: FC<ServiceBannerViewProps> = ({
|
||||
message,
|
||||
backgroundColor,
|
||||
isPreview,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
// We don't want anything funky like an image or a heading in the service
|
||||
// banner.
|
||||
const markdownElementsAllowed = [
|
||||
"text",
|
||||
"a",
|
||||
"pre",
|
||||
"ul",
|
||||
"strong",
|
||||
"emphasis",
|
||||
"italic",
|
||||
"link",
|
||||
"em",
|
||||
];
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
padding: 12px;
|
||||
background-color: ${backgroundColor ?? theme.palette.warning.main};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.error {
|
||||
background-color: ${theme.colors.red[12]};
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div css={[styles.banner, { backgroundColor }]}>
|
||||
{isPreview && <Pill text="Preview" type="info" />}
|
||||
<div
|
||||
css={css`
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
font-weight: 400;
|
||||
color: ${readableForegroundColor(backgroundColor)};
|
||||
|
||||
& a {
|
||||
color: inherit;
|
||||
}
|
||||
`}
|
||||
css={[
|
||||
styles.wrapper,
|
||||
{ color: readableForegroundColor(backgroundColor) },
|
||||
]}
|
||||
>
|
||||
<ReactMarkdown
|
||||
allowedElements={markdownElementsAllowed}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{message}
|
||||
</ReactMarkdown>
|
||||
<InlineMarkdown>{message}</InlineMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
banner: css`
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`,
|
||||
wrapper: css`
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
font-weight: 400;
|
||||
|
||||
& a {
|
||||
color: inherit;
|
||||
}
|
||||
`,
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -58,7 +58,7 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
|
||||
disabled={disabled}
|
||||
type="submit"
|
||||
css={[
|
||||
type === "delete" && styles.warningButton,
|
||||
type === "delete" && styles.dangerButton,
|
||||
type === "success" && styles.successButton,
|
||||
]}
|
||||
>
|
||||
@@ -70,26 +70,26 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
|
||||
};
|
||||
|
||||
const styles = {
|
||||
warningButton: (theme) => ({
|
||||
dangerButton: (theme) => ({
|
||||
"&.MuiButton-contained": {
|
||||
backgroundColor: theme.palette.warning.main,
|
||||
borderColor: theme.palette.warning.main,
|
||||
backgroundColor: theme.experimental.roles.danger.fill,
|
||||
borderColor: theme.experimental.roles.danger.outline,
|
||||
|
||||
"&:not(.MuiLoadingButton-loading)": {
|
||||
color: theme.palette.text.primary,
|
||||
color: theme.experimental.roles.danger.text,
|
||||
},
|
||||
|
||||
"&:hover:not(:disabled)": {
|
||||
backgroundColor: theme.palette.warning.main,
|
||||
borderColor: theme.palette.warning.main,
|
||||
backgroundColor: theme.experimental.roles.danger.disabled.fill,
|
||||
borderColor: theme.experimental.roles.danger.disabled.outline,
|
||||
},
|
||||
|
||||
"&.Mui-disabled": {
|
||||
backgroundColor: theme.palette.warning.dark,
|
||||
borderColor: theme.palette.warning.dark,
|
||||
backgroundColor: theme.experimental.roles.danger.disabled.background,
|
||||
borderColor: theme.experimental.roles.danger.disabled.outline,
|
||||
|
||||
"&:not(.MuiLoadingButton-loading)": {
|
||||
color: theme.palette.warning.main,
|
||||
color: theme.experimental.roles.danger.disabled.text,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import { type FC, type ImgHTMLAttributes } from "react";
|
||||
|
||||
interface ExternalIconProps extends ImgHTMLAttributes<HTMLImageElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const ExternalIcon: FC<ExternalIconProps> = ({
|
||||
size = 36,
|
||||
...attrs
|
||||
}) => {
|
||||
return (
|
||||
<div css={[styles.container, { height: size, width: size }]}>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden
|
||||
css={[
|
||||
styles.icon,
|
||||
{ height: size, width: size, padding: Math.ceil(size / 6) },
|
||||
]}
|
||||
{...attrs}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
borderRadius: 9999,
|
||||
overflow: "clip",
|
||||
},
|
||||
icon: {
|
||||
backgroundColor: "#000",
|
||||
objectFit: "contain",
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
@@ -206,6 +206,7 @@ const styles = {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
...(theme.typography.body2 as CSSObject),
|
||||
color: theme.experimental.roles.active.fill,
|
||||
}),
|
||||
|
||||
linkIcon: {
|
||||
|
||||
@@ -16,6 +16,7 @@ export const Loader: FC<LoaderProps> = ({ size = 26, ...attrs }) => {
|
||||
justifyContent: "center",
|
||||
}}
|
||||
data-testid="loader"
|
||||
data-chromatic="ignore"
|
||||
{...attrs}
|
||||
>
|
||||
<CircularProgress size={size} />
|
||||
|
||||
@@ -121,12 +121,19 @@ export const Markdown: FC<MarkdownProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface MarkdownInlineProps {
|
||||
interface InlineMarkdownProps {
|
||||
/**
|
||||
* The Markdown text to parse and render
|
||||
*/
|
||||
children: string;
|
||||
|
||||
/**
|
||||
* Additional element types to allow.
|
||||
* Allows italic, bold, links, and inline code snippets by default.
|
||||
* eg. `["ol", "ul", "li"]` to support lists.
|
||||
*/
|
||||
allowedElements?: readonly string[];
|
||||
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
@@ -138,13 +145,21 @@ interface MarkdownInlineProps {
|
||||
/**
|
||||
* Supports a strict subset of Markdown that behaves well as inline/confined content.
|
||||
*/
|
||||
export const InlineMarkdown: FC<MarkdownInlineProps> = (props) => {
|
||||
const { children, className, components = {} } = props;
|
||||
export const InlineMarkdown: FC<InlineMarkdownProps> = (props) => {
|
||||
const { children, allowedElements = [], className, components = {} } = props;
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className={className}
|
||||
allowedElements={["p", "em", "strong", "a", "pre", "code"]}
|
||||
allowedElements={[
|
||||
"p",
|
||||
"em",
|
||||
"strong",
|
||||
"a",
|
||||
"pre",
|
||||
"code",
|
||||
...allowedElements,
|
||||
]}
|
||||
unwrapDisallowed
|
||||
components={{
|
||||
p: ({ children }) => <>{children}</>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import { type FC, type PropsWithChildren } from "react";
|
||||
import Button from "@mui/material/Button";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
@@ -11,13 +11,13 @@ type NumberedPageButtonProps = {
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export function NumberedPageButton({
|
||||
export const NumberedPageButton: FC<NumberedPageButtonProps> = ({
|
||||
pageNumber,
|
||||
totalPages,
|
||||
onClick,
|
||||
highlighted = false,
|
||||
disabled = false,
|
||||
}: NumberedPageButtonProps) {
|
||||
}) => {
|
||||
return (
|
||||
<BasePageButton
|
||||
name="Page button"
|
||||
@@ -29,16 +29,16 @@ export function NumberedPageButton({
|
||||
{pageNumber}
|
||||
</BasePageButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type PlaceholderPageButtonProps = PropsWithChildren<{
|
||||
pagesOmitted: number;
|
||||
}>;
|
||||
|
||||
export function PlaceholderPageButton({
|
||||
export const PlaceholderPageButton: FC<PlaceholderPageButtonProps> = ({
|
||||
pagesOmitted,
|
||||
children = <>…</>,
|
||||
}: PlaceholderPageButtonProps) {
|
||||
}) => {
|
||||
return (
|
||||
<BasePageButton
|
||||
disabled
|
||||
@@ -48,7 +48,7 @@ export function PlaceholderPageButton({
|
||||
{children}
|
||||
</BasePageButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type BasePageButtonProps = PropsWithChildren<{
|
||||
name: string;
|
||||
@@ -59,22 +59,29 @@ type BasePageButtonProps = PropsWithChildren<{
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
|
||||
function BasePageButton({
|
||||
const BasePageButton: FC<BasePageButtonProps> = ({
|
||||
children,
|
||||
onClick,
|
||||
name,
|
||||
"aria-label": ariaLabel,
|
||||
highlighted = false,
|
||||
disabled = false,
|
||||
}: BasePageButtonProps) {
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Button
|
||||
css={
|
||||
highlighted && {
|
||||
borderColor: `${theme.palette.info.main}`,
|
||||
backgroundColor: `${theme.palette.info.dark}`,
|
||||
borderColor: theme.experimental.roles.active.outline,
|
||||
backgroundColor: theme.experimental.roles.active.background,
|
||||
|
||||
// Override the hover state with active colors, but not hover
|
||||
// colors because clicking won't do anything.
|
||||
"&:hover": {
|
||||
borderColor: theme.experimental.roles.active.outline,
|
||||
backgroundColor: theme.experimental.roles.active.background,
|
||||
},
|
||||
}
|
||||
}
|
||||
aria-label={ariaLabel}
|
||||
@@ -85,7 +92,7 @@ function BasePageButton({
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function getNumberedButtonLabel(
|
||||
page: number,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
import { type FC } from "react";
|
||||
import { PlaceholderPageButton, NumberedPageButton } from "./PageButtons";
|
||||
import { buildPagedList } from "./utils";
|
||||
import { PaginationNavButton } from "./PaginationNavButton";
|
||||
@@ -17,14 +17,14 @@ export type PaginationWidgetBaseProps = {
|
||||
hasNextPage?: boolean;
|
||||
};
|
||||
|
||||
export const PaginationWidgetBase = ({
|
||||
export const PaginationWidgetBase: FC<PaginationWidgetBaseProps> = ({
|
||||
currentPage,
|
||||
pageSize,
|
||||
totalRecords,
|
||||
onPageChange,
|
||||
hasPreviousPage,
|
||||
hasNextPage,
|
||||
}: PaginationWidgetBaseProps) => {
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const totalPages = Math.ceil(totalRecords / pageSize);
|
||||
@@ -99,11 +99,11 @@ type PaginationRowProps = {
|
||||
onChange: (newPage: number) => void;
|
||||
};
|
||||
|
||||
function PaginationRow({
|
||||
const PaginationRow: FC<PaginationRowProps> = ({
|
||||
currentPage,
|
||||
totalPages,
|
||||
onChange,
|
||||
}: PaginationRowProps) {
|
||||
}) => {
|
||||
const pageInfo = buildPagedList(totalPages, currentPage);
|
||||
const pagesOmitted = totalPages - pageInfo.length - 1;
|
||||
|
||||
@@ -131,4 +131,4 @@ function PaginationRow({
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type FC, type ReactNode, useMemo, forwardRef } from "react";
|
||||
import { css, type Interpolation, type Theme } from "@emotion/react";
|
||||
import { css, useTheme, type Interpolation, type Theme } from "@emotion/react";
|
||||
import type { ThemeRole } from "theme/experimental";
|
||||
|
||||
export type PillType = ThemeRole | keyof typeof themeOverrides;
|
||||
@@ -14,8 +14,8 @@ export interface PillProps {
|
||||
|
||||
const themeOverrides = {
|
||||
neutral: (theme) => ({
|
||||
backgroundColor: theme.colors.gray[13],
|
||||
borderColor: theme.colors.gray[6],
|
||||
backgroundColor: theme.experimental.l1.background,
|
||||
borderColor: theme.experimental.l1.outline,
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -30,6 +30,7 @@ const themeStyles = (type: ThemeRole) => (theme: Theme) => {
|
||||
export const Pill: FC<PillProps> = forwardRef<HTMLDivElement, PillProps>(
|
||||
(props, ref) => {
|
||||
const { icon, text = null, type = "neutral", ...attrs } = props;
|
||||
const theme = useTheme();
|
||||
|
||||
const typeStyles = useMemo(() => {
|
||||
if (type in themeOverrides) {
|
||||
@@ -50,7 +51,7 @@ export const Pill: FC<PillProps> = forwardRef<HTMLDivElement, PillProps>(
|
||||
borderStyle: "solid",
|
||||
borderRadius: 99999,
|
||||
fontSize: 12,
|
||||
color: "#FFF",
|
||||
color: theme.experimental.l1.text,
|
||||
height: 24,
|
||||
paddingLeft: icon ? 6 : 12,
|
||||
paddingRight: 12,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import HelpOutline from "@mui/icons-material/HelpOutline";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { type FC } from "react";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type Theme, useTheme } from "@emotion/react";
|
||||
import { type FC } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import type { WorkspaceAgent, DERPRegion } from "api/typesGenerated";
|
||||
import {
|
||||
HelpTooltipText,
|
||||
@@ -31,7 +30,11 @@ const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
|
||||
interface AgentLatencyProps {
|
||||
agent: WorkspaceAgent;
|
||||
}
|
||||
|
||||
export const AgentLatency: FC<AgentLatencyProps> = ({ agent }) => {
|
||||
const theme = useTheme();
|
||||
const latency = getDisplayLatency(theme, agent);
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ export interface AgentMetadataViewProps {
|
||||
|
||||
export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
|
||||
if (metadata.length === 0) {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div css={styles.root}>
|
||||
@@ -131,7 +131,7 @@ export const AgentMetadata: FC<AgentMetadataProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined;
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||
|
||||
const connect = (): (() => void) => {
|
||||
const source = watchAgentMetadata(agent.id);
|
||||
@@ -259,7 +259,9 @@ const styles = {
|
||||
},
|
||||
|
||||
metadataValueSuccess: (theme) => ({
|
||||
color: theme.palette.success.light,
|
||||
// color: theme.palette.success.light,
|
||||
color: theme.experimental.roles.success.fill,
|
||||
// color: theme.experimental.roles.success.text,
|
||||
}),
|
||||
|
||||
metadataValueError: (theme) => ({
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import {
|
||||
MockPrimaryWorkspaceProxy,
|
||||
MockWorkspaceProxies,
|
||||
@@ -21,7 +23,6 @@ import {
|
||||
} from "testHelpers/entities";
|
||||
import { AgentRow, LineWithID } from "./AgentRow";
|
||||
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const defaultAgentMetadata = [
|
||||
{
|
||||
@@ -102,6 +103,7 @@ const storybookLogs: LineWithID[] = [
|
||||
|
||||
const meta: Meta<typeof AgentRow> = {
|
||||
title: "components/AgentRow",
|
||||
parameters: { chromatic },
|
||||
component: AgentRow,
|
||||
args: {
|
||||
storybookLogs,
|
||||
|
||||
@@ -426,28 +426,14 @@ export const AgentRow: FC<AgentRowProps> = ({
|
||||
</AutoSizer>
|
||||
</Collapse>
|
||||
|
||||
<div css={styles.logsPanelButtons}>
|
||||
{showLogs ? (
|
||||
<button
|
||||
css={[styles.logsPanelButton, styles.toggleLogsButton]}
|
||||
onClick={() => {
|
||||
setShowLogs((v) => !v);
|
||||
}}
|
||||
>
|
||||
<DropdownArrow close />
|
||||
Hide logs
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
css={[styles.logsPanelButton, styles.toggleLogsButton]}
|
||||
onClick={() => {
|
||||
setShowLogs((v) => !v);
|
||||
}}
|
||||
>
|
||||
<DropdownArrow />
|
||||
Show logs
|
||||
</button>
|
||||
)}
|
||||
<div css={{ display: "flex" }}>
|
||||
<button
|
||||
css={styles.logsPanelButton}
|
||||
onClick={() => setShowLogs((v) => !v)}
|
||||
>
|
||||
<DropdownArrow close={showLogs} />
|
||||
{showLogs ? "Hide" : "Show"} logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -673,10 +659,6 @@ const styles = {
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
}),
|
||||
|
||||
logsPanelButtons: {
|
||||
display: "flex",
|
||||
},
|
||||
|
||||
logsPanelButton: (theme) => ({
|
||||
textAlign: "left",
|
||||
background: "transparent",
|
||||
@@ -689,10 +671,11 @@ const styles = {
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
whiteSpace: "nowrap",
|
||||
width: "100%",
|
||||
|
||||
"&:hover": {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.colors.gray[14],
|
||||
backgroundColor: theme.experimental.l2.hover.background,
|
||||
},
|
||||
|
||||
"& svg": {
|
||||
@@ -700,10 +683,6 @@ const styles = {
|
||||
},
|
||||
}),
|
||||
|
||||
toggleLogsButton: {
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
buttonSkeleton: {
|
||||
borderRadius: 4,
|
||||
},
|
||||
|
||||
@@ -1,43 +1,31 @@
|
||||
import { PortForwardPopoverView } from "./PortForwardButton";
|
||||
import { PortForwardButton } from "./PortForwardButton";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import {
|
||||
MockListeningPortsResponse,
|
||||
MockWorkspaceAgent,
|
||||
} from "testHelpers/entities";
|
||||
|
||||
const meta: Meta<typeof PortForwardPopoverView> = {
|
||||
title: "components/PortForwardPopoverView",
|
||||
component: PortForwardPopoverView,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div
|
||||
css={(theme) => ({
|
||||
width: 304,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 8,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
})}
|
||||
>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
const meta: Meta<typeof PortForwardButton> = {
|
||||
title: "components/PortForwardButton",
|
||||
component: PortForwardButton,
|
||||
args: {
|
||||
agent: MockWorkspaceAgent,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PortForwardPopoverView>;
|
||||
type Story = StoryObj<typeof PortForwardButton>;
|
||||
|
||||
export const WithPorts: Story = {
|
||||
export const Example: Story = {
|
||||
args: {
|
||||
ports: MockListeningPortsResponse.ports,
|
||||
storybook: {
|
||||
portsQueryData: MockListeningPortsResponse,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty: Story = {
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
ports: [],
|
||||
storybook: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getAgentListeningPorts } from "api/api";
|
||||
import type {
|
||||
WorkspaceAgent,
|
||||
WorkspaceAgentListeningPort,
|
||||
WorkspaceAgentListeningPortsResponse,
|
||||
} from "api/typesGenerated";
|
||||
import { portForwardURL } from "utils/portForward";
|
||||
import { type ClassName, useClassName } from "hooks/useClassName";
|
||||
@@ -32,34 +33,43 @@ export interface PortForwardButtonProps {
|
||||
username: string;
|
||||
workspaceName: string;
|
||||
agent: WorkspaceAgent;
|
||||
|
||||
/**
|
||||
* Only for use in Storybook
|
||||
*/
|
||||
storybook?: {
|
||||
portsQueryData?: WorkspaceAgentListeningPortsResponse;
|
||||
};
|
||||
}
|
||||
|
||||
export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
|
||||
const { agent } = props;
|
||||
const { agent, storybook } = props;
|
||||
|
||||
const paper = useClassName(classNames.paper, []);
|
||||
|
||||
const portsQuery = useQuery({
|
||||
queryKey: ["portForward", agent.id],
|
||||
queryFn: () => getAgentListeningPorts(agent.id),
|
||||
enabled: agent.status === "connected",
|
||||
enabled: !storybook && agent.status === "connected",
|
||||
refetchInterval: 5_000,
|
||||
});
|
||||
|
||||
const data = storybook ? storybook.portsQueryData : portsQuery.data;
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<AgentButton disabled={!portsQuery.data}>
|
||||
<AgentButton disabled={!data}>
|
||||
{DisplayAppNameMap["port_forwarding_helper"]}
|
||||
{portsQuery.data ? (
|
||||
<div css={styles.portCount}>{portsQuery.data.ports.length}</div>
|
||||
{data ? (
|
||||
<div css={styles.portCount}>{data.ports.length}</div>
|
||||
) : (
|
||||
<CircularProgress size={10} css={{ marginLeft: 8 }} />
|
||||
)}
|
||||
</AgentButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent horizontal="right" classes={{ paper }}>
|
||||
<PortForwardPopoverView {...props} ports={portsQuery.data?.ports} />
|
||||
<PortForwardPopoverView {...props} ports={data?.ports} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
@@ -204,7 +214,7 @@ const styles = {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.colors.gray[11],
|
||||
backgroundColor: theme.experimental.l2.background,
|
||||
marginLeft: 8,
|
||||
}),
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { PortForwardPopoverView } from "./PortForwardButton";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import {
|
||||
MockListeningPortsResponse,
|
||||
MockWorkspaceAgent,
|
||||
} from "testHelpers/entities";
|
||||
|
||||
const meta: Meta<typeof PortForwardPopoverView> = {
|
||||
title: "components/PortForwardPopoverView",
|
||||
component: PortForwardPopoverView,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div
|
||||
css={(theme) => ({
|
||||
width: 304,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 8,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
})}
|
||||
>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
agent: MockWorkspaceAgent,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PortForwardPopoverView>;
|
||||
|
||||
export const WithPorts: Story = {
|
||||
args: {
|
||||
ports: MockListeningPortsResponse.ports,
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
ports: [],
|
||||
},
|
||||
};
|
||||
@@ -1,15 +1,13 @@
|
||||
import { type FC } from "react";
|
||||
import type { WorkspaceResource } from "api/typesGenerated";
|
||||
import { Avatar, AvatarIcon } from "components/Avatar/Avatar";
|
||||
import { FC } from "react";
|
||||
import { WorkspaceResource } from "api/typesGenerated";
|
||||
|
||||
const FALLBACK_ICON = "/icon/widgets.svg";
|
||||
|
||||
// These resources (i.e. docker_image, kubernetes_deployment) map to Terraform
|
||||
// resource types. These are the most used ones and are based on user usage.
|
||||
// We may want to update from time-to-time.
|
||||
const BUILT_IN_ICON_PATHS: {
|
||||
[resourceType: WorkspaceResource["type"]]: string;
|
||||
} = {
|
||||
const BUILT_IN_ICON_PATHS: Record<string, string> = {
|
||||
docker_volume: "/icon/database.svg",
|
||||
docker_container: "/icon/memory.svg",
|
||||
docker_image: "/icon/container.svg",
|
||||
@@ -19,24 +17,16 @@ const BUILT_IN_ICON_PATHS: {
|
||||
google_compute_instance: "/icon/memory.svg",
|
||||
aws_instance: "/icon/memory.svg",
|
||||
kubernetes_deployment: "/icon/memory.svg",
|
||||
null_resource: FALLBACK_ICON,
|
||||
};
|
||||
|
||||
export const getIconPathResource = (resourceType: string): string => {
|
||||
if (resourceType in BUILT_IN_ICON_PATHS) {
|
||||
return BUILT_IN_ICON_PATHS[resourceType];
|
||||
}
|
||||
|
||||
return FALLBACK_ICON;
|
||||
return BUILT_IN_ICON_PATHS[resourceType] ?? FALLBACK_ICON;
|
||||
};
|
||||
|
||||
export type ResourceAvatarProps = { resource: WorkspaceResource };
|
||||
|
||||
export const ResourceAvatar: FC<ResourceAvatarProps> = ({ resource }) => {
|
||||
const hasIcon = resource.icon && resource.icon !== "";
|
||||
const avatarSrc = hasIcon
|
||||
? resource.icon
|
||||
: getIconPathResource(resource.type);
|
||||
const avatarSrc = resource.icon || getIconPathResource(resource.type);
|
||||
|
||||
return (
|
||||
<Avatar background>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { TemplateVersionParameter } from "api/typesGenerated";
|
||||
import { RichParameterInput } from "./RichParameterInput";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import type { TemplateVersionParameter } from "api/typesGenerated";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import { RichParameterInput } from "./RichParameterInput";
|
||||
|
||||
const meta: Meta<typeof RichParameterInput> = {
|
||||
title: "components/RichParameterInput",
|
||||
parameters: { chromatic },
|
||||
component: RichParameterInput,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,13 +5,20 @@ import { useCoderTheme } from "./coderTheme";
|
||||
|
||||
loader.config({ monaco });
|
||||
|
||||
export const SyntaxHighlighter: FC<{
|
||||
interface SyntaxHighlighterProps {
|
||||
value: string;
|
||||
language: string;
|
||||
editorProps?: ComponentProps<typeof Editor> &
|
||||
ComponentProps<typeof DiffEditor>;
|
||||
compareWith?: string;
|
||||
}> = ({ value, compareWith, language, editorProps }) => {
|
||||
}
|
||||
|
||||
export const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({
|
||||
value,
|
||||
compareWith,
|
||||
language,
|
||||
editorProps,
|
||||
}) => {
|
||||
const hasDiff = compareWith && value !== compareWith;
|
||||
const coderTheme = useCoderTheme();
|
||||
const commonProps = {
|
||||
|
||||
@@ -1,222 +1,6 @@
|
||||
import { useMonaco } from "@monaco-editor/react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { editor } from "monaco-editor";
|
||||
import { type Theme, useTheme } from "@emotion/react";
|
||||
|
||||
// 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
|
||||
export const coderTheme = (theme: Theme): editor.IStandaloneThemeData => ({
|
||||
base: "vs-dark",
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
background: "282a36",
|
||||
token: "",
|
||||
},
|
||||
{
|
||||
foreground: "6272a4",
|
||||
token: "comment",
|
||||
},
|
||||
{
|
||||
foreground: "f1fa8c",
|
||||
token: "string",
|
||||
},
|
||||
{
|
||||
foreground: "bd93f9",
|
||||
token: "constant.numeric",
|
||||
},
|
||||
{
|
||||
foreground: "bd93f9",
|
||||
token: "constant.language",
|
||||
},
|
||||
{
|
||||
foreground: "bd93f9",
|
||||
token: "constant.character",
|
||||
},
|
||||
{
|
||||
foreground: "bd93f9",
|
||||
token: "constant.other",
|
||||
},
|
||||
{
|
||||
foreground: "ffb86c",
|
||||
token: "variable.other.readwrite.instance",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "constant.character.escaped",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "constant.character.escape",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "string source",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "string source.ruby",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "keyword",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "storage",
|
||||
},
|
||||
{
|
||||
foreground: "8be9fd",
|
||||
fontStyle: "italic",
|
||||
token: "storage.type",
|
||||
},
|
||||
{
|
||||
foreground: "50fa7b",
|
||||
fontStyle: "underline",
|
||||
token: "entity.name.class",
|
||||
},
|
||||
{
|
||||
foreground: "50fa7b",
|
||||
fontStyle: "italic underline",
|
||||
token: "entity.other.inherited-class",
|
||||
},
|
||||
{
|
||||
foreground: "50fa7b",
|
||||
token: "entity.name.function",
|
||||
},
|
||||
{
|
||||
foreground: "ffb86c",
|
||||
fontStyle: "italic",
|
||||
token: "variable.parameter",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "entity.name.tag",
|
||||
},
|
||||
{
|
||||
foreground: "50fa7b",
|
||||
token: "entity.other.attribute-name",
|
||||
},
|
||||
{
|
||||
foreground: "8be9fd",
|
||||
token: "support.function",
|
||||
},
|
||||
{
|
||||
foreground: "6be5fd",
|
||||
token: "support.constant",
|
||||
},
|
||||
{
|
||||
foreground: "66d9ef",
|
||||
fontStyle: " italic",
|
||||
token: "support.type",
|
||||
},
|
||||
{
|
||||
foreground: "66d9ef",
|
||||
fontStyle: " italic",
|
||||
token: "support.class",
|
||||
},
|
||||
{
|
||||
foreground: "f8f8f0",
|
||||
background: "ff79c6",
|
||||
token: "invalid",
|
||||
},
|
||||
{
|
||||
foreground: "f8f8f0",
|
||||
background: "bd93f9",
|
||||
token: "invalid.deprecated",
|
||||
},
|
||||
{
|
||||
foreground: "cfcfc2",
|
||||
token: "meta.structure.dictionary.json string.quoted.double.json",
|
||||
},
|
||||
{
|
||||
foreground: "6272a4",
|
||||
token: "meta.diff",
|
||||
},
|
||||
{
|
||||
foreground: "6272a4",
|
||||
token: "meta.diff.header",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "markup.deleted",
|
||||
},
|
||||
{
|
||||
foreground: "50fa7b",
|
||||
token: "markup.inserted",
|
||||
},
|
||||
{
|
||||
foreground: "e6db74",
|
||||
token: "markup.changed",
|
||||
},
|
||||
{
|
||||
foreground: "bd93f9",
|
||||
token: "constant.numeric.line-number.find-in-files - match",
|
||||
},
|
||||
{
|
||||
foreground: "e6db74",
|
||||
token: "entity.name.filename",
|
||||
},
|
||||
{
|
||||
foreground: "f83333",
|
||||
token: "message.error",
|
||||
},
|
||||
{
|
||||
foreground: "eeeeee",
|
||||
token:
|
||||
"punctuation.definition.string.begin.json - meta.structure.dictionary.value.json",
|
||||
},
|
||||
{
|
||||
foreground: "eeeeee",
|
||||
token:
|
||||
"punctuation.definition.string.end.json - meta.structure.dictionary.value.json",
|
||||
},
|
||||
{
|
||||
foreground: "8be9fd",
|
||||
token: "meta.structure.dictionary.json string.quoted.double.json",
|
||||
},
|
||||
{
|
||||
foreground: "f1fa8c",
|
||||
token: "meta.structure.dictionary.value.json string.quoted.double.json",
|
||||
},
|
||||
{
|
||||
foreground: "50fa7b",
|
||||
token:
|
||||
"meta meta meta meta meta meta meta.structure.dictionary.value string",
|
||||
},
|
||||
{
|
||||
foreground: "ffb86c",
|
||||
token: "meta meta meta meta meta meta.structure.dictionary.value string",
|
||||
},
|
||||
{
|
||||
foreground: "ff79c6",
|
||||
token: "meta meta meta meta meta.structure.dictionary.value string",
|
||||
},
|
||||
{
|
||||
foreground: "bd93f9",
|
||||
token: "meta meta meta meta.structure.dictionary.value string",
|
||||
},
|
||||
{
|
||||
foreground: "50fa7b",
|
||||
token: "meta meta meta.structure.dictionary.value string",
|
||||
},
|
||||
{
|
||||
foreground: "ffb86c",
|
||||
token: "meta meta.structure.dictionary.value string",
|
||||
},
|
||||
],
|
||||
colors: {
|
||||
"editor.foreground": theme.palette.text.primary,
|
||||
"editor.background": theme.palette.background.default,
|
||||
"editor.selectionBackground": theme.palette.action.hover,
|
||||
"editor.lineHighlightBackground": theme.palette.background.paper,
|
||||
|
||||
"editorCursor.foreground": "#f8f8f0",
|
||||
"editorWhitespace.foreground": "#3B3A32",
|
||||
"editorIndentGuide.activeBackground": "#9D550FB0",
|
||||
"editor.selectionHighlightBorder": "#222218",
|
||||
},
|
||||
});
|
||||
|
||||
export const useCoderTheme = (): { isLoading: boolean; name: string } => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -226,7 +10,7 @@ export const useCoderTheme = (): { isLoading: boolean; name: string } => {
|
||||
|
||||
useEffect(() => {
|
||||
if (monaco) {
|
||||
monaco.editor.defineTheme(name, coderTheme(theme));
|
||||
monaco.editor.defineTheme(name, theme.monaco);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [monaco, theme]);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -37,11 +37,17 @@ const languageByExtension: Record<AllowedExtension, string> = {
|
||||
protobuf: "protobuf",
|
||||
};
|
||||
|
||||
export const TemplateFiles: FC<{
|
||||
interface TemplateFilesProps {
|
||||
currentFiles: TemplateVersionFiles;
|
||||
previousFiles?: TemplateVersionFiles;
|
||||
tab: UseTabResult;
|
||||
}> = ({ currentFiles, previousFiles, tab }) => {
|
||||
}
|
||||
|
||||
export const TemplateFiles: FC<TemplateFilesProps> = ({
|
||||
currentFiles,
|
||||
previousFiles,
|
||||
tab,
|
||||
}) => {
|
||||
const filenames = Object.keys(currentFiles);
|
||||
const selectedFilename = filenames[Number(tab.value)];
|
||||
const currentFile = currentFiles[selectedFilename];
|
||||
|
||||
@@ -5,6 +5,8 @@ import { type FC, type ReactNode, useMemo } from "react";
|
||||
import AnsiToHTML from "ansi-to-html";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
|
||||
const convert = new AnsiToHTML();
|
||||
|
||||
export interface Line {
|
||||
time: string;
|
||||
output: string;
|
||||
@@ -16,9 +18,10 @@ export interface LogsProps {
|
||||
lines: Line[];
|
||||
hideTimestamps?: boolean;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
|
||||
export const Logs: FC<LogsProps> = ({
|
||||
hideTimestamps,
|
||||
lines,
|
||||
className = "",
|
||||
@@ -50,16 +53,23 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
|
||||
|
||||
export const logLineHeight = 20;
|
||||
|
||||
const convert = new AnsiToHTML();
|
||||
|
||||
export const LogLine: FC<{
|
||||
interface LogLineProps {
|
||||
line: Line;
|
||||
hideTimestamp?: boolean;
|
||||
number?: number;
|
||||
style?: React.CSSProperties;
|
||||
sourceIcon?: ReactNode;
|
||||
maxNumber?: number;
|
||||
}> = ({ line, hideTimestamp, number, maxNumber, sourceIcon, style }) => {
|
||||
}
|
||||
|
||||
export const LogLine: FC<LogLineProps> = ({
|
||||
line,
|
||||
hideTimestamp,
|
||||
number,
|
||||
maxNumber,
|
||||
sourceIcon,
|
||||
style,
|
||||
}) => {
|
||||
const output = useMemo(() => {
|
||||
return convert.toHtml(line.output.split(/\r/g).pop() as string);
|
||||
}, [line.output]);
|
||||
@@ -120,15 +130,15 @@ const styles = {
|
||||
padding: "0 32px",
|
||||
|
||||
"&.error": {
|
||||
backgroundColor: theme.palette.error.dark,
|
||||
backgroundColor: theme.experimental.roles.error.background,
|
||||
},
|
||||
|
||||
"&.debug": {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
backgroundColor: theme.experimental.roles.info.background,
|
||||
},
|
||||
|
||||
"&.warn": {
|
||||
backgroundColor: theme.palette.warning.dark,
|
||||
backgroundColor: theme.experimental.roles.warning.background,
|
||||
},
|
||||
}),
|
||||
space: {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
import { WorkspaceBuildLogs } from "./WorkspaceBuildLogs";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import { MockWorkspaceBuildLogs } from "testHelpers/entities";
|
||||
import { WorkspaceBuildLogs } from "./WorkspaceBuildLogs";
|
||||
|
||||
const meta: Meta<typeof WorkspaceBuildLogs> = {
|
||||
title: "components/WorkspaceBuildLogs",
|
||||
parameters: { chromatic },
|
||||
component: WorkspaceBuildLogs,
|
||||
};
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const DormantStatusBadge: FC<DormantStatusBadgeProps> = ({
|
||||
className,
|
||||
}) => {
|
||||
if (!workspace.dormant_at) {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
const formatDate = (dateStr: string): string => {
|
||||
|
||||
@@ -7,15 +7,19 @@ import {
|
||||
import {
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import themes, { DEFAULT_THEME } from "theme";
|
||||
import themes, { DEFAULT_THEME, type Theme } from "theme";
|
||||
import { AuthContext } from "./AuthProvider/AuthProvider";
|
||||
|
||||
export const ThemeProviders: FC<PropsWithChildren> = ({ children }) => {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const ThemeProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
// We need to use the `AuthContext` directly, rather than the `useAuth` hook,
|
||||
// because Storybook and many tests depend on this component, but do not provide
|
||||
// an `AuthProvider`, and `useAuth` will throw in that case.
|
||||
@@ -56,12 +60,23 @@ export const ThemeProviders: FC<PropsWithChildren> = ({ children }) => {
|
||||
|
||||
return (
|
||||
<StyledEngineProvider injectFirst>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<EmotionThemeProvider theme={theme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
{children}
|
||||
</EmotionThemeProvider>
|
||||
</MuiThemeProvider>
|
||||
<ThemeOverride theme={theme}>{children}</ThemeOverride>
|
||||
</StyledEngineProvider>
|
||||
);
|
||||
};
|
||||
|
||||
interface ThemeOverrideProps {
|
||||
theme: Theme;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const ThemeOverride: FC<ThemeOverrideProps> = ({ theme, children }) => {
|
||||
return (
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<EmotionThemeProvider theme={theme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
{children}
|
||||
</EmotionThemeProvider>
|
||||
</MuiThemeProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,16 @@
|
||||
import { FC } from "react";
|
||||
import { type FC } from "react";
|
||||
import { AuditLog } from "api/typesGenerated";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import Link from "@mui/material/Link";
|
||||
import { BuildAuditDescription } from "./BuildAuditDescription";
|
||||
|
||||
export const AuditLogDescription: FC<{ auditLog: AuditLog }> = ({
|
||||
interface AuditLogDescriptionProps {
|
||||
auditLog: AuditLog;
|
||||
}
|
||||
|
||||
export const AuditLogDescription: FC<AuditLogDescriptionProps> = ({
|
||||
auditLog,
|
||||
}): JSX.Element => {
|
||||
}) => {
|
||||
let target = auditLog.resource_target.trim();
|
||||
const user = auditLog.user?.username.trim();
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import TableCell from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import {
|
||||
MockAuditLog,
|
||||
MockAuditLog2,
|
||||
@@ -12,7 +14,6 @@ import {
|
||||
MockAuditLogGitSSH,
|
||||
} from "testHelpers/entities";
|
||||
import { AuditLogRow } from "./AuditLogRow";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta: Meta<typeof AuditLogRow> = {
|
||||
title: "pages/AuditPage/AuditLogRow",
|
||||
@@ -48,6 +49,7 @@ export const NoDiff: Story = {
|
||||
};
|
||||
|
||||
export const WithDiff: Story = {
|
||||
parameters: { chromatic },
|
||||
args: {
|
||||
auditLog: MockAuditLog2,
|
||||
defaultIsDiffOpen: true,
|
||||
@@ -55,6 +57,7 @@ export const WithDiff: Story = {
|
||||
};
|
||||
|
||||
export const WithLongDiffRow: Story = {
|
||||
parameters: { chromatic },
|
||||
args: {
|
||||
auditLog: {
|
||||
...MockAuditLog2,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
import { type ComponentProps } from "react";
|
||||
import { chromaticWithTablet } from "testHelpers/chromatic";
|
||||
import { MockAuditLog, MockAuditLog2, MockUser } from "testHelpers/entities";
|
||||
import { AuditPageView } from "./AuditPageView";
|
||||
import { ComponentProps } from "react";
|
||||
import {
|
||||
mockInitialRenderResult,
|
||||
mockSuccessResult,
|
||||
} from "components/PaginationWidget/PaginationContainer.mocks";
|
||||
import { type UsePaginatedQueryResult } from "hooks/usePaginatedQuery";
|
||||
import { AuditPageView } from "./AuditPageView";
|
||||
|
||||
import {
|
||||
MockMenu,
|
||||
@@ -43,6 +44,7 @@ export default meta;
|
||||
type Story = StoryObj<typeof AuditPageView>;
|
||||
|
||||
export const AuditPage: Story = {
|
||||
parameters: { chromatic: chromaticWithTablet },
|
||||
args: {
|
||||
auditsQuery: mockSuccessResult,
|
||||
},
|
||||
@@ -84,12 +86,3 @@ export const NotVisible: Story = {
|
||||
auditsQuery: mockInitialRenderResult,
|
||||
},
|
||||
};
|
||||
|
||||
export const AuditPageSmallViewport: Story = {
|
||||
args: {
|
||||
auditsQuery: mockSuccessResult,
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { viewports: [600] },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,8 +13,12 @@ export const AuditPaywall: FC = () => {
|
||||
description="Audit Logs allows Auditors to monitor user operations in their deployment. To use this feature, you need an Enterprise license."
|
||||
cta={
|
||||
<Stack direction="row" alignItems="center">
|
||||
<Link href={docs("/admin/upgrade")} target="_blank" rel="noreferrer">
|
||||
<Button size="small" startIcon={<ArrowRightAltOutlined />}>
|
||||
<Link target="_blank" rel="noreferrer">
|
||||
<Button
|
||||
href={docs("/admin/upgrade")}
|
||||
size="small"
|
||||
startIcon={<ArrowRightAltOutlined />}
|
||||
>
|
||||
See how to upgrade
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -32,17 +32,13 @@ export const CliAuthPageView: FC<CliAuthPageViewProps> = ({ sessionToken }) => {
|
||||
}}
|
||||
>
|
||||
Copy the session token below and{" "}
|
||||
<strong
|
||||
css={{
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<strong css={{ whiteSpace: "nowrap" }}>
|
||||
paste it in your terminal
|
||||
</strong>
|
||||
.
|
||||
</p>
|
||||
|
||||
<CodeExample code={sessionToken} password />
|
||||
<CodeExample code={sessionToken} secret />
|
||||
|
||||
<div
|
||||
css={{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import {
|
||||
mockApiError,
|
||||
MockTemplate,
|
||||
@@ -11,6 +12,7 @@ import { CreateWorkspacePageView } from "./CreateWorkspacePageView";
|
||||
|
||||
const meta: Meta<typeof CreateWorkspacePageView> = {
|
||||
title: "pages/CreateWorkspacePage",
|
||||
parameters: { chromatic },
|
||||
component: CreateWorkspacePageView,
|
||||
args: {
|
||||
defaultName: "",
|
||||
|
||||
@@ -9,7 +9,7 @@ const meta: Meta<typeof ExternalAuth> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ExternalAuth>;
|
||||
|
||||
export const GithubNotAuthenticated: Story = {
|
||||
export const Github: Story = {
|
||||
args: {
|
||||
displayIcon: "/icon/github.svg",
|
||||
displayName: "GitHub",
|
||||
@@ -17,6 +17,24 @@ export const GithubNotAuthenticated: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const GithubTimeout: Story = {
|
||||
args: {
|
||||
displayIcon: "/icon/github.svg",
|
||||
displayName: "GitHub",
|
||||
authenticated: false,
|
||||
externalAuthPollingState: "abandoned",
|
||||
},
|
||||
};
|
||||
|
||||
export const GithubFailed: Story = {
|
||||
args: {
|
||||
displayIcon: "/icon/github.svg",
|
||||
displayName: "GitHub",
|
||||
authenticated: false,
|
||||
error: "Github doesn't like you",
|
||||
},
|
||||
};
|
||||
|
||||
export const GithubAuthenticated: Story = {
|
||||
args: {
|
||||
displayIcon: "/icon/github.svg",
|
||||
@@ -25,7 +43,7 @@ export const GithubAuthenticated: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const GitlabNotAuthenticated: Story = {
|
||||
export const Gitlab: Story = {
|
||||
args: {
|
||||
displayIcon: "/icon/gitlab.svg",
|
||||
displayName: "GitLab",
|
||||
@@ -41,7 +59,7 @@ export const GitlabAuthenticated: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const AzureDevOpsNotAuthenticated: Story = {
|
||||
export const AzureDevOps: Story = {
|
||||
args: {
|
||||
displayIcon: "/icon/azure-devops.svg",
|
||||
displayName: "Azure DevOps",
|
||||
@@ -57,7 +75,7 @@ export const AzureDevOpsAuthenticated: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const BitbucketNotAuthenticated: Story = {
|
||||
export const Bitbucket: Story = {
|
||||
args: {
|
||||
displayIcon: "/icon/bitbucket.svg",
|
||||
displayName: "Bitbucket",
|
||||
|
||||
@@ -16,70 +16,82 @@ export interface ExternalAuthProps {
|
||||
startPollingExternalAuth: () => void;
|
||||
error?: string;
|
||||
message?: string;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
|
||||
const {
|
||||
displayName,
|
||||
displayIcon,
|
||||
authenticated,
|
||||
authenticateURL,
|
||||
externalAuthPollingState,
|
||||
startPollingExternalAuth,
|
||||
error,
|
||||
message,
|
||||
} = props;
|
||||
|
||||
export const ExternalAuth: FC<ExternalAuthProps> = ({
|
||||
displayName,
|
||||
displayIcon,
|
||||
authenticated,
|
||||
authenticateURL,
|
||||
externalAuthPollingState,
|
||||
startPollingExternalAuth,
|
||||
error,
|
||||
message,
|
||||
fullWidth = true,
|
||||
}) => {
|
||||
const messageContent =
|
||||
message ??
|
||||
(authenticated
|
||||
? `Authenticated with ${displayName}`
|
||||
: `Login with ${displayName}`);
|
||||
return (
|
||||
<Tooltip
|
||||
title={authenticated && `${displayName} has already been connected.`}
|
||||
>
|
||||
<Stack alignItems="center" spacing={1}>
|
||||
<LoadingButton
|
||||
loadingPosition="start"
|
||||
loading={externalAuthPollingState === "polling"}
|
||||
href={authenticateURL}
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={
|
||||
displayIcon && (
|
||||
<img
|
||||
src={displayIcon}
|
||||
alt={`${displayName} Icon`}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
)
|
||||
}
|
||||
disabled={authenticated}
|
||||
css={{ height: 52 }}
|
||||
color={error ? "error" : undefined}
|
||||
fullWidth
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
// If the user is already authenticated, we don't want to redirect them
|
||||
if (authenticated || authenticateURL === "") {
|
||||
return;
|
||||
}
|
||||
window.open(authenticateURL, "_blank", "width=900,height=600");
|
||||
startPollingExternalAuth();
|
||||
}}
|
||||
>
|
||||
{messageContent}
|
||||
</LoadingButton>
|
||||
|
||||
{externalAuthPollingState === "abandoned" && (
|
||||
<Button variant="text" onClick={startPollingExternalAuth}>
|
||||
<ReplayIcon /> Check again
|
||||
</Button>
|
||||
)}
|
||||
{error && <FormHelperText error>{error}</FormHelperText>}
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
title={authenticated && `${displayName} has already been connected.`}
|
||||
>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
css={!fullWidth && { display: "inline-block" }}
|
||||
>
|
||||
<LoadingButton
|
||||
loadingPosition="start"
|
||||
loading={externalAuthPollingState === "polling"}
|
||||
href={authenticateURL}
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={
|
||||
displayIcon && (
|
||||
<img
|
||||
src={displayIcon}
|
||||
alt={`${displayName} Icon`}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
)
|
||||
}
|
||||
disabled={authenticated}
|
||||
css={{ height: 42 }}
|
||||
fullWidth={fullWidth}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
// If the user is already authenticated, we don't want to redirect them
|
||||
if (authenticated || authenticateURL === "") {
|
||||
return;
|
||||
}
|
||||
window.open(authenticateURL, "_blank", "width=900,height=600");
|
||||
startPollingExternalAuth();
|
||||
}}
|
||||
>
|
||||
{messageContent}
|
||||
</LoadingButton>
|
||||
|
||||
{externalAuthPollingState === "abandoned" && (
|
||||
<Button variant="text" onClick={startPollingExternalAuth}>
|
||||
<ReplayIcon /> Check again
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
{error && (
|
||||
<FormHelperText
|
||||
css={(theme) => ({ color: theme.experimental.roles.error.text })}
|
||||
>
|
||||
{error}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-1
@@ -19,6 +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";
|
||||
|
||||
export type AppearanceSettingsPageViewProps = {
|
||||
appearance: UpdateAppearanceConfig;
|
||||
@@ -29,11 +30,12 @@ export type AppearanceSettingsPageViewProps = {
|
||||
) => void;
|
||||
};
|
||||
|
||||
const fallbackBgColor = colors.neutral[500];
|
||||
|
||||
export const AppearanceSettingsPageView: FC<
|
||||
AppearanceSettingsPageViewProps
|
||||
> = ({ appearance, isEntitled, onSaveAppearance }) => {
|
||||
const theme = useTheme();
|
||||
const fallbackBgColor = theme.colors.blue[7];
|
||||
|
||||
const applicationNameForm = useFormik<{
|
||||
application_name: string;
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
import LicensesSettingsPageView from "./LicensesSettingsPageView";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import { MockLicenseResponse } from "testHelpers/entities";
|
||||
import LicensesSettingsPageView from "./LicensesSettingsPageView";
|
||||
|
||||
export default {
|
||||
title: "pages/DeploySettingsPage/LicensesSettingsPageView",
|
||||
parameters: { chromatic },
|
||||
component: LicensesSettingsPageView,
|
||||
};
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
rel="noreferrer"
|
||||
startIcon={<ArrowRightAltOutlined />}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Learn about Enterprise
|
||||
</Button>
|
||||
@@ -68,7 +69,7 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Read the docs
|
||||
Read the documentation
|
||||
</Link>
|
||||
</Stack>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import CodeOutlined from "@mui/icons-material/CodeOutlined";
|
||||
import TagOutlined from "@mui/icons-material/TagOutlined";
|
||||
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { type FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Link, useOutletContext, useParams } from "react-router-dom";
|
||||
import type {
|
||||
HealthMessage,
|
||||
HealthSeverity,
|
||||
HealthcheckReport,
|
||||
} from "api/typesGenerated";
|
||||
import { getLatencyColor } from "utils/latency";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { pageTitle } from "utils/page";
|
||||
import {
|
||||
Header,
|
||||
HeaderTitle,
|
||||
@@ -8,22 +23,8 @@ import {
|
||||
Logs,
|
||||
HealthyDot,
|
||||
} from "./Content";
|
||||
import {
|
||||
HealthMessage,
|
||||
HealthSeverity,
|
||||
HealthcheckReport,
|
||||
} from "api/typesGenerated";
|
||||
import CodeOutlined from "@mui/icons-material/CodeOutlined";
|
||||
import TagOutlined from "@mui/icons-material/TagOutlined";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
|
||||
import { getLatencyColor } from "utils/latency";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
|
||||
export const DERPRegionPage = () => {
|
||||
export const DERPRegionPage: FC = () => {
|
||||
const theme = useTheme();
|
||||
const healthStatus = useOutletContext<HealthcheckReport>();
|
||||
const params = useParams() as { regionId: string };
|
||||
|
||||
@@ -122,6 +122,7 @@ export function HealthLayout() {
|
||||
<div css={{ display: "flex", flexDirection: "column" }}>
|
||||
<span css={{ fontWeight: 500 }}>Last check</span>
|
||||
<span
|
||||
data-chromatic="ignore"
|
||||
css={{
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "150%",
|
||||
@@ -134,6 +135,7 @@ export function HealthLayout() {
|
||||
<div css={{ display: "flex", flexDirection: "column" }}>
|
||||
<span css={{ fontWeight: 500 }}>Version</span>
|
||||
<span
|
||||
data-chromatic="ignore"
|
||||
css={{
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "150%",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Meta } from "@storybook/react";
|
||||
import { useQueryClient } from "react-query";
|
||||
import {
|
||||
reactRouterParameters,
|
||||
reactRouterOutlet,
|
||||
RouteDefinition,
|
||||
} from "storybook-addon-react-router-v6";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import {
|
||||
MockBuildInfo,
|
||||
MockEntitlements,
|
||||
@@ -11,7 +13,6 @@ import {
|
||||
MockHealth,
|
||||
MockHealthSettings,
|
||||
} from "testHelpers/entities";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { HEALTH_QUERY_KEY, HEALTH_QUERY_SETTINGS_KEY } from "api/queries/debug";
|
||||
import { DashboardProvider } from "components/Dashboard/DashboardProvider";
|
||||
import { HealthLayout } from "./HealthLayout";
|
||||
@@ -26,6 +27,7 @@ export const generateMeta = ({ element, path, params }: MetaOptions): Meta => {
|
||||
return {
|
||||
render: HealthLayout,
|
||||
parameters: {
|
||||
chromatic,
|
||||
layout: "fullscreen",
|
||||
reactRouter: reactRouterParameters({
|
||||
location: { pathParams: params },
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { SetupPageView } from "./SetupPageView";
|
||||
import { mockApiError } from "testHelpers/entities";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import { mockApiError } from "testHelpers/entities";
|
||||
import { SetupPageView } from "./SetupPageView";
|
||||
|
||||
const meta: Meta<typeof SetupPageView> = {
|
||||
title: "pages/SetupPage",
|
||||
parameters: { chromatic },
|
||||
component: SetupPageView,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { TemplateFiles } from "components/TemplateFiles/TemplateFiles";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { getTemplatePageTitle } from "../utils";
|
||||
import { useFileTab, useTemplateFiles } from "components/TemplateFiles/hooks";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import { getTemplatePageTitle } from "../utils";
|
||||
|
||||
const TemplateFilesPage: FC = () => {
|
||||
const { template, activeVersion } = useTemplateLayoutContext();
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { TemplateInsightsPageView } from "./TemplateInsightsPage";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import { MockEntitlementsWithUserLimit } from "testHelpers/entities";
|
||||
import { TemplateInsightsPageView } from "./TemplateInsightsPage";
|
||||
|
||||
const meta: Meta<typeof TemplateInsightsPageView> = {
|
||||
title: "pages/TemplatePage/TemplateInsightsPageView",
|
||||
parameters: { chromatic },
|
||||
component: TemplateInsightsPageView,
|
||||
};
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ import type {
|
||||
} from "api/typesGenerated";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import {
|
||||
PropsWithChildren,
|
||||
type FC,
|
||||
type HTMLAttributes,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
HTMLAttributes,
|
||||
useId,
|
||||
} from "react";
|
||||
import chroma from "chroma-js";
|
||||
|
||||
@@ -39,7 +39,6 @@ export const VersionRow: FC<VersionRowProps> = ({
|
||||
<TimelineEntry
|
||||
data-testid={`version-${version.id}`}
|
||||
{...clickableProps}
|
||||
css={[styles.row]}
|
||||
className={clickableProps.className}
|
||||
>
|
||||
<TableCell css={styles.versionCell}>
|
||||
@@ -127,16 +126,6 @@ export const VersionRow: FC<VersionRowProps> = ({
|
||||
};
|
||||
|
||||
const styles = {
|
||||
row: (theme) => ({
|
||||
"&:hover $promoteButton": {
|
||||
color: theme.palette.text.primary,
|
||||
borderColor: theme.colors.gray[11],
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
promoteButton: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
transition: "none",
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ export const TemplatePermissionsPage: FC<
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={docs("/admin/rbac")} target="_blank" rel="noreferrer">
|
||||
Read the docs
|
||||
Read the documentation
|
||||
</Link>
|
||||
</Stack>
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
|
||||
}
|
||||
|
||||
&.Mui-selected {
|
||||
color: ${theme.palette.text.primary};
|
||||
background: ${theme.colors.gray[14]};
|
||||
color: ${theme.experimental.roles.active.text};
|
||||
background: ${theme.experimental.roles.active.background};
|
||||
}
|
||||
|
||||
&.Mui-focused {
|
||||
@@ -133,16 +133,13 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
{isFolder ? (
|
||||
{isFolder &&
|
||||
Object.keys(content)
|
||||
.sort(sortFileTree(content))
|
||||
.map((filename) => {
|
||||
const child = content[filename];
|
||||
return buildTreeItems(filename, child, currentPath);
|
||||
})
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
})}
|
||||
</TreeItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Editor, { loader } from "@monaco-editor/react";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { FC, useMemo } from "react";
|
||||
import { type FC, useEffect, useMemo } from "react";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
|
||||
loader.config({ monaco });
|
||||
|
||||
export const MonacoEditor: FC<{
|
||||
interface MonacoEditorProps {
|
||||
value?: string;
|
||||
path?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}> = ({ onChange, value, path }) => {
|
||||
}
|
||||
|
||||
export const MonacoEditor: FC<MonacoEditorProps> = ({
|
||||
onChange,
|
||||
value,
|
||||
path,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const language = useMemo(() => {
|
||||
@@ -31,6 +37,20 @@ export const MonacoEditor: FC<{
|
||||
}
|
||||
}, [path]);
|
||||
|
||||
useEffect(() => {
|
||||
document.fonts.ready
|
||||
.then(() => {
|
||||
// Ensures that all text is measured properly.
|
||||
// If this isn't done, there can be weird selection issues.
|
||||
monaco.editor.remeasureFonts();
|
||||
})
|
||||
.catch(() => {
|
||||
// Not a biggie!
|
||||
});
|
||||
|
||||
monaco.editor.defineTheme("min", theme.monaco);
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
value={value}
|
||||
@@ -52,62 +72,16 @@ export const MonacoEditor: FC<{
|
||||
onChange(newValue);
|
||||
}
|
||||
}}
|
||||
onMount={(editor, monaco) => {
|
||||
onMount={(editor) => {
|
||||
// This jank allows for Ctrl + Enter to work outside the editor.
|
||||
// We use this keybind to trigger a build.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Private type in Monaco!
|
||||
(editor as any)._standaloneKeybindingService.addDynamicKeybinding(
|
||||
`-editor.action.insertLineAfter`,
|
||||
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
|
||||
() => {
|
||||
//
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
|
||||
document.fonts.ready
|
||||
.then(() => {
|
||||
// Ensures that all text is measured properly.
|
||||
// If this isn't done, there can be weird selection issues.
|
||||
monaco.editor.remeasureFonts();
|
||||
})
|
||||
.catch(() => {
|
||||
// Not a biggie!
|
||||
});
|
||||
|
||||
monaco.editor.defineTheme("min", {
|
||||
base: "vs-dark",
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
token: "comment",
|
||||
foreground: "6B737C",
|
||||
},
|
||||
{
|
||||
token: "type",
|
||||
foreground: "B392F0",
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
foreground: "9DB1C5",
|
||||
},
|
||||
{
|
||||
token: "variable",
|
||||
foreground: "BBBBBB",
|
||||
},
|
||||
{
|
||||
token: "identifier",
|
||||
foreground: "B392F0",
|
||||
},
|
||||
{
|
||||
token: "delimiter.curly",
|
||||
foreground: "EBB325",
|
||||
},
|
||||
],
|
||||
colors: {
|
||||
"editor.foreground": theme.palette.text.primary,
|
||||
"editor.background": theme.palette.background.paper,
|
||||
},
|
||||
});
|
||||
editor.updateOptions({
|
||||
theme: "min",
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromatic } from "testHelpers/chromatic";
|
||||
import {
|
||||
MockFailedProvisionerJob,
|
||||
MockRunningProvisionerJob,
|
||||
@@ -14,19 +16,19 @@ import {
|
||||
MockWorkspaceVolumeResource,
|
||||
} from "testHelpers/entities";
|
||||
import { TemplateVersionEditor } from "./TemplateVersionEditor";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta: Meta<typeof TemplateVersionEditor> = {
|
||||
title: "pages/TemplateVersionEditorPage",
|
||||
title: "pages/TemplateVersionEditor",
|
||||
parameters: {
|
||||
chromatic,
|
||||
layout: "fullscreen",
|
||||
},
|
||||
component: TemplateVersionEditor,
|
||||
args: {
|
||||
template: MockTemplate,
|
||||
templateVersion: MockTemplateVersion,
|
||||
defaultFileTree: MockTemplateVersionFileTree,
|
||||
},
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { chromaticWithTablet } from "testHelpers/chromatic";
|
||||
import {
|
||||
mockApiError,
|
||||
MockTemplate,
|
||||
@@ -5,10 +7,10 @@ import {
|
||||
MockTemplateExample2,
|
||||
} from "testHelpers/entities";
|
||||
import { TemplatesPageView } from "./TemplatesPageView";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta: Meta<typeof TemplatesPageView> = {
|
||||
title: "pages/TemplatesPage",
|
||||
parameters: { chromatic: chromaticWithTablet },
|
||||
component: TemplatesPageView,
|
||||
};
|
||||
|
||||
@@ -65,15 +67,6 @@ export const WithTemplates: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const WithTemplatesSmallViewPort: Story = {
|
||||
args: {
|
||||
...WithTemplates.args,
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { viewports: [600] },
|
||||
},
|
||||
};
|
||||
|
||||
export const EmptyCanCreate: Story = {
|
||||
args: {
|
||||
canCreateTemplates: true,
|
||||
|
||||
@@ -44,7 +44,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { docs } from "utils/docs";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { DeprecatedBadge } from "components/Badges/Badges";
|
||||
|
||||
export const Language = {
|
||||
developerCount: (activeCount: number): string => {
|
||||
@@ -127,7 +127,7 @@ const TemplateRow: FC<TemplateRowProps> = ({ template }) => {
|
||||
|
||||
<TableCell css={styles.actionCell}>
|
||||
{template.deprecated ? (
|
||||
<Pill text="Deprecated" type="warning" />
|
||||
<DeprecatedBadge />
|
||||
) : (
|
||||
<Button
|
||||
size="small"
|
||||
@@ -278,15 +278,15 @@ const styles = {
|
||||
}),
|
||||
tableRow: (theme) => ({
|
||||
"&:hover .actionButton": {
|
||||
color: theme.palette.text.primary,
|
||||
borderColor: theme.colors.gray[11],
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.text.primary,
|
||||
},
|
||||
color: theme.experimental.l2.hover.text,
|
||||
borderColor: theme.experimental.l2.hover.outline,
|
||||
},
|
||||
}),
|
||||
actionButton: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
transition: "none",
|
||||
color: theme.palette.text.secondary,
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.text.primary,
|
||||
},
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -18,7 +18,6 @@ type Story = StoryObj<typeof AppearanceForm>;
|
||||
|
||||
export const Example: Story = {
|
||||
args: {
|
||||
enableAuto: true,
|
||||
initialValues: { theme_preference: "" },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { type Interpolation } from "@emotion/react";
|
||||
import { type FC, useMemo } from "react";
|
||||
import { type FC } from "react";
|
||||
import type { UpdateUserAppearanceSettingsRequest } from "api/typesGenerated";
|
||||
import themes, { DEFAULT_THEME, type Theme } from "theme";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { BetaBadge } from "components/Badges/Badges";
|
||||
import { ThemeOverride } from "contexts/ThemeProvider";
|
||||
|
||||
export interface AppearanceFormProps {
|
||||
isUpdating?: boolean;
|
||||
error?: unknown;
|
||||
initialValues: UpdateUserAppearanceSettingsRequest;
|
||||
onSubmit: (values: UpdateUserAppearanceSettingsRequest) => Promise<unknown>;
|
||||
|
||||
// temporary, so that storybook can test the right thing without showing
|
||||
// a semi-broken auto theme to users. will be removed when light mode is done.
|
||||
enableAuto?: boolean;
|
||||
}
|
||||
|
||||
export const AppearanceForm: FC<AppearanceFormProps> = ({
|
||||
@@ -22,7 +20,6 @@ export const AppearanceForm: FC<AppearanceFormProps> = ({
|
||||
error,
|
||||
onSubmit,
|
||||
initialValues,
|
||||
enableAuto,
|
||||
}) => {
|
||||
const currentTheme = initialValues.theme_preference || DEFAULT_THEME;
|
||||
|
||||
@@ -39,14 +36,13 @@ export const AppearanceForm: FC<AppearanceFormProps> = ({
|
||||
{Boolean(error) && <ErrorAlert error={error} />}
|
||||
|
||||
<Stack direction="row" wrap="wrap">
|
||||
{enableAuto && (
|
||||
<AutoThemePreviewButton
|
||||
displayName="Auto"
|
||||
active={currentTheme === "auto"}
|
||||
themes={[themes.dark, themes.light]}
|
||||
onSelect={() => onChangeTheme("auto")}
|
||||
/>
|
||||
)}
|
||||
<AutoThemePreviewButton
|
||||
displayName="Auto"
|
||||
active={currentTheme === "auto"}
|
||||
themes={[themes.dark, themes.light]}
|
||||
onSelect={() => onChangeTheme("auto")}
|
||||
/>
|
||||
|
||||
<ThemePreviewButton
|
||||
displayName="Dark"
|
||||
active={currentTheme === "dark"}
|
||||
@@ -59,21 +55,26 @@ export const AppearanceForm: FC<AppearanceFormProps> = ({
|
||||
theme={themes.darkBlue}
|
||||
onSelect={() => onChangeTheme("darkBlue")}
|
||||
/>
|
||||
<ThemePreviewButton
|
||||
displayName="Light"
|
||||
beta
|
||||
active={currentTheme === "light"}
|
||||
theme={themes.light}
|
||||
onSelect={() => onChangeTheme("light")}
|
||||
/>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
interface AutoThemePreviewButtonProps {
|
||||
active?: boolean;
|
||||
className?: string;
|
||||
displayName: string;
|
||||
interface AutoThemePreviewButtonProps extends Omit<ThemePreviewProps, "theme"> {
|
||||
themes: [Theme, Theme];
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
const AutoThemePreviewButton: FC<AutoThemePreviewButtonProps> = ({
|
||||
active,
|
||||
beta,
|
||||
className,
|
||||
displayName,
|
||||
themes,
|
||||
@@ -101,11 +102,13 @@ const AutoThemePreviewButton: FC<AutoThemePreviewButtonProps> = ({
|
||||
clipPath: "polygon(-5% -5%, 50% -5%, 50% 105%, -5% 105%)",
|
||||
}}
|
||||
active={active}
|
||||
beta={beta}
|
||||
displayName={displayName}
|
||||
theme={leftTheme}
|
||||
/>
|
||||
<ThemePreview
|
||||
active={active}
|
||||
beta={beta}
|
||||
displayName={displayName}
|
||||
theme={rightTheme}
|
||||
/>
|
||||
@@ -114,16 +117,13 @@ const AutoThemePreviewButton: FC<AutoThemePreviewButtonProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface ThemePreviewButtonProps {
|
||||
active?: boolean;
|
||||
className?: string;
|
||||
displayName: string;
|
||||
theme: Theme;
|
||||
interface ThemePreviewButtonProps extends ThemePreviewProps {
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
const ThemePreviewButton: FC<ThemePreviewButtonProps> = ({
|
||||
active,
|
||||
beta,
|
||||
className,
|
||||
displayName,
|
||||
theme,
|
||||
@@ -141,7 +141,12 @@ const ThemePreviewButton: FC<ThemePreviewButtonProps> = ({
|
||||
css={{ ...visuallyHidden }}
|
||||
/>
|
||||
<label htmlFor={displayName} className={className}>
|
||||
<ThemePreview active={active} displayName={displayName} theme={theme} />
|
||||
<ThemePreview
|
||||
active={active}
|
||||
beta={beta}
|
||||
displayName={displayName}
|
||||
theme={theme}
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
@@ -149,6 +154,7 @@ const ThemePreviewButton: FC<ThemePreviewButtonProps> = ({
|
||||
|
||||
interface ThemePreviewProps {
|
||||
active?: boolean;
|
||||
beta?: boolean;
|
||||
className?: string;
|
||||
displayName: string;
|
||||
theme: Theme;
|
||||
@@ -156,141 +162,146 @@ interface ThemePreviewProps {
|
||||
|
||||
const ThemePreview: FC<ThemePreviewProps> = ({
|
||||
active,
|
||||
beta,
|
||||
className,
|
||||
displayName,
|
||||
theme,
|
||||
}) => {
|
||||
const styles = useMemo(
|
||||
() =>
|
||||
({
|
||||
container: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
width: 220,
|
||||
color: theme.palette.text.primary,
|
||||
borderRadius: 6,
|
||||
overflow: "clip",
|
||||
userSelect: "none",
|
||||
},
|
||||
containerActive: {
|
||||
outline: `2px solid ${theme.experimental.roles.active.outline}`,
|
||||
},
|
||||
page: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
header: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "6px 10px",
|
||||
marginBottom: 8,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
headerLinks: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
},
|
||||
headerLink: {
|
||||
backgroundColor: theme.palette.text.secondary,
|
||||
height: 6,
|
||||
width: 20,
|
||||
borderRadius: 3,
|
||||
},
|
||||
activeHeaderLink: {
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
},
|
||||
proxy: {
|
||||
backgroundColor: theme.palette.success.light,
|
||||
height: 6,
|
||||
width: 12,
|
||||
borderRadius: 3,
|
||||
},
|
||||
user: {
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
height: 8,
|
||||
width: 8,
|
||||
borderRadius: 4,
|
||||
float: "right",
|
||||
},
|
||||
body: {
|
||||
width: 120,
|
||||
margin: "auto",
|
||||
},
|
||||
title: {
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
height: 8,
|
||||
width: 45,
|
||||
borderRadius: 4,
|
||||
marginBottom: 6,
|
||||
},
|
||||
table: {
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderBottom: "none",
|
||||
borderTopLeftRadius: 3,
|
||||
borderTopRightRadius: 3,
|
||||
overflow: "clip",
|
||||
},
|
||||
tableHeader: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
height: 10,
|
||||
margin: -1,
|
||||
},
|
||||
label: {
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
padding: "4px 12px",
|
||||
fontSize: 14,
|
||||
},
|
||||
workspace: {
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
height: 15,
|
||||
|
||||
"&::after": {
|
||||
content: '""',
|
||||
display: "block",
|
||||
marginTop: 4,
|
||||
marginLeft: 4,
|
||||
backgroundColor: theme.palette.text.disabled,
|
||||
height: 6,
|
||||
width: 30,
|
||||
borderRadius: 3,
|
||||
},
|
||||
},
|
||||
}) satisfies Record<string, Interpolation<never>>,
|
||||
[theme],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={[styles.container, active && styles.containerActive]}
|
||||
className={className}
|
||||
>
|
||||
<div css={styles.page}>
|
||||
<div css={styles.header}>
|
||||
<div css={styles.headerLinks}>
|
||||
<div css={[styles.headerLink, styles.activeHeaderLink]}></div>
|
||||
<div css={styles.headerLink}></div>
|
||||
<div css={styles.headerLink}></div>
|
||||
<ThemeOverride theme={theme}>
|
||||
<div
|
||||
css={[styles.container, active && styles.containerActive]}
|
||||
className={className}
|
||||
>
|
||||
<div css={styles.page}>
|
||||
<div css={styles.header}>
|
||||
<div css={styles.headerLinks}>
|
||||
<div css={[styles.headerLink, styles.activeHeaderLink]}></div>
|
||||
<div css={styles.headerLink}></div>
|
||||
<div css={styles.headerLink}></div>
|
||||
</div>
|
||||
<div css={styles.headerLinks}>
|
||||
<div css={styles.proxy}></div>
|
||||
<div css={styles.user}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div css={styles.headerLinks}>
|
||||
<div css={styles.proxy}></div>
|
||||
<div css={styles.user}></div>
|
||||
<div css={styles.body}>
|
||||
<div css={styles.title}></div>
|
||||
<div css={styles.table}>
|
||||
<div css={styles.tableHeader}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div css={styles.body}>
|
||||
<div css={styles.title}></div>
|
||||
<div css={styles.table}>
|
||||
<div css={styles.tableHeader}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
<div css={styles.workspace}></div>
|
||||
</div>
|
||||
<div css={styles.label}>
|
||||
<span>{displayName}</span>
|
||||
{beta && <BetaBadge />}
|
||||
</div>
|
||||
</div>
|
||||
<div css={styles.label}>{displayName}</div>
|
||||
</div>
|
||||
</ThemeOverride>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: (theme) => ({
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
width: 220,
|
||||
color: theme.palette.text.primary,
|
||||
borderRadius: 6,
|
||||
overflow: "clip",
|
||||
userSelect: "none",
|
||||
}),
|
||||
containerActive: (theme) => ({
|
||||
outline: `2px solid ${theme.experimental.roles.active.outline}`,
|
||||
}),
|
||||
page: (theme) => ({
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}),
|
||||
header: (theme) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "6px 10px",
|
||||
marginBottom: 8,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
}),
|
||||
headerLinks: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
},
|
||||
headerLink: (theme) => ({
|
||||
backgroundColor: theme.palette.text.secondary,
|
||||
height: 6,
|
||||
width: 20,
|
||||
borderRadius: 3,
|
||||
}),
|
||||
activeHeaderLink: (theme) => ({
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
}),
|
||||
proxy: (theme) => ({
|
||||
backgroundColor: theme.palette.success.light,
|
||||
height: 6,
|
||||
width: 12,
|
||||
borderRadius: 3,
|
||||
}),
|
||||
user: (theme) => ({
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
height: 8,
|
||||
width: 8,
|
||||
borderRadius: 4,
|
||||
float: "right",
|
||||
}),
|
||||
body: {
|
||||
width: 120,
|
||||
margin: "auto",
|
||||
},
|
||||
title: (theme) => ({
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
height: 8,
|
||||
width: 45,
|
||||
borderRadius: 4,
|
||||
marginBottom: 6,
|
||||
}),
|
||||
table: (theme) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderBottom: "none",
|
||||
borderTopLeftRadius: 3,
|
||||
borderTopRightRadius: 3,
|
||||
overflow: "clip",
|
||||
}),
|
||||
tableHeader: (theme) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
height: 10,
|
||||
margin: -1,
|
||||
}),
|
||||
label: (theme) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
padding: "4px 12px",
|
||||
fontSize: 14,
|
||||
}),
|
||||
workspace: (theme) => ({
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
height: 15,
|
||||
|
||||
"&::after": {
|
||||
content: '""',
|
||||
display: "block",
|
||||
marginTop: 4,
|
||||
marginLeft: 4,
|
||||
backgroundColor: theme.palette.text.disabled,
|
||||
height: 6,
|
||||
width: 30,
|
||||
borderRadius: 3,
|
||||
},
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AppearancePage } from "./AppearancePage";
|
||||
import { MockUser } from "testHelpers/entities";
|
||||
|
||||
describe("appearance page", () => {
|
||||
it("changes theme to dark", async () => {
|
||||
it("does nothing when selecting current theme", async () => {
|
||||
renderWithAuth(<AppearancePage />);
|
||||
|
||||
jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({
|
||||
@@ -18,10 +18,7 @@ describe("appearance page", () => {
|
||||
await userEvent.click(dark);
|
||||
|
||||
// Check if the API was called correctly
|
||||
expect(API.updateAppearanceSettings).toBeCalledTimes(1);
|
||||
expect(API.updateAppearanceSettings).toHaveBeenCalledWith("me", {
|
||||
theme_preference: "dark",
|
||||
});
|
||||
expect(API.updateAppearanceSettings).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it("changes theme to dark blue", async () => {
|
||||
@@ -41,4 +38,22 @@ describe("appearance page", () => {
|
||||
theme_preference: "darkBlue",
|
||||
});
|
||||
});
|
||||
|
||||
it("changes theme to light", async () => {
|
||||
renderWithAuth(<AppearancePage />);
|
||||
|
||||
jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({
|
||||
...MockUser,
|
||||
theme_preference: "light",
|
||||
});
|
||||
|
||||
const light = await screen.findByText("Light");
|
||||
await userEvent.click(light);
|
||||
|
||||
// Check if the API was called correctly
|
||||
expect(API.updateAppearanceSettings).toBeCalledTimes(1);
|
||||
expect(API.updateAppearanceSettings).toHaveBeenCalledWith("me", {
|
||||
theme_preference: "light",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,16 @@ type Story = StoryObj<typeof ExternalAuthPageView>;
|
||||
|
||||
export const NoProviders: Story = {};
|
||||
|
||||
export const NoIcon: Story = {
|
||||
args: {
|
||||
...meta.args,
|
||||
auths: {
|
||||
providers: [{ ...MockGithubExternalProvider, display_icon: "" }],
|
||||
links: [MockGithubAuthLink],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Authenticated: Story = {
|
||||
args: {
|
||||
...meta.args,
|
||||
@@ -36,7 +46,7 @@ export const Authenticated: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const UnAuthenticated: Story = {
|
||||
export const Unauthenticated: Story = {
|
||||
args: {
|
||||
...meta.args,
|
||||
auths: {
|
||||
|
||||
@@ -131,9 +131,8 @@ const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
title={app.display_name || app.id}
|
||||
// subtitle={template.description}
|
||||
avatar={
|
||||
app.display_icon !== "" && (
|
||||
app.display_icon && (
|
||||
<Avatar src={app.display_icon} variant="square" fitImage />
|
||||
)
|
||||
}
|
||||
@@ -150,6 +149,7 @@ const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
|
||||
message={authenticated ? "Authenticated" : "Click to Login"}
|
||||
externalAuthPollingState={externalAuthPollingState}
|
||||
startPollingExternalAuth={startPollingExternalAuth}
|
||||
fullWidth={false}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -11,7 +11,8 @@ const meta: Meta<typeof SSHKeysPageView> = {
|
||||
user_id: "test-user-id",
|
||||
created_at: "2022-07-28T07:45:50.795918897Z",
|
||||
updated_at: "2022-07-28T07:45:50.795919142Z",
|
||||
public_key: "SSH-Key",
|
||||
public_key:
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICnKzATuWwmmt5+CKTPuRGN0R1PBemA+6/SStpLiyX+L",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,18 +14,17 @@
|
||||
* users like that, though, know that it will be painful
|
||||
*/
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { type User, type Role } from "api/typesGenerated";
|
||||
|
||||
import { EditRolesButton } from "./EditRolesButton";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import Stack from "@mui/material/Stack";
|
||||
|
||||
import { type FC } from "react";
|
||||
import { type User, type Role } from "api/typesGenerated";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "components/Popover/Popover";
|
||||
import { EditRolesButton } from "./EditRolesButton";
|
||||
|
||||
type UserRoleCellProps = {
|
||||
canEditUsers: boolean;
|
||||
@@ -36,14 +35,14 @@ type UserRoleCellProps = {
|
||||
onUserRolesUpdate: (user: User, newRoleNames: string[]) => void;
|
||||
};
|
||||
|
||||
export function UserRoleCell({
|
||||
export const UserRoleCell: FC<UserRoleCellProps> = ({
|
||||
canEditUsers,
|
||||
allAvailableRoles,
|
||||
user,
|
||||
isLoading,
|
||||
oidcRoleSyncEnabled,
|
||||
onUserRolesUpdate,
|
||||
}: UserRoleCellProps) {
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [mainDisplayRole = fallbackRole, ...extraRoles] =
|
||||
@@ -75,11 +74,11 @@ export function UserRoleCell({
|
||||
text={mainDisplayRole.display_name}
|
||||
css={{
|
||||
backgroundColor: hasOwnerRole
|
||||
? theme.palette.info.dark
|
||||
: theme.palette.background.paper,
|
||||
? theme.experimental.roles.info.background
|
||||
: theme.experimental.l2.background,
|
||||
borderColor: hasOwnerRole
|
||||
? theme.palette.info.light
|
||||
: theme.palette.divider,
|
||||
? theme.experimental.roles.info.outline
|
||||
: theme.experimental.l2.outline,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -87,13 +86,13 @@ export function UserRoleCell({
|
||||
</Stack>
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type OverflowRolePillProps = {
|
||||
roles: readonly Role[];
|
||||
};
|
||||
|
||||
function OverflowRolePill({ roles }: OverflowRolePillProps) {
|
||||
const OverflowRolePill: FC<OverflowRolePillProps> = ({ roles }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
@@ -144,7 +143,7 @@ function OverflowRolePill({ roles }: OverflowRolePillProps) {
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const fallbackRole: Role = {
|
||||
name: "member",
|
||||
|
||||
@@ -4,6 +4,10 @@ import {
|
||||
MockAssignableSiteRoles,
|
||||
MockAuthMethodsPasswordOnly,
|
||||
MockGroup,
|
||||
MockUserAdminRole,
|
||||
MockTemplateAdminRole,
|
||||
MockMemberRole,
|
||||
MockAuditorRole,
|
||||
} from "testHelpers/entities";
|
||||
import { UsersTable } from "./UsersTable";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
@@ -43,7 +47,12 @@ export const Editable: Story = {
|
||||
...MockUser,
|
||||
username: "John Doe",
|
||||
email: "john.doe@coder.com",
|
||||
roles: [],
|
||||
roles: [
|
||||
MockUserAdminRole,
|
||||
MockTemplateAdminRole,
|
||||
MockMemberRole,
|
||||
MockAuditorRole,
|
||||
],
|
||||
status: "dormant",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ export const SidebarItem: FC<SidebarItemProps> = ({
|
||||
return (
|
||||
<button
|
||||
css={(theme) => ({
|
||||
background: active ? theme.colors.gray[13] : "none",
|
||||
background: active ? theme.experimental.l2.background : "none",
|
||||
border: "none",
|
||||
fontSize: 14,
|
||||
width: "100%",
|
||||
|
||||
@@ -173,18 +173,18 @@ const styles = {
|
||||
orphanContainer: (theme) => ({
|
||||
marginTop: 24,
|
||||
display: "flex",
|
||||
backgroundColor: theme.colors.orange[15],
|
||||
backgroundColor: theme.experimental.roles.danger.background,
|
||||
justifyContent: "space-between",
|
||||
border: `1px solid ${theme.colors.orange[11]}`,
|
||||
border: `1px solid ${theme.experimental.roles.danger.outline}`,
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
gap: 8,
|
||||
lineHeight: "18px",
|
||||
|
||||
"& .option": {
|
||||
color: theme.colors.orange[11],
|
||||
color: theme.experimental.roles.danger.fill,
|
||||
"&.Mui-checked": {
|
||||
color: theme.colors.orange[11],
|
||||
color: theme.experimental.roles.danger.fill,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
+4
-4
@@ -25,13 +25,13 @@ import {
|
||||
defaultSchedule,
|
||||
emptySchedule,
|
||||
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule";
|
||||
import { ChangeEvent, FC } from "react";
|
||||
import { type ChangeEvent, type FC } from "react";
|
||||
import * as Yup from "yup";
|
||||
import { getFormHelpers } from "utils/formUtils";
|
||||
import { timeZones } from "utils/timeZones";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { formatDuration, intervalToDuration } from "date-fns";
|
||||
import { DisabledBadge } from "components/Badges/Badges";
|
||||
|
||||
// REMARK: some plugins depend on utc, so it's listed first. Otherwise they're
|
||||
// sorted alphabetically.
|
||||
@@ -290,7 +290,7 @@ export const WorkspaceScheduleForm: FC<
|
||||
</div>
|
||||
{!enableAutoStart && (
|
||||
<Tooltip title="This option can be enabled in the template settings">
|
||||
<Pill text="Disabled" />
|
||||
<DisabledBadge />
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
@@ -378,7 +378,7 @@ export const WorkspaceScheduleForm: FC<
|
||||
</div>
|
||||
{!enableAutoStop && (
|
||||
<Tooltip title="This option can be enabled in the template settings">
|
||||
<Pill text="Disabled" />
|
||||
<DisabledBadge />
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export const chromatic = {
|
||||
modes: {
|
||||
dark: { theme: "dark" },
|
||||
light: { theme: "light" },
|
||||
},
|
||||
};
|
||||
|
||||
export const chromaticWithTablet = {
|
||||
modes: {
|
||||
"dark desktop": { theme: "dark" },
|
||||
"light tablet": { theme: "light", viewport: "ipad" },
|
||||
},
|
||||
};
|
||||
@@ -2933,7 +2933,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
created_at: "2023-05-01T19:15:56.606593Z",
|
||||
updated_at: "2023-12-05T14:13:36.647535Z",
|
||||
deleted: false,
|
||||
version: "v2.4.0-devel+5fad61102",
|
||||
version: "v2.5.0-devel+5fad61102",
|
||||
},
|
||||
{
|
||||
id: "9d786ce0-55b1-4ace-8acc-a4672ff8d41f",
|
||||
@@ -2956,7 +2956,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
created_at: "2023-05-01T20:34:11.114005Z",
|
||||
updated_at: "2023-12-05T14:13:45.941716Z",
|
||||
deleted: false,
|
||||
version: "v2.4.0-devel+5fad61102",
|
||||
version: "v2.5.0-devel+5fad61102",
|
||||
},
|
||||
{
|
||||
id: "2e209786-73b1-4838-ba78-e01c9334450a",
|
||||
@@ -2979,7 +2979,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
created_at: "2023-05-01T20:41:02.76448Z",
|
||||
updated_at: "2023-12-05T14:13:41.968568Z",
|
||||
deleted: false,
|
||||
version: "v2.4.0-devel+5fad61102",
|
||||
version: "v2.5.0-devel+5fad61102",
|
||||
},
|
||||
{
|
||||
id: "c272e80c-0cce-49d6-9782-1b5cf90398e8",
|
||||
@@ -3050,7 +3050,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
created_at: "2023-12-01T09:21:15.996267Z",
|
||||
updated_at: "2023-12-05T14:13:59.663174Z",
|
||||
deleted: false,
|
||||
version: "v2.4.0-devel+5fad61102",
|
||||
version: "v2.5.0-devel+5fad61102",
|
||||
},
|
||||
{
|
||||
id: "72649dc9-03c7-46a8-bc95-96775e93ddc1",
|
||||
@@ -3073,7 +3073,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
created_at: "2023-12-01T09:23:44.505529Z",
|
||||
updated_at: "2023-12-05T14:13:55.769058Z",
|
||||
deleted: false,
|
||||
version: "v2.4.0-devel+5fad61102",
|
||||
version: "v2.5.0-devel+5fad61102",
|
||||
},
|
||||
{
|
||||
id: "1f78398f-e5ae-4c38-aa89-30222181d443",
|
||||
@@ -3096,12 +3096,12 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
created_at: "2023-12-01T09:36:00.231252Z",
|
||||
updated_at: "2023-12-05T14:13:47.015031Z",
|
||||
deleted: false,
|
||||
version: "v2.4.0-devel+5fad61102",
|
||||
version: "v2.5.0-devel+5fad61102",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
coder_version: "v0.27.1-devel+c575292",
|
||||
coder_version: "v2.5.0-devel+5fad61102",
|
||||
};
|
||||
|
||||
export const MockListeningPortsResponse: TypesGen.WorkspaceAgentListeningPortsResponse =
|
||||
@@ -3184,7 +3184,7 @@ export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = {
|
||||
created_at: "2023-11-23T15:37:25.513213Z",
|
||||
updated_at: "2023-11-23T18:09:19.734747Z",
|
||||
deleted: false,
|
||||
version: "v2.4.0-devel+89bae7eff",
|
||||
version: "v2.5.0-devel+89bae7eff",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { QueryClient } from "react-query";
|
||||
import { AppProviders } from "App";
|
||||
import { ThemeProviders } from "contexts/ThemeProviders";
|
||||
import { ThemeProvider } from "contexts/ThemeProvider";
|
||||
import { DashboardLayout } from "components/Dashboard/DashboardLayout";
|
||||
import { RequireAuth } from "components/RequireAuth/RequireAuth";
|
||||
import { TemplateSettingsLayout } from "pages/TemplateSettingsPage/TemplateSettingsLayout";
|
||||
@@ -265,6 +265,6 @@ export const waitForLoaderToBeRemoved = async (): Promise<void> => {
|
||||
|
||||
export const renderComponent = (component: React.ReactElement) => {
|
||||
return tlRender(component, {
|
||||
wrapper: ({ children }) => <ThemeProviders>{children}</ThemeProviders>,
|
||||
wrapper: ({ children }) => <ThemeProvider>{children}</ThemeProvider>,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,41 +3,41 @@ import colors from "../tailwind";
|
||||
|
||||
export default {
|
||||
l1: {
|
||||
background: colors.gray[950],
|
||||
outline: colors.gray[700],
|
||||
fill: colors.gray[600],
|
||||
background: colors.zinc[950],
|
||||
outline: colors.zinc[700],
|
||||
fill: colors.zinc[600],
|
||||
text: colors.white,
|
||||
},
|
||||
|
||||
l2: {
|
||||
background: colors.gray[900],
|
||||
outline: colors.gray[700],
|
||||
fill: "#f00",
|
||||
text: colors.white,
|
||||
background: colors.zinc[900],
|
||||
outline: colors.zinc[700],
|
||||
fill: colors.zinc[500],
|
||||
text: colors.zinc[50],
|
||||
disabled: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
fill: "#f00",
|
||||
text: colors.gray[200],
|
||||
fill: colors.zinc[500],
|
||||
text: colors.zinc[200],
|
||||
},
|
||||
hover: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
background: colors.zinc[800],
|
||||
outline: colors.zinc[600],
|
||||
fill: "#f00",
|
||||
text: colors.white,
|
||||
},
|
||||
},
|
||||
|
||||
l3: {
|
||||
background: colors.gray[800],
|
||||
outline: colors.gray[700],
|
||||
fill: colors.gray[600],
|
||||
background: colors.zinc[800],
|
||||
outline: colors.zinc[700],
|
||||
fill: colors.zinc[600],
|
||||
text: colors.white,
|
||||
disabled: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
fill: "#f00",
|
||||
text: colors.gray[200],
|
||||
text: colors.zinc[200],
|
||||
},
|
||||
hover: {
|
||||
background: "#f00",
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
danger: {
|
||||
background: colors.orange[950],
|
||||
outline: colors.orange[500],
|
||||
fill: colors.orange[600],
|
||||
fill: colors.orange[700],
|
||||
text: colors.orange[50],
|
||||
disabled: {
|
||||
background: colors.orange[950],
|
||||
@@ -68,8 +68,8 @@ export default {
|
||||
},
|
||||
error: {
|
||||
background: colors.red[950],
|
||||
outline: colors.red[500],
|
||||
fill: colors.red[600],
|
||||
outline: colors.red[600],
|
||||
fill: colors.red[400],
|
||||
text: colors.red[50],
|
||||
},
|
||||
warning: {
|
||||
@@ -126,5 +126,11 @@ export default {
|
||||
text: colors.white,
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
background: colors.violet[950],
|
||||
outline: colors.violet[500],
|
||||
fill: colors.violet[400],
|
||||
text: colors.violet[50],
|
||||
},
|
||||
},
|
||||
} satisfies NewTheme;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import colors from "./colors";
|
||||
import experimental from "./experimental";
|
||||
import monaco from "./monaco";
|
||||
import muiTheme from "./mui";
|
||||
|
||||
export default {
|
||||
...muiTheme,
|
||||
colors,
|
||||
experimental,
|
||||
monaco,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import muiTheme from "./mui";
|
||||
import type * as monaco from "monaco-editor";
|
||||
|
||||
export default {
|
||||
base: "vs-dark",
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
token: "comment",
|
||||
foreground: "6B737C",
|
||||
},
|
||||
{
|
||||
token: "type",
|
||||
foreground: "B392F0",
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
foreground: "9DB1C5",
|
||||
},
|
||||
{
|
||||
token: "variable",
|
||||
foreground: "DDDDDD",
|
||||
},
|
||||
{
|
||||
token: "identifier",
|
||||
foreground: "B392F0",
|
||||
},
|
||||
{
|
||||
token: "delimiter.curly",
|
||||
foreground: "EBB325",
|
||||
},
|
||||
],
|
||||
colors: {
|
||||
"editor.foreground": muiTheme.palette.text.primary,
|
||||
"editor.background": muiTheme.palette.background.paper,
|
||||
},
|
||||
} satisfies monaco.editor.IStandaloneThemeData as monaco.editor.IStandaloneThemeData;
|
||||
@@ -1,4 +1,5 @@
|
||||
import colors from "./colors";
|
||||
// eslint-disable-next-line no-restricted-imports -- We need MUI here
|
||||
import { alertClasses } from "@mui/material/Alert";
|
||||
import { createTheme, type ThemeOptions } from "@mui/material/styles";
|
||||
import {
|
||||
BODY_FONT_FAMILY,
|
||||
@@ -8,17 +9,17 @@ import {
|
||||
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 tw from "../tailwind";
|
||||
import colors from "./colors";
|
||||
|
||||
let 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],
|
||||
@@ -489,6 +490,7 @@ muiTheme = createTheme(muiTheme, {
|
||||
lineHeight: "150%",
|
||||
borderRadius: 4,
|
||||
background: muiTheme.palette.divider,
|
||||
padding: "8px 16px",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,17 +12,17 @@ export default {
|
||||
l2: {
|
||||
background: colors.gray[900],
|
||||
outline: colors.gray[700],
|
||||
fill: "#f00",
|
||||
text: colors.white,
|
||||
fill: colors.gray[500],
|
||||
text: colors.gray[50],
|
||||
disabled: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
fill: "#f00",
|
||||
fill: colors.gray[500],
|
||||
text: colors.gray[200],
|
||||
},
|
||||
hover: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
outline: colors.gray[600],
|
||||
fill: "#f00",
|
||||
text: colors.white,
|
||||
},
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
roles: {
|
||||
danger: {
|
||||
background: colors.orange[950],
|
||||
outline: colors.orange[500],
|
||||
outline: colors.orange[600],
|
||||
fill: colors.orange[600],
|
||||
text: colors.orange[50],
|
||||
disabled: {
|
||||
@@ -126,5 +126,11 @@ export default {
|
||||
text: colors.white,
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
background: colors.violet[950],
|
||||
outline: colors.violet[500],
|
||||
fill: colors.violet[400],
|
||||
text: colors.violet[50],
|
||||
},
|
||||
},
|
||||
} satisfies NewTheme;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import colors from "./colors";
|
||||
import experimental from "./experimental";
|
||||
import monaco from "./monaco";
|
||||
import muiTheme from "./mui";
|
||||
|
||||
export default {
|
||||
...muiTheme,
|
||||
colors,
|
||||
experimental,
|
||||
monaco,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import muiTheme from "./mui";
|
||||
import type * as monaco from "monaco-editor";
|
||||
|
||||
export default {
|
||||
base: "vs-dark",
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
token: "comment",
|
||||
foreground: "6B737C",
|
||||
},
|
||||
{
|
||||
token: "type",
|
||||
foreground: "B392F0",
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
foreground: "9DB1C5",
|
||||
},
|
||||
{
|
||||
token: "variable",
|
||||
foreground: "DDDDDD",
|
||||
},
|
||||
{
|
||||
token: "identifier",
|
||||
foreground: "B392F0",
|
||||
},
|
||||
{
|
||||
token: "delimiter.curly",
|
||||
foreground: "EBB325",
|
||||
},
|
||||
],
|
||||
colors: {
|
||||
"editor.foreground": muiTheme.palette.text.primary,
|
||||
"editor.background": muiTheme.palette.background.paper,
|
||||
},
|
||||
} satisfies monaco.editor.IStandaloneThemeData as monaco.editor.IStandaloneThemeData;
|
||||
@@ -489,6 +489,7 @@ muiTheme = createTheme(muiTheme, {
|
||||
lineHeight: "150%",
|
||||
borderRadius: 4,
|
||||
background: muiTheme.palette.divider,
|
||||
padding: "8px 16px",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface NewTheme {
|
||||
info: Role; // just sharing :)
|
||||
success: InteractiveRole; // yay!! it's working!!
|
||||
active: InteractiveRole; // selected items, focused inputs, in progress
|
||||
preview: Role; // experiments, alpha/beta features
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import type { Theme as MuiTheme } from "@mui/material/styles";
|
||||
import type * as monaco from "monaco-editor";
|
||||
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;
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME = "auto";
|
||||
export const DEFAULT_THEME = "dark";
|
||||
|
||||
const theme = {
|
||||
dark,
|
||||
darkBlue,
|
||||
light: dark,
|
||||
light,
|
||||
} satisfies Record<string, Theme>;
|
||||
|
||||
export default theme;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import tw from "../tailwind";
|
||||
|
||||
export default {
|
||||
white: "#fff",
|
||||
|
||||
gray: {
|
||||
17: tw.zinc[950],
|
||||
16: tw.zinc[900],
|
||||
14: tw.zinc[800],
|
||||
13: tw.zinc[700],
|
||||
12: tw.zinc[600],
|
||||
11: tw.zinc[500],
|
||||
9: tw.zinc[400],
|
||||
6: tw.zinc[300],
|
||||
4: tw.zinc[200],
|
||||
2: tw.zinc[100],
|
||||
1: tw.zinc[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],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
import { type NewTheme } from "../experimental";
|
||||
import colors from "../tailwind";
|
||||
|
||||
export default {
|
||||
l1: {
|
||||
background: colors.gray[50],
|
||||
outline: colors.gray[300],
|
||||
fill: colors.gray[700],
|
||||
text: colors.black,
|
||||
},
|
||||
|
||||
l2: {
|
||||
background: colors.gray[100],
|
||||
outline: colors.gray[500],
|
||||
fill: colors.gray[500],
|
||||
text: colors.gray[950],
|
||||
disabled: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
fill: colors.gray[500],
|
||||
text: colors.gray[200],
|
||||
},
|
||||
hover: {
|
||||
background: colors.gray[200],
|
||||
outline: colors.gray[700],
|
||||
fill: "#f00",
|
||||
text: colors.black,
|
||||
},
|
||||
},
|
||||
|
||||
l3: {
|
||||
background: colors.gray[200],
|
||||
outline: colors.gray[700],
|
||||
fill: colors.gray[600],
|
||||
text: colors.black,
|
||||
disabled: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
fill: "#f00",
|
||||
text: colors.gray[200],
|
||||
},
|
||||
hover: {
|
||||
background: "#f00",
|
||||
outline: "#f00",
|
||||
fill: "#f00",
|
||||
text: colors.black,
|
||||
},
|
||||
},
|
||||
|
||||
roles: {
|
||||
danger: {
|
||||
background: colors.orange[50],
|
||||
outline: colors.orange[400],
|
||||
fill: colors.orange[600],
|
||||
text: colors.orange[950],
|
||||
disabled: {
|
||||
background: colors.orange[50],
|
||||
outline: colors.orange[800],
|
||||
fill: colors.orange[800],
|
||||
text: colors.orange[800],
|
||||
},
|
||||
hover: {
|
||||
background: colors.orange[100],
|
||||
outline: colors.orange[500],
|
||||
fill: colors.orange[500],
|
||||
text: colors.black,
|
||||
},
|
||||
},
|
||||
error: {
|
||||
background: colors.red[100],
|
||||
outline: colors.red[500],
|
||||
fill: colors.red[600],
|
||||
text: colors.red[950],
|
||||
},
|
||||
warning: {
|
||||
background: colors.amber[50],
|
||||
outline: colors.amber[300],
|
||||
fill: "#f00",
|
||||
text: colors.amber[950],
|
||||
},
|
||||
notice: {
|
||||
background: colors.yellow[50],
|
||||
outline: colors.yellow[600],
|
||||
fill: colors.yellow[500],
|
||||
text: colors.yellow[950],
|
||||
},
|
||||
info: {
|
||||
background: colors.blue[50],
|
||||
outline: colors.blue[400],
|
||||
fill: colors.blue[600],
|
||||
text: colors.blue[950],
|
||||
},
|
||||
success: {
|
||||
background: colors.green[50],
|
||||
outline: colors.green[500],
|
||||
fill: colors.green[600],
|
||||
text: colors.green[950],
|
||||
disabled: {
|
||||
background: colors.green[50],
|
||||
outline: colors.green[800],
|
||||
fill: colors.green[800],
|
||||
text: colors.green[800],
|
||||
},
|
||||
hover: {
|
||||
background: colors.green[100],
|
||||
outline: colors.green[500],
|
||||
fill: colors.green[500],
|
||||
text: colors.black,
|
||||
},
|
||||
},
|
||||
active: {
|
||||
background: colors.sky[100],
|
||||
outline: colors.sky[500],
|
||||
fill: colors.sky[600],
|
||||
text: colors.sky[950],
|
||||
disabled: {
|
||||
background: colors.sky[50],
|
||||
outline: colors.sky[800],
|
||||
fill: colors.sky[800],
|
||||
text: colors.sky[200],
|
||||
},
|
||||
hover: {
|
||||
background: colors.sky[200],
|
||||
outline: colors.sky[400],
|
||||
fill: colors.sky[500],
|
||||
text: colors.black,
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
background: colors.violet[50],
|
||||
outline: colors.violet[500],
|
||||
fill: colors.violet[600],
|
||||
text: colors.violet[950],
|
||||
},
|
||||
},
|
||||
} satisfies NewTheme;
|
||||
@@ -0,0 +1,11 @@
|
||||
import colors from "./colors";
|
||||
import experimental from "./experimental";
|
||||
import monaco from "./monaco";
|
||||
import muiTheme from "./mui";
|
||||
|
||||
export default {
|
||||
...muiTheme,
|
||||
colors,
|
||||
experimental,
|
||||
monaco,
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import muiTheme from "./mui";
|
||||
import type * as monaco from "monaco-editor";
|
||||
|
||||
export default {
|
||||
base: "vs",
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
token: "comment",
|
||||
foreground: "6B737C",
|
||||
},
|
||||
{
|
||||
token: "type",
|
||||
foreground: "682CD7",
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
foreground: "1766B4",
|
||||
},
|
||||
{
|
||||
token: "variable",
|
||||
foreground: "444444",
|
||||
},
|
||||
{
|
||||
token: "identifier",
|
||||
foreground: "682CD7",
|
||||
},
|
||||
{
|
||||
token: "delimiter.curly",
|
||||
foreground: "EBB325",
|
||||
},
|
||||
],
|
||||
colors: {
|
||||
"editor.foreground": muiTheme.palette.text.primary,
|
||||
"editor.background": muiTheme.palette.background.paper,
|
||||
},
|
||||
} satisfies monaco.editor.IStandaloneThemeData as monaco.editor.IStandaloneThemeData;
|
||||
@@ -0,0 +1,576 @@
|
||||
// eslint-disable-next-line no-restricted-imports -- We need MUI here
|
||||
import { alertClasses } from "@mui/material/Alert";
|
||||
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";
|
||||
import tw from "../tailwind";
|
||||
|
||||
let muiTheme = createTheme({
|
||||
palette: {
|
||||
mode: "light",
|
||||
primary: {
|
||||
main: tw.sky[600],
|
||||
contrastText: tw.sky[50],
|
||||
light: tw.sky[400],
|
||||
dark: tw.sky[500],
|
||||
},
|
||||
secondary: {
|
||||
main: tw.zinc[500],
|
||||
contrastText: tw.zinc[800],
|
||||
dark: tw.zinc[600],
|
||||
},
|
||||
background: {
|
||||
default: tw.zinc[50],
|
||||
paper: tw.zinc[100],
|
||||
},
|
||||
text: {
|
||||
primary: tw.zinc[950],
|
||||
secondary: tw.zinc[700],
|
||||
disabled: tw.zinc[600],
|
||||
},
|
||||
divider: tw.zinc[200],
|
||||
warning: {
|
||||
light: tw.amber[500],
|
||||
main: tw.amber[800],
|
||||
dark: tw.amber[950],
|
||||
},
|
||||
success: {
|
||||
main: tw.green[500],
|
||||
dark: tw.green[600],
|
||||
},
|
||||
info: {
|
||||
light: tw.blue[400],
|
||||
main: tw.blue[600],
|
||||
dark: tw.blue[950],
|
||||
contrastText: tw.zinc[200],
|
||||
},
|
||||
error: {
|
||||
light: tw.red[400],
|
||||
main: tw.red[500],
|
||||
dark: tw.red[950],
|
||||
contrastText: tw.zinc[800],
|
||||
},
|
||||
action: {
|
||||
hover: tw.zinc[100],
|
||||
},
|
||||
neutral: {
|
||||
main: tw.zinc[950],
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: BODY_FONT_FAMILY,
|
||||
|
||||
body1: {
|
||||
fontSize: "1rem" /* 16px at default scaling */,
|
||||
lineHeight: "160%",
|
||||
},
|
||||
|
||||
body2: {
|
||||
fontSize: "0.875rem" /* 14px at default scaling */,
|
||||
lineHeight: "160%",
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius,
|
||||
},
|
||||
});
|
||||
|
||||
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: tw.zinc[700],
|
||||
},
|
||||
},
|
||||
},
|
||||
// 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: {
|
||||
textTransform: "none",
|
||||
letterSpacing: "normal",
|
||||
fontWeight: 500,
|
||||
height: BUTTON_MD_HEIGHT,
|
||||
padding: "8px 16px",
|
||||
borderRadius: "6px",
|
||||
fontSize: 14,
|
||||
|
||||
whiteSpace: "nowrap",
|
||||
":focus-visible": {
|
||||
outline: `2px solid ${muiTheme.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: {
|
||||
boxShadow: "0 1px 4px #0001",
|
||||
":hover": {
|
||||
boxShadow: "0 1px 4px #0001",
|
||||
border: `1px solid ${tw.zinc[500]}`,
|
||||
},
|
||||
"&.Mui-disabled": {
|
||||
boxShadow: "none !important",
|
||||
},
|
||||
},
|
||||
outlinedNeutral: {
|
||||
borderColor: tw.zinc[300],
|
||||
|
||||
"&.Mui-disabled": {
|
||||
borderColor: tw.zinc[200],
|
||||
color: tw.zinc[500],
|
||||
|
||||
"& > .MuiLoadingButton-loadingIndicator": {
|
||||
color: tw.zinc[500],
|
||||
},
|
||||
},
|
||||
},
|
||||
contained: {
|
||||
boxShadow: "0 1px 4px #0001",
|
||||
"&.Mui-disabled": {
|
||||
boxShadow: "none !important",
|
||||
},
|
||||
":hover": {
|
||||
boxShadow: "0 1px 4px #0001",
|
||||
},
|
||||
},
|
||||
containedNeutral: {
|
||||
backgroundColor: tw.zinc[100],
|
||||
border: `1px solid ${tw.zinc[200]}`,
|
||||
|
||||
"&.Mui-disabled": {
|
||||
backgroundColor: tw.zinc[50],
|
||||
border: `1px solid ${tw.zinc[100]}`,
|
||||
},
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: tw.zinc[200],
|
||||
border: `1px solid ${tw.zinc[300]}`,
|
||||
},
|
||||
},
|
||||
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: `${tw.zinc[300]} !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: tw.zinc[400],
|
||||
},
|
||||
},
|
||||
},
|
||||
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: tw.zinc[300],
|
||||
},
|
||||
// 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[600]}`,
|
||||
},
|
||||
|
||||
"&.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[600]}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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.background.paper,
|
||||
color: muiTheme.palette.secondary.contrastText,
|
||||
border: `1px solid ${muiTheme.palette.divider}`,
|
||||
padding: "8px 16px",
|
||||
boxShadow: "0 1px 4px #0001",
|
||||
},
|
||||
},
|
||||
},
|
||||
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 ${tw.blue[600]}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ThemeOptions);
|
||||
|
||||
export default muiTheme;
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import type { Theme } from "@emotion/react";
|
||||
|
||||
export const getLatencyColor = (theme: Theme, latency?: number) => {
|
||||
if (!latency) {
|
||||
return theme.palette.text.secondary;
|
||||
}
|
||||
|
||||
let color = theme.palette.success.light;
|
||||
let color = theme.experimental.roles.success.fill;
|
||||
|
||||
if (latency >= 150 && latency < 300) {
|
||||
color = theme.palette.warning.light;
|
||||
color = theme.experimental.roles.warning.fill;
|
||||
} else if (latency >= 300) {
|
||||
color = theme.palette.error.light;
|
||||
color = theme.experimental.roles.error.fill;
|
||||
}
|
||||
return color;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user