mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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>>;
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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:
|
||||
<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>
|
||||
{" "}
|
||||
<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",
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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">
|
||||
© {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>>;
|
||||
|
||||
+2
-15
@@ -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>
|
||||
|
||||
+2
-15
@@ -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'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>>;
|
||||
|
||||
@@ -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've authenticated with GitHub!</Welcome>
|
||||
|
||||
<p css={styles.text}>
|
||||
<p className="m-0 text-center text-base leading-relaxed text-content-secondary">
|
||||
If you'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>>;
|
||||
|
||||
Reference in New Issue
Block a user