chore: de-emotion style constant (#24835)

This pull-request looks at all (most) of our instances of `const styles
= { ... }` and attempts to smooth them down into the minimum viable
Tailwind equivalent 🙂
This commit is contained in:
Jake Howell
2026-05-07 09:21:24 +10:00
committed by GitHub
parent d19e5f86a7
commit 8c2b1c7d69
34 changed files with 365 additions and 1347 deletions
@@ -1,6 +1,6 @@
import type { Interpolation, Theme } from "@emotion/react";
import TextField from "@mui/material/TextField";
import { type FC, type FormEvent, useId, useState } from "react";
import { Alert } from "#/components/Alert/Alert";
import { ConfirmDialog } from "../ConfirmDialog/ConfirmDialog";
interface DeleteDialogProps {
@@ -65,7 +65,11 @@ export const DeleteDialog: FC<DeleteDialogProps> = ({
<p>
{verb ?? "Deleting"} this {entity} is irreversible!
</p>
{Boolean(info) && <div css={styles.callout}>{info}</div>}
{Boolean(info) && (
<Alert severity="warning" prominent>
{info}
</Alert>
)}
<p>
Type <strong>{name}</strong> below to confirm.
</p>
@@ -102,13 +106,3 @@ export const DeleteDialog: FC<DeleteDialogProps> = ({
/>
);
};
const styles = {
callout: (theme) => ({
backgroundColor: theme.roles.danger.background,
border: `1px solid ${theme.roles.danger.outline}`,
borderRadius: theme.shape.borderRadius,
color: theme.roles.danger.text,
padding: "8px 16px",
}),
} satisfies Record<string, Interpolation<Theme>>;
+13 -65
View File
@@ -1,9 +1,9 @@
import { css, type Interpolation, type Theme } from "@emotion/react";
import CircularProgress from "@mui/material/CircularProgress";
import { CloudUploadIcon, FolderIcon, TrashIcon } from "lucide-react";
import { type DragEvent, type FC, type ReactNode, useRef } from "react";
import { Button } from "#/components/Button/Button";
import { useClickable } from "#/hooks/useClickable";
import { cn } from "#/utils/cn";
interface FileUploadProps {
isUploading: boolean;
@@ -34,10 +34,7 @@ export const FileUpload: FC<FileUploadProps> = ({
if (!isUploading && file) {
return (
<div
css={styles.file}
className="flex flex-row justify-between items-center gap-4"
>
<div className="flex flex-row items-center justify-between gap-4 rounded-lg border border-border bg-surface-primary p-4">
<div className="flex flex-row items-center gap-4">
<FolderIcon className="size-icon-sm" />
<span>{file.name}</span>
@@ -59,12 +56,16 @@ export const FileUpload: FC<FileUploadProps> = ({
<>
<div
data-testid="drop-zone"
css={[styles.root, isUploading && styles.disabled]}
className={cn(
"flex cursor-pointer items-center justify-center rounded-lg border-2",
"border-dashed border-border p-12 hover:bg-surface-primary",
isUploading && "pointer-events-none opacity-75",
)}
{...clickable}
{...fileDrop}
>
<div className="flex flex-col items-center gap-2">
<div css={styles.iconWrapper}>
<div className="flex size-16 items-center justify-center">
{isUploading ? (
<CircularProgress size={32} />
) : (
@@ -73,8 +74,10 @@ export const FileUpload: FC<FileUploadProps> = ({
</div>
<div className="flex flex-col items-center gap-1">
<span css={styles.title}>{title}</span>
<span css={styles.description}>{description}</span>
<span className="text-base leading-none">{title}</span>
<span className="mt-1 max-w-[400px] text-center text-sm leading-6 text-content-secondary">
{description}
</span>
</div>
</div>
</div>
@@ -83,7 +86,7 @@ export const FileUpload: FC<FileUploadProps> = ({
type="file"
data-testid="file-upload"
ref={inputRef}
css={styles.input}
className="hidden"
accept={extensions?.map((ext) => `.${ext}`).join(",")}
onChange={(event) => {
const file = event.currentTarget.files?.[0];
@@ -136,58 +139,3 @@ const useFileDrop = (
onDrop,
};
};
const styles = {
root: (theme) => css`
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
border: 2px dashed ${theme.palette.divider};
padding: 48px;
cursor: pointer;
&:hover {
background-color: ${theme.palette.background.paper};
}
`,
disabled: {
pointerEvents: "none",
opacity: 0.75,
},
// Used to maintain the size of icon and spinner
iconWrapper: {
width: 64,
height: 64,
display: "flex",
alignItems: "center",
justifyContent: "center",
},
title: {
fontSize: 16,
lineHeight: "1",
},
description: (theme) => ({
color: theme.palette.text.secondary,
textAlign: "center",
maxWidth: 400,
fontSize: 14,
lineHeight: "1.5",
marginTop: 4,
}),
input: {
display: "none",
},
file: (theme) => ({
borderRadius: 8,
border: `1px solid ${theme.palette.divider}`,
padding: 16,
background: theme.palette.background.paper,
}),
} satisfies Record<string, Interpolation<Theme>>;
+21 -75
View File
@@ -1,4 +1,4 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { useTheme } from "@emotion/react";
import {
type ComponentProps,
createContext,
@@ -91,27 +91,34 @@ export const FormSection: FC<FormSectionProps> = ({
return (
<section
ref={ref}
css={[
styles.formSection,
direction === "horizontal" && styles.formSectionHorizontal,
]}
className={classes.root}
className={cn(
"flex items-start flex-col gap-4 lg:gap-6",
direction === "horizontal" && "lg:flex-row lg:gap-[120px]",
classes.root,
)}
>
<div
css={[
styles.formSectionInfo,
direction === "horizontal" && styles.formSectionInfoHorizontal,
]}
className={classes.sectionInfo}
className={cn(
"w-full shrink-0 top-6",
direction === "horizontal" && "lg:sticky lg:max-w-[312px]",
classes.sectionInfo,
)}
>
<header className="flex items-center gap-4">
<h2 css={styles.formSectionInfoTitle} className={classes.infoTitle}>
<h2
className={cn(
"m-0 mb-2 flex flex-row items-center gap-3 text-xl font-medium text-content-primary",
classes.infoTitle,
)}
>
{title}
</h2>
{alpha && <AlphaBadge />}
{deprecated && <DeprecatedBadge />}
</header>
<div css={styles.formSectionInfoDescription}>{description}</div>
<div className="m-0 text-sm leading-[160%] text-content-secondary">
{description}
</div>
</div>
{children}
@@ -124,71 +131,10 @@ export const FormFields: FC<ComponentProps<"div">> = ({
...props
}) => {
return (
<div
className={cn("flex flex-col gap-6", className)}
{...props}
css={styles.formSectionFields}
/>
<div className={cn("flex w-full flex-col gap-6", className)} {...props} />
);
};
const styles = {
formSection: (theme) => ({
display: "flex",
alignItems: "flex-start",
flexDirection: "column",
gap: 24,
[theme.breakpoints.down("lg")]: {
flexDirection: "column",
gap: 16,
},
}),
formSectionHorizontal: {
flexDirection: "row",
gap: 120,
},
formSectionInfo: (theme) => ({
width: "100%",
flexShrink: 0,
top: 24,
[theme.breakpoints.down("md")]: {
width: "100%",
position: "initial" as const,
},
}),
formSectionInfoHorizontal: (theme) => ({
maxWidth: 312,
[theme.breakpoints.up("lg")]: {
position: "sticky",
},
}),
formSectionInfoTitle: (theme) => ({
fontSize: 20,
color: theme.palette.text.primary,
fontWeight: 500,
margin: 0,
marginBottom: 8,
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 12,
}),
formSectionInfoDescription: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
lineHeight: "160%",
margin: 0,
}),
formSectionFields: {
width: "100%",
},
} satisfies Record<string, Interpolation<Theme>>;
export const FormFooter: FC<HTMLProps<HTMLDivElement>> = ({
className,
...props
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import CircularProgress from "@mui/material/CircularProgress";
import Link from "@mui/material/Link";
import { isAxiosError } from "axios";
@@ -72,7 +71,7 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
deviceExchangeError,
}) => {
let status = (
<p css={styles.status}>
<p className="flex items-center justify-center gap-2 text-content-disabled">
<CircularProgress size={16} color="secondary" data-chromatic="ignore" />
Checking for authentication...
</p>
@@ -131,10 +130,12 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
return (
<div>
<p css={styles.text}>
<p className="m-0 text-center text-base leading-relaxed text-content-secondary">
Copy your one-time code:&nbsp;
<div css={styles.copyCode}>
<span css={styles.code}>{externalAuthDevice.user_code}</span>
<div className="inline-flex items-center">
<span className="font-bold text-content-primary">
{externalAuthDevice.user_code}
</span>
&nbsp;{" "}
<CopyButton
text={externalAuthDevice.user_code}
@@ -144,9 +145,9 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
<br />
Then open the link below and paste it:
</p>
<div css={styles.links}>
<div className="m-4 flex flex-col gap-1">
<Link
css={styles.link}
className="flex items-center justify-center gap-2 text-base"
href={externalAuthDevice.verification_uri}
target="_blank"
rel="noreferrer"
@@ -160,46 +161,3 @@ export const GitDeviceAuth: FC<GitDeviceAuthProps> = ({
</div>
);
};
const styles = {
text: (theme) => ({
fontSize: 16,
color: theme.palette.text.secondary,
textAlign: "center",
lineHeight: "160%",
margin: 0,
}),
copyCode: {
display: "inline-flex",
alignItems: "center",
},
code: (theme) => ({
fontWeight: "bold",
color: theme.palette.text.primary,
}),
links: {
display: "flex",
gap: 4,
margin: 16,
flexDirection: "column",
},
link: {
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 16,
gap: 8,
},
status: (theme) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 8,
color: theme.palette.text.disabled,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -64,6 +64,13 @@ export const Active: Story = {
},
};
export const Muted: Story = {
args: {
children: "Muted",
type: "muted" as const,
},
};
export const WithIcon: Story = {
args: {
children: "Information",
+39 -67
View File
@@ -1,46 +1,58 @@
import type { Interpolation, Theme } from "@emotion/react";
import { useTheme } from "@emotion/react";
import CircularProgress, {
type CircularProgressProps,
} from "@mui/material/CircularProgress";
import { type FC, type ReactNode, useMemo } from "react";
import type { ThemeRole } from "#/theme/roles";
import { cn } from "#/utils/cn";
type PillType = ThemeRole | "muted";
type PillProps = React.ComponentPropsWithRef<"div"> & {
icon?: ReactNode;
type?: ThemeRole;
type?: PillType;
size?: "md" | "lg";
};
const themeStyles = (type: ThemeRole) => (theme: Theme) => {
const palette = theme.roles[type];
return {
backgroundColor: palette.background,
borderColor: palette.outline,
};
};
const PILL_HEIGHT = 24;
const PILL_ICON_SIZE = 14;
const PILL_ICON_SPACING = (PILL_HEIGHT - PILL_ICON_SIZE) / 2;
export const Pill: FC<PillProps> = ({
icon,
type = "inactive",
children,
size = "md",
className,
style,
...divProps
}) => {
const typeStyles = useMemo(() => themeStyles(type), [type]);
const theme = useTheme();
const roleColors = useMemo(() => {
if (type === "muted") {
return undefined;
}
const palette = theme.roles[type];
return {
backgroundColor: palette.background,
borderColor: palette.outline,
color: palette.text,
};
}, [theme, type]);
return (
<div
css={[
styles.pill,
Boolean(icon) && size === "md" && styles.pillWithIcon,
size === "lg" && styles.pillLg,
Boolean(icon) && size === "lg" && styles.pillLgWithIcon,
typeStyles,
]}
className={cn(
"inline-flex items-center whitespace-nowrap rounded-full border border-solid",
"font-normal text-xs leading-none cursor-default",
"[&>svg]:size-[14px]",
type === "muted" &&
"bg-surface-tertiary border-border-secondary text-content-secondary",
size === "md" && "h-6 gap-[5px] px-3",
Boolean(icon) && size === "md" && "pl-[5px]",
size === "lg" && "h-[30px] gap-[10px] px-4",
Boolean(icon) && size === "lg" && "pl-[10px]",
className,
)}
style={{ ...roleColors, ...style }}
{...divProps}
>
{icon}
@@ -50,53 +62,13 @@ export const Pill: FC<PillProps> = ({
};
export const PillSpinner: FC<CircularProgressProps> = (props) => {
const theme = useTheme();
return (
<CircularProgress size={PILL_ICON_SIZE} css={styles.spinner} {...props} />
<CircularProgress
size={PILL_ICON_SIZE}
sx={{ "& svg": { transform: "scale(.75)" } }}
style={{ color: theme.experimental.l1.text }}
{...props}
/>
);
};
const styles = {
pill: (theme) => ({
fontSize: 12,
color: theme.experimental.l1.text,
cursor: "default",
display: "inline-flex",
alignItems: "center",
whiteSpace: "nowrap",
fontWeight: 400,
borderWidth: 1,
borderStyle: "solid",
borderRadius: 99999,
lineHeight: 1,
height: PILL_HEIGHT,
gap: PILL_ICON_SPACING,
paddingLeft: 12,
paddingRight: 12,
"& svg": {
width: PILL_ICON_SIZE,
height: PILL_ICON_SIZE,
},
}),
pillWithIcon: {
paddingLeft: PILL_ICON_SPACING,
},
pillLg: {
gap: PILL_ICON_SPACING * 2,
padding: "14px 16px",
},
pillLgWithIcon: {
paddingLeft: PILL_ICON_SPACING * 2,
},
spinner: (theme) => ({
color: theme.experimental.l1.text,
// It is necessary to align it with the MUI Icons internal padding
"& svg": {
transform: "scale(.75)",
},
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import { CircleCheckIcon, CircleMinusIcon, TagIcon, XIcon } from "lucide-react";
import type { ComponentProps, FC } from "react";
import { Button } from "#/components/Button/Button";
@@ -85,9 +84,9 @@ const BooleanPill: FC<BooleanPillProps> = ({
size="lg"
icon={
value ? (
<CircleCheckIcon css={styles.truePill} className="size-icon-sm" />
<CircleCheckIcon className="size-icon-sm text-content-link" />
) : (
<CircleMinusIcon css={styles.falsePill} className="size-icon-sm" />
<CircleMinusIcon className="size-icon-sm text-content-warning" />
)
}
{...divProps}
@@ -96,12 +95,3 @@ const BooleanPill: FC<BooleanPillProps> = ({
</Pill>
);
};
const styles = {
truePill: (theme) => ({
color: theme.roles.active.outline,
}),
falsePill: (theme) => ({
color: theme.roles.danger.outline,
}),
} satisfies Record<string, Interpolation<Theme>>;
+17 -112
View File
@@ -1,8 +1,8 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC } from "react";
import type { WorkspaceAgent } from "#/api/typesGenerated";
import { TerminalIcon } from "#/components/Icons/TerminalIcon";
import { VSCodeIcon } from "#/components/Icons/VSCodeIcon";
import { cn } from "#/utils/cn";
import { DisplayAppNameMap } from "./AppLink/AppLink";
import { AppPreview } from "./AppLink/AppPreview";
import { BaseIcon } from "./AppLink/BaseIcon";
@@ -23,55 +23,36 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
return (
<div
key={agent.id}
className="flex flex-row items-center justify-between gap-4"
css={styles.agentRow}
className="relative flex flex-row items-center justify-between gap-4 bg-surface-secondary px-8 pb-4 pt-4 text-base [&:not(:last-child)]:pb-0 after:absolute after:left-[43px] after:top-0 after:h-full after:w-0.5 after:bg-border after:content-['']"
>
<div className="flex flex-row items-baseline gap-4">
<div css={styles.agentStatusWrapper}>
<div css={styles.agentStatusPreview} />
<div className="flex w-6 shrink-0 justify-center">
<div className="relative z-[1] size-2.5 rounded-full border-solid border-2 border-content-secondary bg-surface-secondary" />
</div>
<div
className="flex flex-row items-baseline gap-8"
css={styles.agentData}
>
<div className="flex flex-row items-baseline gap-8 font-normal text-sm text-content-secondary max-md:flex-wrap max-md:gap-4">
<div
className="flex flex-row items-baseline gap-2"
css={[
styles.noShrink,
styles.agentDataItem,
(theme) => ({
[theme.breakpoints.up("sm")]: {
minWidth: alignValues ? 240 : undefined,
},
}),
]}
className={cn(
"flex shrink-0 flex-row items-baseline gap-2 max-md:w-fit max-md:flex-col max-md:items-start max-md:gap-2",
alignValues && "sm:min-w-[240px]",
)}
>
<span>Agent:</span>
<span css={styles.agentDataValue}>{agent.name}</span>
<span className="text-content-primary">{agent.name}</span>
</div>
<div
className="flex flex-row items-baseline gap-2"
css={[
styles.noShrink,
styles.agentDataItem,
(theme) => ({
[theme.breakpoints.up("sm")]: {
minWidth: alignValues ? 100 : undefined,
},
}),
]}
className={cn(
"flex shrink-0 flex-row items-baseline gap-2 max-md:w-fit max-md:flex-col max-md:items-start max-md:gap-2",
alignValues && "sm:min-w-[100px]",
)}
>
<span>OS:</span>
<span css={[styles.agentDataValue, styles.agentOS]}>
<span className="font-normal text-sm capitalize text-content-primary">
{agent.operating_system}
</span>
</div>
<div
className="flex flex-row items-center gap-2"
css={styles.agentDataItem}
>
<div className="flex flex-row items-center gap-2 max-md:w-fit max-md:flex-col max-md:items-start max-md:gap-2">
<span>Apps:</span>
<div className="flex flex-row items-center gap-1 flex-wrap">
{/* We display all modules returned in agent.apps */}
@@ -112,7 +93,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
)
)}
{agent.apps.length === 0 && agent.display_apps.length === 0 && (
<span css={styles.agentDataValue}>None</span>
<span className="text-content-primary">None</span>
)}
</div>
</div>
@@ -121,79 +102,3 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
</div>
);
};
const styles = {
agentRow: (theme) => ({
padding: "16px 32px",
backgroundColor: theme.palette.background.paper,
fontSize: 16,
position: "relative",
"&:not(:last-child)": {
paddingBottom: 0,
},
"&:after": {
content: "''",
height: "100%",
width: 2,
backgroundColor: theme.palette.divider,
position: "absolute",
top: 0,
left: 43,
},
}),
agentStatusWrapper: {
width: 24,
display: "flex",
justifyContent: "center",
flexShrink: 0,
},
agentStatusPreview: (theme) => ({
width: 10,
height: 10,
border: `2px solid ${theme.palette.text.secondary}`,
borderRadius: "100%",
position: "relative",
zIndex: 1,
background: theme.palette.background.paper,
}),
agentName: {
fontWeight: 600,
},
agentOS: {
textTransform: "capitalize",
fontSize: 14,
},
agentData: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
[theme.breakpoints.down("md")]: {
gap: 16,
flexWrap: "wrap",
},
}),
agentDataValue: (theme) => ({
color: theme.palette.text.primary,
}),
noShrink: {
flexShrink: 0,
},
agentDataItem: (theme) => ({
[theme.breakpoints.down("md")]: {
flexDirection: "column",
alignItems: "flex-start",
gap: 8,
width: "fit-content",
},
}),
} satisfies Record<string, Interpolation<Theme>>;
+13 -42
View File
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import { TriangleAlertIcon } from "lucide-react";
import type { FC } from "react";
@@ -24,6 +23,13 @@ import {
agentScriptMessages,
} from "../workspaces/health";
const statusDotBaseClassName = "size-1.5 shrink-0 rounded-full";
const statusDotConnectedClassName =
"bg-content-success shadow-[0_0_12px_0] shadow-content-success";
const statusDotDisconnectedClassName = "bg-content-secondary";
const statusDotConnectingClassName =
"bg-content-link animate-pulse [animation-delay:0.5s]";
// If we think in the agent status and lifecycle into a single enum/state I'd
// say we would have: connecting, timeout, disconnected, connected:created,
// connected:starting, connected:start_timeout, connected:start_error,
@@ -86,7 +92,7 @@ const ReadyLifecycle: FC = () => {
role="status"
data-testid="agent-status-ready"
aria-label="Ready"
css={[styles.status, styles.connected]}
className={cn(statusDotBaseClassName, statusDotConnectedClassName)}
/>
);
};
@@ -98,7 +104,7 @@ const StartingLifecycle: FC = () => {
<div
role="status"
aria-label="Starting..."
css={[styles.status, styles.connecting]}
className={cn(statusDotBaseClassName, statusDotConnectingClassName)}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Starting...</TooltipContent>
@@ -146,7 +152,7 @@ const ShuttingDownLifecycle: FC = () => {
<div
role="status"
aria-label="Stopping..."
css={[styles.status, styles.connecting]}
className={cn(statusDotBaseClassName, statusDotConnectingClassName)}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Stopping...</TooltipContent>
@@ -180,7 +186,7 @@ const OffLifecycle: FC = () => {
<div
role="status"
aria-label="Stopped"
css={[styles.status, styles.disconnected]}
className={cn(statusDotBaseClassName, statusDotDisconnectedClassName)}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Stopped</TooltipContent>
@@ -225,7 +231,7 @@ const DisconnectedStatus: FC = () => {
<div
role="status"
aria-label="Disconnected"
css={[styles.status, styles.disconnected]}
className={cn(statusDotBaseClassName, statusDotDisconnectedClassName)}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Disconnected</TooltipContent>
@@ -240,7 +246,7 @@ const ConnectingStatus: FC = () => {
<div
role="status"
aria-label="Connecting..."
css={[styles.status, styles.connecting]}
className={cn(statusDotBaseClassName, statusDotConnectingClassName)}
/>
</TooltipTrigger>
<TooltipContent side="bottom">Connecting...</TooltipContent>
@@ -310,38 +316,3 @@ export const DevcontainerStatus: FC<DevcontainerStatusProps> = ({
return <SubAgentStatus agent={agent} />;
};
const styles = {
status: {
width: 6,
height: 6,
borderRadius: "100%",
flexShrink: 0,
},
connected: (theme) => ({
backgroundColor: theme.palette.success.light,
boxShadow: `0 0 12px 0 ${theme.palette.success.light}`,
}),
disconnected: (theme) => ({
backgroundColor: theme.palette.text.secondary,
}),
"@keyframes pulse": {
"0%": {
opacity: 1,
},
"50%": {
opacity: 0.4,
},
"100%": {
opacity: 1,
},
},
connecting: (theme) => ({
backgroundColor: theme.palette.info.light,
animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite",
}),
} satisfies Record<string, Interpolation<Theme>>;
+23 -76
View File
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import { Children, type FC, type JSX, useState } from "react";
import type { WorkspaceAgent, WorkspaceResource } from "#/api/typesGenerated";
import { ChevronDownIcon } from "#/components/AnimatedIcons/ChevronDown";
@@ -13,66 +12,6 @@ import {
import { ResourceAvatar } from "./ResourceAvatar";
import { SensitiveValue } from "./SensitiveValue";
const styles = {
resourceCard: (theme) => ({
border: `1px solid ${theme.palette.divider}`,
background: theme.palette.background.default,
"&:not(:last-child)": {
borderBottom: 0,
},
"&:first-of-type": {
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
},
"&:last-child": {
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
},
}),
resourceCardProfile: {
flexShrink: 0,
width: "fit-content",
minWidth: 220,
},
resourceCardHeader: (theme) => ({
padding: "24px 32px",
borderBottom: `1px solid ${theme.palette.divider}`,
"&:last-child": {
borderBottom: 0,
},
[theme.breakpoints.down("md")]: {
width: "100%",
overflow: "scroll",
},
}),
metadata: () => ({
lineHeight: "1.5",
fontSize: 14,
}),
metadataLabel: (theme) => ({
fontSize: 12,
color: theme.palette.text.secondary,
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}),
metadataValue: () => ({
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}),
} satisfies Record<string, Interpolation<Theme>>;
interface ResourceCardProps {
resource: WorkspaceResource;
agentRow: (agent: WorkspaceAgent) => JSX.Element;
@@ -95,18 +34,22 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
const gridWidth = mLength === 1 ? 1 : 4;
return (
<div key={resource.id} css={styles.resourceCard} className="resource-card">
<div
className="flex flex-row items-start gap-20"
css={styles.resourceCardHeader}
>
<div className="flex flex-row gap-2" css={styles.resourceCardProfile}>
<div
key={resource.id}
className="resource-card border border-solid border-border bg-surface-primary [&:not(:last-child)]:border-b-0 first:rounded-t-[8px] last:rounded-b-[8px]"
>
<div className="flex flex-row items-start gap-20 border-0 border-b border-solid border-border px-8 py-6 last:border-b-0 max-md:w-full max-md:overflow-auto">
<div className="flex w-fit min-w-[220px] shrink-0 flex-row gap-2">
<div>
<ResourceAvatar resource={resource} />
</div>
<div css={styles.metadata}>
<div css={styles.metadataLabel}>{resource.type}</div>
<div css={styles.metadataValue}>{resource.name}</div>
<div className="font-normal text-sm leading-6">
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-xs text-content-secondary">
{resource.type}
</div>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{resource.name}
</div>
</div>
</div>
@@ -117,18 +60,22 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
}}
>
{resource.daily_cost > 0 && (
<div css={styles.metadata}>
<div css={styles.metadataLabel}>
<div className="font-normal text-sm leading-6">
<div className="overflow-hidden text-ellipsis whitespace-nowrap font-normal text-xs text-content-secondary">
<b>Daily cost</b>
</div>
<div css={styles.metadataValue}>{resource.daily_cost}</div>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{resource.daily_cost}
</div>
</div>
)}
{visibleMetadata.map((meta) => {
return (
<div css={styles.metadata} key={meta.key}>
<div css={styles.metadataLabel}>{meta.key}</div>
<div css={styles.metadataValue}>
<div className="font-normal text-sm leading-6" key={meta.key}>
<div className="overflow-hidden text-ellipsis whitespace-nowrap font-normal text-xs text-content-secondary">
{meta.key}
</div>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{meta.sensitive ? (
<SensitiveValue value={meta.value} />
) : (
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import type { FC, HTMLAttributes } from "react";
import { Link as RouterLink } from "react-router";
@@ -6,6 +5,7 @@ import type { TemplateExample } from "#/api/typesGenerated";
import { Button } from "#/components/Button/Button";
import { ExternalImage } from "#/components/ExternalImage/ExternalImage";
import { Pill } from "#/components/Pill/Pill";
import { cn } from "#/utils/cn";
type TemplateExampleCardProps = HTMLAttributes<HTMLDivElement> & {
example: TemplateExample;
@@ -15,22 +15,35 @@ type TemplateExampleCardProps = HTMLAttributes<HTMLDivElement> & {
export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
example,
activeTag,
className,
...divProps
}) => {
return (
<div css={styles.card} {...divProps}>
<div css={styles.header}>
<div css={styles.icon}>
<div
className={cn(
"flex w-[320px] flex-col rounded-md border border-solid border-border p-6 text-left text-inherit",
className,
)}
{...divProps}
>
<div className="mb-6 flex items-center justify-between">
<div className="size-8 shrink-0 pt-1">
<ExternalImage
src={example.icon}
className="w-full h-full object-contain"
/>
</div>
<div css={styles.tags}>
<div className="flex flex-wrap justify-end gap-2">
{example.tags.map((tag) => (
<RouterLink key={tag} to={`/starter-templates?tag=${tag}`}>
<Pill css={[styles.tag, activeTag === tag && styles.activeTag]}>
<Pill
className={cn(
"cursor-pointer border-border",
"hover:border-content-primary",
activeTag === tag && "!border-border-pending !bg-surface-sky",
)}
>
{tag}
</Pill>
</RouterLink>
@@ -40,7 +53,7 @@ export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
<div>
<h4 className="text-sm font-semibold m-0 mb-1">{example.name}</h4>
<span css={styles.description}>
<span className="block text-[13px] leading-[1.6] text-content-secondary">
{example.description}{" "}
<Link
component={RouterLink}
@@ -52,7 +65,7 @@ export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
</span>
</div>
<div css={styles.useButtonContainer}>
<div className="mt-auto flex flex-col items-center gap-3 pt-6">
<Button asChild className="w-full">
<RouterLink to={`/templates/new?exampleId=${example.id}`}>
Use template
@@ -62,67 +75,3 @@ export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
</div>
);
};
const styles = {
card: (theme) => ({
width: "320px",
padding: 24,
borderRadius: 6,
border: `1px solid ${theme.palette.divider}`,
textAlign: "left",
color: "inherit",
display: "flex",
flexDirection: "column",
}),
header: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 24,
},
icon: {
flexShrink: 0,
paddingTop: 4,
width: 32,
height: 32,
},
tags: {
display: "flex",
flexWrap: "wrap",
gap: 8,
justifyContent: "end",
},
tag: (theme) => ({
borderColor: theme.palette.divider,
textDecoration: "none",
cursor: "pointer",
"&: hover": {
borderColor: theme.palette.primary.main,
},
}),
activeTag: (theme) => ({
borderColor: theme.roles.active.outline,
backgroundColor: theme.roles.active.background,
}),
description: (theme) => ({
fontSize: 13,
color: theme.palette.text.secondary,
lineHeight: "1.6",
display: "block",
}),
useButtonContainer: {
display: "flex",
gap: 12,
flexDirection: "column",
paddingTop: 24,
marginTop: "auto",
alignItems: "center",
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,6 +1,6 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC } from "react";
import { MemoizedMarkdown } from "#/components/Markdown/Markdown";
import { cn } from "#/utils/cn";
interface TemplateUpdateMessageProps {
children: string;
@@ -10,35 +10,16 @@ export const TemplateUpdateMessage: FC<TemplateUpdateMessageProps> = ({
children,
}) => {
return (
<MemoizedMarkdown css={styles.versionMessage}>{children}</MemoizedMarkdown>
<MemoizedMarkdown
className={cn(
"text-sm leading-[1.2]",
"[&_h1]:mb-[0.75em] [&_h2]:mb-[0.75em] [&_h3]:mb-[0.75em]",
"[&_h4]:mb-[0.75em] [&_h5]:mb-[0.75em] [&_h6]:mb-[0.75em]",
"[&_h1]:text-[1.2em] [&_h2]:text-[1.15em] [&_h3]:text-[1.1em]",
"[&_h4]:text-[1.05em] [&_h5]:text-[1em] [&_h6]:text-[0.95em]",
)}
>
{children}
</MemoizedMarkdown>
);
};
const styles = {
versionMessage: {
fontSize: 14,
lineHeight: 1.2,
"& h1, & h2, & h3, & h4, & h5, & h6": {
margin: "0 0 0.75em",
},
"& h1": {
fontSize: "1.2em",
},
"& h2": {
fontSize: "1.15em",
},
"& h3": {
fontSize: "1.1em",
},
"& h4": {
fontSize: "1.05em",
},
"& h5": {
fontSize: "1em",
},
"& h6": {
fontSize: "0.95em",
},
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,5 +1,4 @@
import { css } from "@emotion/css";
import type { Interpolation, Theme } from "@emotion/react";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
@@ -60,13 +59,13 @@ export const UpdateBuildParametersDialog: FC<
>
Workspace parameters
</DialogTitle>
<DialogContent css={styles.content}>
<DialogContent className="px-10">
<DialogContentText className="m-0">
This template has new parameters that must be configured to complete
the update
</DialogContentText>
<VerticalForm
css={styles.form}
className="pt-8"
onSubmit={form.handleSubmit}
id="updateParameters"
>
@@ -96,7 +95,7 @@ export const UpdateBuildParametersDialog: FC<
)}
</VerticalForm>
</DialogContent>
<DialogActions disableSpacing css={styles.dialogActions}>
<DialogActions disableSpacing className="flex flex-col gap-2 p-10">
<Button
variant="outline"
className="w-full"
@@ -123,19 +122,3 @@ const classNames = {
}
`,
};
const styles = {
content: {
padding: "0 40px",
},
form: {
paddingTop: 32,
},
dialogActions: {
padding: 40,
flexDirection: "column",
gap: 8,
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC, HTMLProps } from "react";
import { cn } from "#/utils/cn";
@@ -48,7 +47,21 @@ export const YAxisHeader: FC<HTMLProps<HTMLSpanElement>> = (props) => {
};
export const YAxisLabels: FC<HTMLProps<HTMLUListElement>> = (props) => {
return <ul css={styles.labels} {...props} />;
return (
<ul
{...props}
className={cn(
"m-0 list-none text-right",
"flex flex-col",
props.className,
)}
style={{
...props.style,
gap: "var(--x-axis-rows-gap)",
padding: "var(--section-padding)",
}}
/>
);
};
type YAxisLabelProps = Omit<HTMLProps<HTMLLIElement>, "id"> & {
@@ -57,32 +70,16 @@ type YAxisLabelProps = Omit<HTMLProps<HTMLLIElement>, "id"> & {
export const YAxisLabel: FC<YAxisLabelProps> = ({ id, ...props }) => {
return (
<li {...props} css={styles.label} id={encodeURIComponent(id)}>
<li
{...props}
className={cn(
"flex items-center",
"[&>*]:block [&>*]:w-full [&>*]:overflow-hidden [&>*]:text-ellipsis [&>*]:whitespace-nowrap",
props.className,
)}
id={encodeURIComponent(id)}
>
<span>{props.children}</span>
</li>
);
};
const styles = {
labels: {
margin: 0,
listStyle: "none",
display: "flex",
flexDirection: "column",
gap: "var(--x-axis-rows-gap)",
textAlign: "right",
padding: "var(--section-padding)",
},
label: {
display: "flex",
alignItems: "center",
"& > *": {
display: "block",
width: "100%",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,8 +1,5 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC } from "react";
import type { AuditDiff } from "#/api/typesGenerated";
import { MONOSPACE_FONT_FAMILY } from "#/theme/constants";
import colors from "#/theme/tailwindColors";
const getDiffValue = (value: unknown): string => {
if (typeof value === "string") {
@@ -50,29 +47,33 @@ export const AuditLogDiff: FC<AuditLogDiffProps> = ({ diff }) => {
const diffEntries = Object.entries(diff);
return (
<div css={styles.diff}>
<div css={[styles.diffColumn, styles.diffOld]}>
<div className="relative z-[2] flex items-start border-t border-border font-mono text-sm">
<div className="flex-1 self-stretch bg-red-950 pb-5 pr-4 pt-4 leading-[160%] text-red-50 [overflow-wrap:anywhere]">
{diffEntries.map(([attrName, valueDiff], index) => (
<div key={attrName} css={styles.diffRow}>
<div css={styles.diffLine}>{index + 1}</div>
<div css={styles.diffIcon}>-</div>
<div key={attrName} className="flex items-baseline">
<div className="w-12 shrink-0 text-right opacity-50">
{index + 1}
</div>
<div className="w-8 shrink-0 text-center text-base">-</div>
<div>
{attrName}:{" "}
<span css={[styles.diffValue, styles.diffValueOld]}>
<span className="rounded p-px bg-red-800">
{valueDiff.secret ? "••••••••" : getDiffValue(valueDiff.old)}
</span>
</div>
</div>
))}
</div>
<div css={[styles.diffColumn, styles.diffNew]}>
<div className="flex-1 self-stretch bg-green-950 pb-5 pr-4 pt-4 leading-[160%] text-green-50 [overflow-wrap:anywhere]">
{diffEntries.map(([attrName, valueDiff], index) => (
<div key={attrName} css={styles.diffRow}>
<div css={styles.diffLine}>{index + 1}</div>
<div css={styles.diffIcon}>+</div>
<div key={attrName} className="flex items-baseline">
<div className="w-12 shrink-0 text-right opacity-50">
{index + 1}
</div>
<div className="w-8 shrink-0 text-center text-base">+</div>
<div>
{attrName}:{" "}
<span css={[styles.diffValue, styles.diffValueNew]}>
<span className="rounded p-px bg-green-800">
{valueDiff.secret ? "••••••••" : getDiffValue(valueDiff.new)}
</span>
</div>
@@ -82,67 +83,3 @@ export const AuditLogDiff: FC<AuditLogDiffProps> = ({ diff }) => {
</div>
);
};
const styles = {
diff: (theme) => ({
display: "flex",
alignItems: "flex-start",
fontSize: theme.typography.body2.fontSize,
borderTop: `1px solid ${theme.palette.divider}`,
fontFamily: MONOSPACE_FONT_FAMILY,
position: "relative",
zIndex: 2,
}),
diffColumn: {
flex: 1,
paddingTop: 16,
paddingBottom: 20,
paddingRight: 16,
lineHeight: "160%",
alignSelf: "stretch",
overflowWrap: "anywhere",
},
diffOld: {
backgroundColor: colors.red[950],
color: colors.red[50],
},
diffRow: {
display: "flex",
alignItems: "baseline",
},
diffLine: {
opacity: 0.5,
width: 48,
textAlign: "right",
flexShrink: 0,
},
diffIcon: (theme) => ({
width: 32,
textAlign: "center",
fontSize: theme.typography.body1.fontSize,
flexShrink: 0,
}),
diffNew: {
backgroundColor: colors.green[950],
color: colors.green[50],
},
diffValue: {
padding: 1,
borderRadius: 4,
},
diffValueOld: {
backgroundColor: colors.red[800],
},
diffValueNew: {
backgroundColor: colors.green[800],
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,8 +1,8 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC } from "react";
import { Link as RouterLink } from "react-router";
import { CodeExample } from "#/components/CodeExample/CodeExample";
import { Welcome } from "#/components/Welcome/Welcome";
import { cn } from "#/utils/cn";
type CliInstallPageViewProps = {
origin: string;
@@ -10,10 +10,15 @@ type CliInstallPageViewProps = {
export const CliInstallPageView: FC<CliInstallPageViewProps> = ({ origin }) => {
return (
<div css={styles.container}>
<div
className={cn(
"mx-auto h-screen w-[480px]",
"flex flex-1 flex-col items-center justify-center",
)}
>
<Welcome>Install the Coder CLI</Welcome>
<p css={styles.instructions}>
<p className="pb-2 text-center text-base leading-[1.4] text-content-secondary">
Copy the command below and{" "}
<strong className="block">paste it in your terminal.</strong>
</p>
@@ -25,56 +30,16 @@ export const CliInstallPageView: FC<CliInstallPageViewProps> = ({ origin }) => {
/>
<div className="pt-4">
<RouterLink to="/workspaces" css={styles.backLink}>
<RouterLink
to="/workspaces"
className="block py-4 text-center text-content-primary underline decoration-[hsla(0,0%,100%,0.7)] underline-offset-[3px] hover:no-underline"
>
Go to workspaces
</RouterLink>
</div>
<div css={styles.copyright}>
<div className="mt-6 text-xs text-content-secondary">
&copy; {new Date().getFullYear()} Coder Technologies, Inc.
</div>
</div>
);
};
const styles = {
container: {
flex: 1,
// Fallback to 100vh
height: ["100vh", "-webkit-fill-available"],
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
width: 480,
margin: "auto",
},
instructions: (theme) => ({
fontSize: 16,
color: theme.palette.text.secondary,
paddingBottom: 8,
textAlign: "center",
lineHeight: 1.4,
}),
backLink: (theme) => ({
display: "block",
textAlign: "center",
color: theme.palette.text.primary,
textDecoration: "underline",
textUnderlineOffset: 3,
textDecorationColor: "hsla(0deg, 0%, 100%, 0.7)",
paddingTop: 16,
paddingBottom: 16,
"&:hover": {
textDecoration: "none",
},
}),
copyright: (theme) => ({
fontSize: 12,
color: theme.palette.text.secondary,
marginTop: 24,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,8 +1,8 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC } from "react";
import { Link, useSearchParams } from "react-router";
import type { TemplateExample } from "#/api/typesGenerated";
import { TemplateExampleCard } from "#/modules/templates/TemplateExampleCard/TemplateExampleCard";
import { cn } from "#/utils/cn";
import type { StarterTemplatesByTag } from "#/utils/starterTemplates";
const getTagLabel = (tag: string) => {
@@ -66,13 +66,22 @@ export const StarterTemplates: FC<StarterTemplatesProps> = ({
<div className="flex flex-row gap-8 items-start">
{starterTemplatesByTag && tags && (
<div className="flex flex-col gap-4 w-[202px] shrink-0 sticky">
<h2 css={styles.sectionTitle}>Choose a starter template</h2>
<span css={styles.filterCaption}>Filter</span>
<h2 className="m-0 text-base font-normal text-content-primary">
Choose a starter template
</h2>
<span className="text-xs font-semibold uppercase tracking-[0.1em] text-content-secondary">
Filter
</span>
{tags.map((tag) => (
<Link
key={tag}
to={`?tag=${tag}`}
css={[styles.tagLink, tag === activeTag && styles.tagLinkActive]}
className={cn(
"text-sm capitalize no-underline hover:text-content-primary",
tag === activeTag
? "font-semibold text-content-primary"
: "text-content-secondary",
)}
>
{getTagLabel(tag)} ({starterTemplatesByTag[tag].length})
</Link>
@@ -95,36 +104,3 @@ export const StarterTemplates: FC<StarterTemplatesProps> = ({
</div>
);
};
const styles = {
filterCaption: (theme) => ({
textTransform: "uppercase",
fontWeight: 600,
fontSize: 12,
color: theme.palette.text.secondary,
letterSpacing: "0.1em",
}),
tagLink: (theme) => ({
color: theme.palette.text.secondary,
textDecoration: "none",
fontSize: 14,
textTransform: "capitalize",
"&:hover": {
color: theme.palette.text.primary,
},
}),
tagLinkActive: (theme) => ({
color: theme.palette.text.primary,
fontWeight: 600,
}),
sectionTitle: (theme) => ({
color: theme.palette.text.primary,
fontSize: 16,
fontWeight: 400,
margin: 0,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Drawer from "@mui/material/Drawer";
import { TriangleAlertIcon, XIcon } from "lucide-react";
import type { FC } from "react";
@@ -39,9 +38,12 @@ export const BuildLogsDrawer: FC<BuildLogsDrawerProps> = ({
return (
<Drawer anchor="right" {...drawerProps}>
<div css={styles.root}>
<header css={styles.header}>
<h3 css={styles.title}>Creating template...</h3>
<div className="flex h-full w-[800px] flex-col">
<header
className="flex items-center justify-between border-b border-border px-6"
style={{ height: navHeight }}
>
<h3 className="m-0 text-base font-medium">Creating template...</h3>
<Button size="icon-lg" variant="subtle" onClick={drawerProps.onClose}>
<XIcon />
<span className="sr-only">Close build logs</span>
@@ -72,7 +74,7 @@ export const BuildLogsDrawer: FC<BuildLogsDrawerProps> = ({
)}
{hasLogs ? (
<section css={styles.logs}>
<section className="flex-1 overflow-auto bg-surface-primary">
<WorkspaceBuildLogs logs={logs} className="border-0" />
</section>
) : (
@@ -91,16 +93,16 @@ const MissingVariablesBanner: FC<MissingVariablesBannerProps> = ({
onFillVariables,
}) => {
return (
<div css={bannerStyles.root}>
<div css={bannerStyles.content}>
<TriangleAlertIcon className="size-icon-lg" css={bannerStyles.icon} />
<h4 css={bannerStyles.title}>Missing variables</h4>
<p css={bannerStyles.description}>
<div className="flex items-center justify-center p-10">
<div className="flex max-w-[360px] flex-col items-center text-center">
<TriangleAlertIcon className="size-icon-lg text-content-warning" />
<h4 className="m-0 mt-4 font-medium leading-none">Missing variables</h4>
<p className="m-0 mt-2 text-sm leading-6 text-content-secondary">
During the build process, we identified some missing variables. Rest
assured, we have automatically added them to the form for you.
</p>
<Button
css={bannerStyles.button}
className="mt-4"
size="sm"
variant="outline"
onClick={onFillVariables}
@@ -111,65 +113,3 @@ const MissingVariablesBanner: FC<MissingVariablesBannerProps> = ({
</div>
);
};
const styles = {
root: {
width: 800,
height: "100%",
display: "flex",
flexDirection: "column",
},
header: (theme) => ({
height: navHeight,
padding: "0 24px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
borderBottom: `1px solid ${theme.palette.divider}`,
}),
title: {
margin: 0,
fontWeight: 500,
fontSize: 16,
},
logs: (theme) => ({
flex: 1,
overflow: "auto",
backgroundColor: theme.palette.background.default,
}),
} satisfies Record<string, Interpolation<Theme>>;
const bannerStyles = {
root: {
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 40,
},
content: {
display: "flex",
flexDirection: "column",
alignItems: "center",
textAlign: "center",
maxWidth: 360,
},
icon: (theme) => ({
color: theme.roles.warning.fill.outline,
}),
title: {
fontWeight: 500,
lineHeight: "1",
margin: 0,
marginTop: 16,
},
description: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 14,
margin: 0,
marginTop: 8,
lineHeight: "1.5",
}),
button: {
marginTop: 16,
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import FormControlLabel from "@mui/material/FormControlLabel";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
@@ -17,11 +16,13 @@ interface VariableLabelProps {
const VariableLabel: FC<VariableLabelProps> = ({ variable }) => {
return (
<label htmlFor={variable.name}>
<span css={styles.labelName}>
<span className="mb-1 block text-sm text-content-secondary">
var.{variable.name}
{!variable.required && " (optional)"}
</span>
<span css={styles.labelDescription}>{variable.description}</span>
<span className="block text-base font-semibold text-content-primary">
{variable.description}
</span>
</label>
);
};
@@ -42,7 +43,7 @@ export const VariableInput: FC<VariableInputProps> = ({
return (
<div className="flex flex-col gap-1.5">
<VariableLabel variable={variable} />
<div css={styles.input}>
<div className="flex flex-col">
<VariableField
disabled={disabled}
onChange={onChange}
@@ -109,22 +110,3 @@ const VariableField: FC<VariableInputProps> = ({
/>
);
};
const styles = {
labelName: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
display: "block",
marginBottom: 4,
}),
labelDescription: (theme) => ({
fontSize: 16,
color: theme.palette.text.primary,
display: "block",
fontWeight: 600,
}),
input: {
display: "flex",
flexDirection: "column",
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Checkbox from "@mui/material/Checkbox";
import { EllipsisVerticalIcon } from "lucide-react";
import type { FC } from "react";
@@ -39,12 +38,12 @@ export const AnnouncementBannerItem: FC<AnnouncementBannerItemProps> = ({
/>
</TableCell>
<TableCell css={!enabled && styles.disabled}>
<TableCell className={!enabled ? "text-content-disabled" : ""}>
{message || <em>No message</em>}
</TableCell>
<TableCell>
<div css={styles.colorSample} style={{ backgroundColor }}></div>
<div className="size-6 rounded-sm" style={{ backgroundColor }} />
</TableCell>
<TableCell>
@@ -71,15 +70,3 @@ export const AnnouncementBannerItem: FC<AnnouncementBannerItemProps> = ({
</TableRow>
);
};
const styles = {
disabled: (theme) => ({
color: theme.roles.inactive.fill.outline,
}),
colorSample: {
width: 24,
height: 24,
borderRadius: 4,
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -5,7 +5,6 @@ import {
TooltipContent,
TooltipTrigger,
} from "#/components/Tooltip/Tooltip";
import { cn } from "#/utils/cn";
import { isUUID } from "#/utils/uuid";
interface OrganizationPillsProps {
@@ -23,12 +22,7 @@ export const OrganizationPills: FC<OrganizationPillsProps> = ({
return (
<div className="flex flex-row gap-2">
{orgs.length > 0 ? (
<Pill
className={cn(
"border-none w-fit",
orgs[0].isUUID ? "bg-surface-destructive" : "bg-surface-secondary",
)}
>
<Pill type={orgs[0].isUUID ? "error" : "muted"} className="w-fit">
{orgs[0].name}
</Pill>
) : (
@@ -48,10 +42,7 @@ const OverflowPillList: FC<OverflowPillProps> = ({ organizations }) => {
return (
<Tooltip>
<TooltipTrigger asChild>
<Pill
className="min-h-4 min-w-6 bg-surface-secondary border-none px-3 py-1"
data-testid="overflow-pill"
>
<Pill type="muted" className="w-fit" data-testid="overflow-pill">
+{organizations.length}
</Pill>
</TooltipTrigger>
@@ -61,12 +52,8 @@ const OverflowPillList: FC<OverflowPillProps> = ({ organizations }) => {
{organizations.map((organization) => (
<li key={organization.name}>
<Pill
className={cn(
"border-none w-fit",
organization.isUUID
? "bg-surface-destructive"
: "bg-surface-secondary",
)}
type={organization.isUUID ? "error" : "muted"}
className="w-fit"
>
{organization.name}
</Pill>
@@ -1,4 +1,4 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { useTheme } from "@emotion/react";
import Divider from "@mui/material/Divider";
import { ChevronLeftIcon, CopyIcon } from "lucide-react";
import { type FC, useState } from "react";
@@ -143,7 +143,7 @@ export const EditOAuth2AppPageView: FC<EditOAuth2AppProps> = ({
onCancel={() => setShowDelete(false)}
/>
<dl css={styles.dataList}>
<dl className="grid [grid-template-columns:max-content_auto] [&>dd]:ml-2.5 [&>dt]:font-bold">
<dt>Client ID</dt>
<dd>
<CopyableValue value={app.id} side="right">
@@ -311,16 +311,3 @@ const OAuth2SecretRow: FC<OAuth2SecretRowProps> = ({
</TableRow>
);
};
const styles = {
dataList: {
display: "grid",
gridTemplateColumns: "max-content auto",
"& > dt": {
fontWeight: "bold",
},
"& > dd": {
marginLeft: 10,
},
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,8 +1,7 @@
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
import { WrenchIcon } from "lucide-react";
import type { FC, HTMLAttributes, PropsWithChildren } from "react";
import { DisabledBadge, EnabledBadge } from "#/components/Badges/Badges";
import { MONOSPACE_FONT_FAMILY } from "#/theme/constants";
import { cn } from "#/utils/cn";
export const OptionName: FC<PropsWithChildren> = ({ children }) => {
return (
@@ -22,7 +21,8 @@ interface OptionValueProps {
export const OptionValue: FC<OptionValueProps> = (props) => {
const { children: value } = props;
const theme = useTheme();
const optionClassName =
"text-sm font-mono [overflow-wrap:anywhere] select-all [&_ul]:p-4";
if (typeof value === "boolean") {
return (
@@ -34,7 +34,7 @@ export const OptionValue: FC<OptionValueProps> = (props) => {
if (typeof value === "number") {
return (
<span css={styles.option} className="option-value-number">
<span className={cn("option-value-number", optionClassName)}>
{value}
</span>
);
@@ -42,15 +42,13 @@ export const OptionValue: FC<OptionValueProps> = (props) => {
if (!value || value.length === 0) {
return (
<span css={styles.option} className="option-value-empty">
Not set
</span>
<span className={cn("option-value-empty", optionClassName)}>Not set</span>
);
}
if (typeof value === "string") {
return (
<span css={styles.option} className="option-value-string">
<span className={cn("option-value-string", optionClassName)}>
{value}
</span>
);
@@ -64,16 +62,13 @@ export const OptionValue: FC<OptionValueProps> = (props) => {
.map(([option, isEnabled]) => (
<li
key={option}
css={[
styles.option,
!isEnabled && {
marginLeft: 32,
color: theme.palette.text.disabled,
},
]}
className={`option-array-item-${option} ${
isEnabled ? "option-enabled" : "option-disabled"
}`}
className={cn(
`option-array-item-${option}`,
isEnabled
? "option-enabled"
: "option-disabled ml-8 text-content-disabled",
optionClassName,
)}
>
<div className="inline-flex items-center">
{isEnabled && <WrenchIcon className="size-4 mx-2" />}
@@ -89,7 +84,7 @@ export const OptionValue: FC<OptionValueProps> = (props) => {
return (
<ul className="option-array list-inside">
{value.map((item) => (
<li key={item} css={styles.option}>
<li key={item} className={optionClassName}>
{item}
</li>
))}
@@ -98,7 +93,7 @@ export const OptionValue: FC<OptionValueProps> = (props) => {
}
return (
<span css={styles.option} className="option-value-json">
<span className={cn("option-value-json", optionClassName)}>
{JSON.stringify(value)}
</span>
);
@@ -107,66 +102,36 @@ export const OptionValue: FC<OptionValueProps> = (props) => {
type OptionConfigProps = HTMLAttributes<HTMLDivElement> & { isSource: boolean };
// OptionConfig takes a isSource bool to indicate if the Option is the source of the configured value.
export const OptionConfig: FC<OptionConfigProps> = ({ isSource, ...attrs }) => {
export const OptionConfig: FC<OptionConfigProps> = ({
isSource,
className,
...attrs
}) => {
return (
<div
{...attrs}
css={[styles.configOption, isSource && styles.sourceConfigOption]}
className={cn(
"inline-flex items-center gap-1.5 rounded border border-solid p-1.5",
"font-mono text-[13px] font-semibold leading-none",
"border-border-secondary bg-surface-secondary",
isSource &&
"border-content-link [&_[data-slot=option-config-flag]]:bg-content-link",
className,
)}
/>
);
};
export const OptionConfigFlag: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
const theme = useTheme();
return (
<div
{...props}
className="OptionConfigFlag"
css={{
fontSize: 10,
fontWeight: 600,
display: "block",
backgroundColor: theme.palette.divider,
lineHeight: 1,
padding: "2px 4px",
borderRadius: 1,
}}
data-slot="option-config-flag"
className={cn(
"block rounded-[1px] bg-border-secondary px-1 py-0.5",
"text-[10px] font-semibold leading-none",
props.className,
)}
/>
);
};
const styles = {
configOption: (theme) => ({
fontSize: 13,
fontFamily: MONOSPACE_FONT_FAMILY,
fontWeight: 600,
backgroundColor: theme.palette.background.paper,
display: "inline-flex",
alignItems: "center",
borderRadius: 4,
padding: 6,
lineHeight: 1,
gap: 6,
border: `1px solid ${theme.palette.divider}`,
}),
sourceConfigOption: (theme) => ({
border: `1px solid ${theme.roles.active.fill.outline}`,
"& .OptionConfigFlag": {
background: theme.roles.active.fill.solid,
},
}),
option: css`
font-size: 14px;
font-family: ${MONOSPACE_FONT_FAMILY};
overflow-wrap: anywhere;
user-select: all;
& ul {
padding: 16px;
}
`,
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import { ExternalLinkIcon, RotateCwIcon } from "lucide-react";
import type { FC, ReactNode } from "react";
@@ -69,7 +68,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
You&apos;ve authenticated with {externalAuth.display_name}!
</Welcome>
<p css={styles.text}>
<p className="m-0 text-center text-base leading-relaxed text-content-secondary">
{externalAuth.user?.login && `Hey @${externalAuth.user?.login}! 👋`}
{(!externalAuth.app_installable ||
externalAuth.installations.length > 0) &&
@@ -77,10 +76,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
</p>
{externalAuth.installations.length > 0 && (
<div
css={styles.authorizedInstalls}
className="flex gap-2 items-center"
>
<div className="m-8 flex items-center gap-1 text-content-disabled">
{externalAuth.installations.map((install) => {
if (!install.account) {
return;
@@ -111,9 +107,9 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
</div>
)}
<div css={styles.links}>
<div className="m-4 flex flex-col gap-1">
{!hasInstallations && externalAuth.app_installable && (
<Alert severity="warning" css={styles.installAlert}>
<Alert severity="warning" className="m-4">
You must {installTheApp} to clone private repositories. Accounts
will appear here once authorized.
</Alert>
@@ -126,7 +122,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
href={externalAuth.app_install_url}
target="_blank"
rel="noreferrer"
css={styles.link}
className="flex items-center justify-center gap-2 text-base"
>
<ExternalLinkIcon className="size-icon-xs" />
{externalAuth.installations.length > 0 ? "Configure" : "Install"}{" "}
@@ -134,7 +130,7 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
</Link>
)}
<Link
css={styles.link}
className="flex items-center justify-center gap-2 text-base"
href="#"
onClick={() => {
onReauthenticate();
@@ -148,39 +144,3 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
};
export default ExternalAuthPageView;
const styles = {
text: (theme) => ({
fontSize: 16,
color: theme.palette.text.secondary,
textAlign: "center",
lineHeight: "160%",
margin: 0,
}),
installAlert: {
margin: 16,
},
links: {
display: "flex",
gap: 4,
margin: 16,
flexDirection: "column",
},
link: {
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 16,
gap: 8,
},
authorizedInstalls: (theme) => ({
display: "flex",
gap: 4,
color: theme.palette.text.disabled,
margin: 32,
}),
} satisfies Record<string, Interpolation<Theme>>;
+5 -11
View File
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import { EllipsisVerticalIcon, UserPlusIcon } from "lucide-react";
import { type FC, useState } from "react";
import { useMutation, useQueryClient } from "react-query";
@@ -41,6 +40,7 @@ import {
TableRow,
} from "#/components/Table/Table";
import { isEveryoneGroup } from "#/modules/groups";
import { cn } from "#/utils/cn";
import type { GroupPageOutletContext } from "./GroupPage";
const GroupMembersPage: FC = () => {
@@ -249,7 +249,10 @@ const GroupMemberRow: FC<GroupMemberRowProps> = ({
</TableCell>
<TableCell
width="40%"
css={[styles.status, member.status === "suspended" && styles.suspended]}
className={cn(
"capitalize",
member.status === "suspended" ? "text-content-secondary" : "",
)}
>
<div>{member.status}</div>
<LastSeen at={member.last_seen_at} className="text-xs" />
@@ -279,13 +282,4 @@ const GroupMemberRow: FC<GroupMemberRowProps> = ({
);
};
const styles = {
status: {
textTransform: "capitalize",
},
suspended: (theme) => ({
color: theme.palette.text.secondary,
}),
} satisfies Record<string, Interpolation<Theme>>;
export default GroupMembersPage;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC } from "react";
import type { ApiErrorResponse } from "#/api/errors";
import type { ExternalAuthDevice } from "#/api/typesGenerated";
@@ -36,7 +35,7 @@ const LoginOAuthDevicePageView: FC<LoginOAuthDevicePageViewProps> = ({
<SignInLayout>
<Welcome>You&apos;ve authenticated with GitHub!</Welcome>
<p css={styles.text}>
<p className="m-0 text-center text-base leading-relaxed text-content-secondary">
If you&apos;re not redirected automatically,{" "}
<a href={redirectUrl}>click here</a>.
</p>
@@ -45,13 +44,3 @@ const LoginOAuthDevicePageView: FC<LoginOAuthDevicePageViewProps> = ({
};
export default LoginOAuthDevicePageView;
const styles = {
text: (theme) => ({
fontSize: 16,
color: theme.palette.text.secondary,
textAlign: "center",
lineHeight: "160%",
margin: 0,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import TextField from "@mui/material/TextField";
@@ -324,7 +323,7 @@ const PermissionCheckboxGroup: FC<PermissionCheckboxGroupProps> = ({
return (
<TableRow key={resourceKey}>
<TableCell className="pl-0.5" colSpan={2}>
<li key={resourceKey} css={styles.checkBoxes}>
<li key={resourceKey} className="m-0 list-none">
<Checkbox
size="small"
name={`${resourceKey}`}
@@ -344,10 +343,10 @@ const PermissionCheckboxGroup: FC<PermissionCheckboxGroupProps> = ({
}
/>
{resourceKey}
<ul css={styles.checkBoxes}>
<ul className="m-0 list-none">
{Object.entries(value).map(([actionKey, value]) => (
<li key={actionKey} css={styles.actionItem}>
<span css={styles.actionText}>
<li key={actionKey} className="grid grid-cols-[270px_1fr]">
<span className="text-content-primary">
<Checkbox
size="small"
name={`${resourceKey}:${actionKey}`}
@@ -358,7 +357,7 @@ const PermissionCheckboxGroup: FC<PermissionCheckboxGroupProps> = ({
/>
{actionKey}
</span>
<span css={styles.actionDescription}>{value}</span>
<span className="pt-1.5 text-content-secondary">{value}</span>
</li>
))}
</ul>
@@ -402,22 +401,4 @@ const ShowAllResourcesCheckbox: FC<ShowAllResourcesCheckboxProps> = ({
);
};
const styles = {
checkBoxes: {
margin: 0,
listStyleType: "none",
},
actionText: (theme) => ({
color: theme.palette.text.primary,
}),
actionDescription: (theme) => ({
color: theme.palette.text.secondary,
paddingTop: 6,
}),
actionItem: {
display: "grid",
gridTemplateColumns: "270px 1fr",
},
} satisfies Record<string, Interpolation<Theme>>;
export default CreateEditRolePageView;
@@ -1,4 +1,3 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import Stack from "@mui/material/Stack";
import type { FC } from "react";
import type { Permission } from "#/api/typesGenerated";
@@ -58,7 +57,7 @@ const PermissionsPill: FC<PermissionPillProps> = ({
);
return (
<Pill css={styles.permissionPill}>
<Pill type="muted" className="w-fit">
<b>{resource}</b>:{" "}
{actions.map((p) => `${p.negate ? "!" : ""}${p.action}`).join(", ")}
</Pill>
@@ -74,16 +73,12 @@ const OverflowPermissionPill: FC<OverflowPermissionPillProps> = ({
resources,
permissions,
}) => {
const theme = useTheme();
return (
<Tooltip>
<TooltipTrigger asChild>
<Pill
css={{
backgroundColor: theme.palette.background.paper,
borderColor: theme.palette.divider,
}}
type="muted"
className="w-fit"
data-testid="overflow-permissions-pill"
>
+{resources.length} more
@@ -102,12 +97,3 @@ const OverflowPermissionPill: FC<OverflowPermissionPillProps> = ({
</Tooltip>
);
};
const styles = {
permissionPill: (theme) => ({
backgroundColor: theme.experimental.pillDefault.background,
borderColor: theme.experimental.pillDefault.outline,
color: theme.experimental.pillDefault.text,
width: "fit-content",
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,9 +1,8 @@
import type { Interpolation, Theme } from "@emotion/react";
import type { FC, HTMLAttributes, ReactNode } from "react";
export const HorizontalContainer: FC<HTMLAttributes<HTMLDivElement>> = ({
...attrs
}) => {
return <div css={styles.horizontalContainer} {...attrs} />;
return <div className="flex flex-col gap-16 md:gap-20" {...attrs} />;
};
interface HorizontalSectionProps
@@ -20,68 +19,17 @@ export const HorizontalSection: FC<HorizontalSectionProps> = ({
...attrs
}) => {
return (
<section css={styles.formSection} {...attrs}>
<div css={styles.formSectionInfo}>
<h2 css={styles.formSectionInfoTitle}>{title}</h2>
<div css={styles.formSectionInfoDescription}>{description}</div>
<section className="flex flex-col gap-4 lg:flex-row lg:gap-32" {...attrs}>
<div className="w-full shrink-0 lg:sticky lg:top-6 lg:max-w-[312px]">
<h2 className="m-0 mb-2 flex flex-row items-center gap-3 text-xl font-normal text-content-primary">
{title}
</h2>
<div className="m-0 text-sm leading-[160%] text-content-secondary">
{description}
</div>
</div>
{children}
</section>
);
};
const styles = {
horizontalContainer: (theme) => ({
display: "flex",
flexDirection: "column",
gap: 80,
[theme.breakpoints.down("md")]: {
gap: 64,
},
}),
formSection: (theme) => ({
display: "flex",
flexDirection: "row",
gap: 120,
[theme.breakpoints.down("lg")]: {
flexDirection: "column",
gap: 16,
},
}),
formSectionInfo: (theme) => ({
width: "100%",
flexShrink: 0,
top: 24,
maxWidth: 312,
position: "sticky",
[theme.breakpoints.down("md")]: {
width: "100%",
position: "initial",
},
}),
formSectionInfoTitle: (theme) => ({
fontSize: 20,
color: theme.palette.text.primary,
fontWeight: 400,
margin: 0,
marginBottom: 8,
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 12,
}),
formSectionInfoDescription: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
lineHeight: "160%",
margin: 0,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Stack from "@mui/material/Stack";
import type { FC } from "react";
import { Pill } from "#/components/Pill/Pill";
@@ -17,7 +16,7 @@ export const IdpPillList: FC<PillListProps> = ({ roles }) => {
return (
<Stack direction="row" spacing={1}>
{roles.length > 0 ? (
<Pill css={isUUID(roles[0]) ? styles.errorPill : styles.pill}>
<Pill className="w-fit" type={isUUID(roles[0]) ? "error" : "muted"}>
{roles[0]}
</Pill>
) : (
@@ -37,14 +36,16 @@ const OverflowPill: FC<OverflowPillProps> = ({ roles }) => {
return (
<Tooltip>
<TooltipTrigger asChild>
<Pill data-testid="overflow-pill">+{roles.length} more</Pill>
<Pill type="muted" className="w-fit" data-testid="overflow-pill">
+{roles.length} more
</Pill>
</TooltipTrigger>
<TooltipContent className="px-4 py-3 border-surface-quaternary">
<ul className="flex flex-col gap-2 list-none my-0 pl-0">
{roles.map((role) => (
<li key={role}>
<Pill css={isUUID(role) ? styles.errorPill : styles.pill}>
<Pill className="w-fit" type={isUUID(role) ? "error" : "muted"}>
{role}
</Pill>
</li>
@@ -54,18 +55,3 @@ const OverflowPill: FC<OverflowPillProps> = ({ roles }) => {
</Tooltip>
);
};
const styles = {
pill: (theme) => ({
backgroundColor: theme.experimental.pillDefault.background,
borderColor: theme.experimental.pillDefault.outline,
color: theme.experimental.pillDefault.text,
width: "fit-content",
}),
errorPill: (theme) => ({
backgroundColor: theme.roles.error.background,
borderColor: theme.roles.error.outline,
color: theme.roles.error.text,
width: "fit-content",
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,4 +1,3 @@
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
import type { FC } from "react";
import { useNavigate } from "react-router";
import type { TemplateVersion } from "#/api/typesGenerated";
@@ -34,25 +33,15 @@ export const VersionRow: FC<VersionRowProps> = ({
const jobStatus = version.job.status;
return (
<TimelineEntry
data-testid={`version-${version.id}`}
{...clickableProps}
className={clickableProps.className}
>
<TableCell css={styles.versionCell}>
<div
className="flex flex-row items-center justify-between gap-4"
css={styles.versionWrapper}
>
<TimelineEntry data-testid={`version-${version.id}`} {...clickableProps}>
<TableCell className="relative border-b-0 !p-0">
<div className="flex flex-row items-center justify-between gap-4 px-8 py-4">
<div className="flex flex-row items-center gap-4">
<Avatar
fallback={version.created_by.username}
src={version.created_by.avatar_url}
/>
<div
className="flex flex-row items-center gap-2"
css={styles.versionSummary}
>
<div className="flex flex-row items-center gap-2 font-inherit text-base font-normal leading-normal">
<span>
<strong>{version.created_by.username}</strong> created the
version <strong>{version.name}</strong>
@@ -60,7 +49,7 @@ export const VersionRow: FC<VersionRowProps> = ({
{version.message && (
<InfoTooltip title="Message" message={version.message} />
)}
<span css={styles.versionTime}>
<span className="text-xs text-content-secondary">
{new Date(version.created_at).toLocaleTimeString()}
</span>
</div>
@@ -130,25 +119,3 @@ export const VersionRow: FC<VersionRowProps> = ({
</TimelineEntry>
);
};
const styles = {
versionWrapper: {
padding: "16px 32px",
},
versionCell: {
padding: "0 !important",
position: "relative",
borderBottom: 0,
},
versionSummary: (theme) => ({
...(theme.typography.body1 as CSSObject),
fontFamily: "inherit",
}),
versionTime: (theme) => ({
color: theme.palette.text.secondary,
fontSize: 12,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,5 +1,3 @@
import { css } from "@emotion/css";
import type { Interpolation, Theme } from "@emotion/react";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
@@ -47,16 +45,16 @@ export const MissingTemplateVariablesDialog: FC<
>
<DialogTitle
id="update-build-parameters-title"
classes={{ root: classNames.root }}
className="px-10 py-6 text-xl font-normal"
>
Template variables
</DialogTitle>
<DialogContent css={styles.content}>
<DialogContentText css={styles.info}>
<DialogContent className="px-10">
<DialogContentText className="m-0">
There are a few missing template variable values. Please fill them in.
</DialogContentText>
<VerticalForm
css={styles.form}
className="pt-8"
id="updateVariables"
onSubmit={(e) => {
e.preventDefault();
@@ -89,7 +87,7 @@ export const MissingTemplateVariablesDialog: FC<
)}
</VerticalForm>
</DialogContent>
<DialogActions disableSpacing css={styles.dialogActions}>
<DialogActions disableSpacing className="flex flex-col gap-2 p-10">
<Button className="w-full" type="submit" form="updateVariables">
Submit
</Button>
@@ -105,34 +103,3 @@ export const MissingTemplateVariablesDialog: FC<
</Dialog>
);
};
const classNames = {
root: css`
padding: 24px 40px;
& h2 {
font-size: 20px;
font-weight: 400;
}
`,
};
const styles = {
content: {
padding: "0 40px",
},
info: {
margin: 0,
},
form: {
paddingTop: 32,
},
dialogActions: {
padding: 40,
flexDirection: "column",
gap: 8,
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -45,7 +45,10 @@ export const ProvisionerTagsPopover: FC<ProvisionerTagsPopoverProps> = ({
>
<FormSection
classes={{
root: "flex flex-col gap-4",
// Override lg:gap-6 from FormSection defaults. The
// lg:flex-col counters the default FormContext
// direction ("horizontal") which adds lg:flex-row.
root: "flex-col lg:flex-col gap-4 lg:gap-4",
}}
title="Provisioner Tags"
description={
@@ -1,4 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import { ArrowRightIcon, PlusIcon } from "lucide-react";
import type { FC } from "react";
import { Link as RouterLink, useNavigate } from "react-router";
@@ -41,6 +40,7 @@ import {
import { useClickableTableRow } from "#/hooks/useClickableTableRow";
import { linkToTemplate, useLinks } from "#/modules/navigation";
import type { WorkspacePermissions } from "#/modules/permissions/workspaces";
import { cn } from "#/utils/cn";
import { createDayString } from "#/utils/createDayString";
import { docs } from "#/utils/docs";
import {
@@ -101,6 +101,7 @@ const TemplateActions: FC<TemplateActionsProps> = ({
asChild
variant="outline"
size="sm"
className="transition-none group-hover:border-border-secondary"
title={`Create a workspace using the ${template.display_name} template`}
onClick={(e) => {
e.stopPropagation();
@@ -142,7 +143,7 @@ const TemplateRow: FC<TemplateRowProps> = ({
key={template.id}
data-testid={`template-${template.id}`}
{...clickableRow}
css={styles.tableRow}
className={cn("group", clickableRow.className)}
>
<TableCell>
<AvatarData
@@ -159,7 +160,7 @@ const TemplateRow: FC<TemplateRowProps> = ({
/>
</TableCell>
<TableCell css={styles.secondary}>
<TableCell className="text-content-secondary">
{showOrganizations ? (
<AvatarData
title={template.organization_display_name}
@@ -171,15 +172,15 @@ const TemplateRow: FC<TemplateRowProps> = ({
)}
</TableCell>
<TableCell css={styles.secondary}>
<TableCell className="text-content-secondary">
{formatTemplateBuildTime(template.build_time_stats.start.P50)}
</TableCell>
<TableCell data-chromatic="ignore" css={styles.secondary}>
<TableCell data-chromatic="ignore" className="text-content-secondary">
{createDayString(template.updated_at)}
</TableCell>
<TableCell css={styles.actionCell}>
<TableCell className="whitespace-nowrap">
<TemplateActions
template={template}
workspacePermissions={workspacePermissions}
@@ -307,41 +308,3 @@ const TableLoader: FC = () => {
</TableLoaderSkeleton>
);
};
const styles = {
templateIconWrapper: {
// Same size then the avatar component
width: 36,
height: 36,
padding: 2,
"& img": {
width: "100%",
},
},
actionCell: {
whiteSpace: "nowrap",
},
cellPrimaryLine: (theme) => ({
color: theme.palette.text.primary,
fontWeight: 600,
}),
cellSecondaryLine: (theme) => ({
fontSize: 13,
color: theme.palette.text.secondary,
lineHeight: "150%",
}),
secondary: (theme) => ({
color: theme.palette.text.secondary,
}),
tableRow: (theme) => ({
"&:hover .actionButton": {
color: theme.experimental.l2.hover.text,
borderColor: theme.experimental.l2.hover.outline,
},
}),
actionButton: (theme) => ({
transition: "none",
color: theme.palette.text.primary,
}),
} satisfies Record<string, Interpolation<Theme>>;