mirror of
https://github.com/coder/coder.git
synced 2026-10-29 17:44:33 +08:00
refactor(site): migrate more styles from emotion to tailwind (#24914)
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import { type CSSObject, useTheme } from "@emotion/react";
|
|
||||||
import type { FC, ReactNode } from "react";
|
import type { FC, ReactNode } from "react";
|
||||||
import { Avatar } from "#/components/Avatar/Avatar";
|
import { Avatar } from "#/components/Avatar/Avatar";
|
||||||
import { cn } from "#/utils/cn";
|
import { cn } from "#/utils/cn";
|
||||||
@@ -16,8 +15,6 @@ export const AvatarCard: FC<AvatarCardProps> = ({
|
|||||||
subtitle,
|
subtitle,
|
||||||
maxWidth = "none",
|
maxWidth = "none",
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -44,12 +41,7 @@ export const AvatarCard: FC<AvatarCardProps> = ({
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<div
|
<div className="text-sm leading-relaxed text-content-secondary">
|
||||||
css={[
|
|
||||||
theme.typography.body2 as CSSObject,
|
|
||||||
{ color: theme.palette.text.secondary },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import { type FC, type FormEvent, useId, useState } from "react";
|
import { useId, useState } from "react";
|
||||||
import { Alert } from "#/components/Alert/Alert";
|
import { Alert } from "#/components/Alert/Alert";
|
||||||
import { ConfirmDialog } from "../ConfirmDialog/ConfirmDialog";
|
import { ConfirmDialog } from "../ConfirmDialog/ConfirmDialog";
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ interface DeleteDialogProps {
|
|||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteDialog: FC<DeleteDialogProps> = ({
|
export const DeleteDialog: React.FC<DeleteDialogProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onCancel,
|
onCancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
@@ -37,7 +37,7 @@ export const DeleteDialog: FC<DeleteDialogProps> = ({
|
|||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
const deletionConfirmed = name === userConfirmationText;
|
const deletionConfirmed = name === userConfirmationText;
|
||||||
const onSubmit = (event: FormEvent) => {
|
const onSubmit = (event: React.SubmitEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (deletionConfirmed) {
|
if (deletionConfirmed) {
|
||||||
onConfirm();
|
onConfirm();
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import {
|
import {
|
||||||
cloneElement,
|
cloneElement,
|
||||||
type FC,
|
type FC,
|
||||||
@@ -51,10 +50,9 @@ export const TopbarData: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
|||||||
|
|
||||||
export const TopbarDivider: FC<
|
export const TopbarDivider: FC<
|
||||||
Omit<HTMLAttributes<HTMLSpanElement>, "children">
|
Omit<HTMLAttributes<HTMLSpanElement>, "children">
|
||||||
> = (props) => {
|
> = ({ className, ...props }) => {
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<span {...props} css={{ color: theme.palette.divider }}>
|
<span {...props} className={cn("text-border", className)}>
|
||||||
/
|
/
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
|
|||||||
import LinearProgress from "./LinearProgress";
|
import LinearProgress from "./LinearProgress";
|
||||||
|
|
||||||
const meta: Meta<typeof LinearProgress> = {
|
const meta: Meta<typeof LinearProgress> = {
|
||||||
title: "Components/LinearProgress",
|
title: "components/LinearProgress",
|
||||||
component: LinearProgress,
|
component: LinearProgress,
|
||||||
args: {
|
args: {
|
||||||
variant: "determinate",
|
variant: "determinate",
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const InlineMarkdown: FC<InlineMarkdownProps> = (props) => {
|
|||||||
|
|
||||||
code: ({ node, className, children, style, ...props }) => (
|
code: ({ node, className, children, style, ...props }) => (
|
||||||
<code
|
<code
|
||||||
className="rounded-sm bg-border px-1 py-px text-[14px] text-content-primary"
|
className="rounded-sm bg-border px-1 py-px text-sm text-content-primary"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const Markdown: FC<MarkdownProps> = (props) => {
|
|||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
) : (
|
) : (
|
||||||
<code
|
<code
|
||||||
className="rounded-sm bg-border px-1 py-px text-[14px] text-content-primary"
|
className="rounded-sm bg-border px-1 py-px text-sm text-content-primary"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import { Building2Icon, UserIcon } from "lucide-react";
|
import { Building2Icon, UserIcon } from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import type { HealthMessage, ProvisionerDaemon } from "#/api/typesGenerated";
|
import type { HealthMessage, ProvisionerDaemon } from "#/api/typesGenerated";
|
||||||
@@ -8,6 +7,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "#/components/Tooltip/Tooltip";
|
} from "#/components/Tooltip/Tooltip";
|
||||||
|
import { cn } from "#/utils/cn";
|
||||||
import { createDayString } from "#/utils/createDayString";
|
import { createDayString } from "#/utils/createDayString";
|
||||||
import { ProvisionerTag } from "./ProvisionerTag";
|
import { ProvisionerTag } from "./ProvisionerTag";
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@ export const Provisioner: FC<ProvisionerProps> = ({
|
|||||||
provisioner,
|
provisioner,
|
||||||
warnings,
|
warnings,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
|
||||||
const daemonScope = provisioner.tags.scope || "organization";
|
const daemonScope = provisioner.tags.scope || "organization";
|
||||||
const iconScope =
|
const iconScope =
|
||||||
daemonScope === "organization" ? (
|
daemonScope === "organization" ? (
|
||||||
@@ -36,20 +35,16 @@ export const Provisioner: FC<ProvisionerProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={provisioner.name}
|
key={provisioner.name}
|
||||||
css={[
|
className={cn(
|
||||||
{
|
"rounded-lg border border-solid border-border text-sm",
|
||||||
borderRadius: 8,
|
isWarning && "border-border-warning",
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
)}
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
isWarning && { borderColor: theme.palette.warning.light },
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<header className="p-6 flex items-center justify-between gap-6">
|
<header className="p-6 flex items-center justify-between gap-6">
|
||||||
<div className="flex items-center gap-6 object-fill">
|
<div className="flex items-center gap-6 object-fill">
|
||||||
<div className="leading-[160%]">
|
<div className="leading-relaxed">
|
||||||
<h4 className="font-medium m-0">{provisioner.name}</h4>
|
<h4 className="font-medium m-0">{provisioner.name}</h4>
|
||||||
<span css={{ color: theme.palette.text.secondary }}>
|
<span className="text-content-secondary">
|
||||||
<code>{provisioner.version}</code>
|
<code>{provisioner.version}</code>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,17 +66,7 @@ export const Provisioner: FC<ProvisionerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div
|
<div className="border-solid border-0 border-t border-border flex items-center justify-between py-3 px-6 text-xs text-content-secondary">
|
||||||
css={{
|
|
||||||
borderTop: `1px solid ${theme.palette.divider}`,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
padding: "8px 24px",
|
|
||||||
fontSize: 12,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{warnings && warnings.length > 0 ? (
|
{warnings && warnings.length > 0 ? (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{warnings.map((warning) => (
|
{warnings.map((warning) => (
|
||||||
@@ -92,7 +77,7 @@ export const Provisioner: FC<ProvisionerProps> = ({
|
|||||||
<span>No warnings</span>
|
<span>No warnings</span>
|
||||||
)}
|
)}
|
||||||
{provisioner.last_seen_at && (
|
{provisioner.last_seen_at && (
|
||||||
<span css={{ color: theme.roles.info.text }} data-chromatic="ignore">
|
<span className="text-content-primary" data-chromatic="ignore">
|
||||||
Last seen {createDayString(provisioner.last_seen_at)}
|
Last seen {createDayString(provisioner.last_seen_at)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+1
-14
@@ -1,4 +1,3 @@
|
|||||||
import { css } from "@emotion/react";
|
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import type {
|
import type {
|
||||||
DeploymentValues,
|
DeploymentValues,
|
||||||
@@ -60,19 +59,7 @@ export const ExternalAuthSettingsPageView: FC<
|
|||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table className="[&_td]:py-6 [&_td:last-child]:pl-8 [&_th:last-child]:pl-8">
|
||||||
css={css`
|
|
||||||
& td {
|
|
||||||
padding-top: 24px;
|
|
||||||
padding-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& td:last-child,
|
|
||||||
& th:last-child {
|
|
||||||
padding-left: 32px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-1/3">ID</TableHead>
|
<TableHead className="w-1/3">ID</TableHead>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useMutation } from "react-query";
|
import { useMutation } from "react-query";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -23,17 +22,9 @@ export const Troubleshooting: FC<TroubleshootingProps> = ({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div className="text-sm text-content-secondary leading-relaxed mb-4">
|
||||||
css={{
|
|
||||||
fontSize: 14,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
lineHeight: "160%",
|
|
||||||
marginBottom: 16,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Send a test notification to troubleshoot your notification settings.
|
Send a test notification to troubleshoot your notification settings.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import {
|
import {
|
||||||
CircleAlertIcon,
|
CircleAlertIcon,
|
||||||
CircleCheckIcon,
|
CircleCheckIcon,
|
||||||
@@ -14,27 +13,43 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import type { HealthCode, HealthSeverity } from "#/api/typesGenerated";
|
import type { HealthCode, HealthSeverity } from "#/api/typesGenerated";
|
||||||
import { Link } from "#/components/Link/Link";
|
import { Link } from "#/components/Link/Link";
|
||||||
|
import { cn } from "#/utils/cn";
|
||||||
import { docs } from "#/utils/docs";
|
import { docs } from "#/utils/docs";
|
||||||
import { healthyColor } from "./healthyColor";
|
|
||||||
|
|
||||||
const CONTENT_PADDING = 36;
|
const CONTENT_PADDING = 36;
|
||||||
|
|
||||||
export const Header: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
export const Header: FC<HTMLAttributes<HTMLDivElement>> = ({
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className="flex items-center justify-between"
|
className={cn("flex items-center justify-between", className)}
|
||||||
style={{ padding: `36px ${CONTENT_PADDING}px` }}
|
style={{ padding: `36px ${CONTENT_PADDING}px`, ...style }}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{children}
|
||||||
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HeaderTitle: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
export const HeaderTitle: FC<HTMLAttributes<HTMLDivElement>> = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<h2
|
<h2
|
||||||
className="m-0 leading-[1.2] text-xl font-medium flex items-center gap-4"
|
className={cn(
|
||||||
|
"m-0 leading-[1.2] text-xl font-medium flex items-center gap-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{children}
|
||||||
|
</h2>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,11 +59,18 @@ interface HealthIconProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const HealthIcon: FC<HealthIconProps> = ({ size, severity }) => {
|
export const HealthIcon: FC<HealthIconProps> = ({ size, severity }) => {
|
||||||
const theme = useTheme();
|
|
||||||
const color = healthyColor(theme, severity);
|
|
||||||
const Icon = severity === "error" ? CircleAlertIcon : CircleCheckIcon;
|
const Icon = severity === "error" ? CircleAlertIcon : CircleCheckIcon;
|
||||||
|
|
||||||
return <Icon css={{ width: size, height: size, color }} />;
|
return (
|
||||||
|
<Icon
|
||||||
|
className={cn(
|
||||||
|
severity === "ok" && "text-content-success",
|
||||||
|
severity === "warning" && "text-content-warning",
|
||||||
|
severity === "error" && "text-content-destructive",
|
||||||
|
)}
|
||||||
|
style={{ width: size, height: size }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface HealthyDotProps {
|
interface HealthyDotProps {
|
||||||
@@ -56,72 +78,96 @@ interface HealthyDotProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const HealthyDot: FC<HealthyDotProps> = ({ severity }) => {
|
export const HealthyDot: FC<HealthyDotProps> = ({ severity }) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
css={{
|
className={cn(
|
||||||
width: 8,
|
"size-2 rounded-full",
|
||||||
height: 8,
|
severity === "ok" && "bg-content-success",
|
||||||
borderRadius: 9999,
|
severity === "warning" && "bg-content-warning",
|
||||||
backgroundColor: healthyColor(theme, severity),
|
severity === "error" && "bg-content-destructive",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Main: FC<HTMLAttributes<HTMLDivElement>> = ({
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("flex flex-col gap-9", className)}
|
||||||
|
style={{
|
||||||
|
padding: `0 ${CONTENT_PADDING}px ${CONTENT_PADDING}px`,
|
||||||
|
...style,
|
||||||
}}
|
}}
|
||||||
/>
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Main: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
export const GridData: FC<HTMLAttributes<HTMLDivElement>> = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col gap-9"
|
className={cn(
|
||||||
style={{ padding: `0 ${CONTENT_PADDING}px ${CONTENT_PADDING}px` }}
|
"leading-[1.4] w-min whitespace-nowrap",
|
||||||
|
"grid grid-cols-[auto_auto] gap-3 gap-x-12",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridData: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
export const GridDataLabel: FC<HTMLAttributes<HTMLSpanElement>> = ({
|
||||||
return (
|
className,
|
||||||
<div
|
children,
|
||||||
className={`
|
...props
|
||||||
leading-[1.4] w-min whitespace-nowrap
|
}) => {
|
||||||
grid grid-cols-[auto_auto] gap-3 gap-x-12
|
|
||||||
`}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GridDataLabel: FC<HTMLAttributes<HTMLSpanElement>> = (props) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
css={{
|
className={cn("text-sm font-medium text-content-secondary", className)}
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: 500,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
}}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridDataValue: FC<HTMLAttributes<HTMLSpanElement>> = (props) => {
|
export const GridDataValue: FC<HTMLAttributes<HTMLSpanElement>> = ({
|
||||||
const theme = useTheme();
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span className={cn("text-sm text-content-primary", className)} {...props}>
|
||||||
css={{
|
{children}
|
||||||
fontSize: 14,
|
</span>
|
||||||
color: theme.palette.text.primary,
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SectionLabel: FC<HTMLAttributes<HTMLHeadingElement>> = (props) => {
|
export const SectionLabel: FC<HTMLAttributes<HTMLHeadingElement>> = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<h4 {...props} className="text-sm font-medium m-0 leading-[1.2] mb-4" />
|
<h4
|
||||||
|
className={cn("text-sm font-medium m-0 leading-[1.2] mb-4", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</h4>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,23 +175,18 @@ type PillProps = React.ComponentPropsWithRef<"div"> & {
|
|||||||
icon: ReactElement<HTMLAttributes<HTMLElement>>;
|
icon: ReactElement<HTMLAttributes<HTMLElement>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Pill: React.FC<PillProps> = ({ icon, children, ...divProps }) => {
|
export const Pill: React.FC<PillProps> = ({
|
||||||
const theme = useTheme();
|
className,
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
...divProps
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
css={{
|
className={cn(
|
||||||
display: "inline-flex",
|
"inline-flex items-center h-8 rounded-full border border-solid border-border text-xs font-medium p-2 gap-2 cursor-default",
|
||||||
alignItems: "center",
|
className,
|
||||||
height: 32,
|
)}
|
||||||
borderRadius: 9999,
|
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 500,
|
|
||||||
padding: 8,
|
|
||||||
gap: 8,
|
|
||||||
cursor: "default",
|
|
||||||
}}
|
|
||||||
{...divProps}
|
{...divProps}
|
||||||
>
|
>
|
||||||
{cloneElement(icon, { className: "size-[14px]" })}
|
{cloneElement(icon, { className: "size-[14px]" })}
|
||||||
@@ -178,16 +219,13 @@ export const BooleanPill: FC<BooleanPillProps> = ({
|
|||||||
children,
|
children,
|
||||||
...divProps
|
...divProps
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
|
||||||
const color = value ? theme.roles.success.outline : theme.roles.error.outline;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pill
|
<Pill
|
||||||
icon={
|
icon={
|
||||||
value ? (
|
value ? (
|
||||||
<CircleCheckIcon css={{ color }} className="size-icon-sm" />
|
<CircleCheckIcon className="size-icon-sm text-content-success" />
|
||||||
) : (
|
) : (
|
||||||
<CircleMinusIcon css={{ color }} className="size-icon-sm" />
|
<CircleMinusIcon className="size-icon-sm text-content-destructive" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{...divProps}
|
{...divProps}
|
||||||
@@ -199,21 +237,13 @@ export const BooleanPill: FC<BooleanPillProps> = ({
|
|||||||
|
|
||||||
type LogsProps = HTMLAttributes<HTMLDivElement> & { lines: readonly string[] };
|
type LogsProps = HTMLAttributes<HTMLDivElement> & { lines: readonly string[] };
|
||||||
|
|
||||||
export const Logs: FC<LogsProps> = ({ lines, ...divProps }) => {
|
export const Logs: FC<LogsProps> = ({ className, lines, ...divProps }) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
css={{
|
className={cn(
|
||||||
fontFamily: "monospace",
|
"font-mono text-[13px] leading-relaxed p-6 bg-surface-secondary overflow-x-auto whitespace-pre-wrap break-all",
|
||||||
fontSize: 13,
|
className,
|
||||||
lineHeight: "160%",
|
)}
|
||||||
padding: 24,
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
overflowX: "auto",
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
wordBreak: "break-all",
|
|
||||||
}}
|
|
||||||
{...divProps}
|
{...divProps}
|
||||||
>
|
>
|
||||||
{lines.map((line, index) => (
|
{lines.map((line, index) => (
|
||||||
@@ -222,9 +252,7 @@ export const Logs: FC<LogsProps> = ({ lines, ...divProps }) => {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{lines.length === 0 && (
|
{lines.length === 0 && (
|
||||||
<span css={{ color: theme.palette.text.secondary }}>
|
<span className="text-content-secondary">No logs available</span>
|
||||||
No logs available
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import { CodeIcon } from "lucide-react";
|
import { CodeIcon } from "lucide-react";
|
||||||
import { useOutletContext } from "react-router";
|
import { useOutletContext } from "react-router";
|
||||||
import type { HealthcheckReport } from "#/api/typesGenerated";
|
import type { HealthcheckReport } from "#/api/typesGenerated";
|
||||||
@@ -8,7 +7,6 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "#/components/Tooltip/Tooltip";
|
} from "#/components/Tooltip/Tooltip";
|
||||||
import { MONOSPACE_FONT_FAMILY } from "#/theme/constants";
|
|
||||||
import { pageTitle } from "#/utils/page";
|
import { pageTitle } from "#/utils/page";
|
||||||
import {
|
import {
|
||||||
Header,
|
Header,
|
||||||
@@ -23,7 +21,6 @@ import { DismissWarningButton } from "./DismissWarningButton";
|
|||||||
const WebsocketPage = () => {
|
const WebsocketPage = () => {
|
||||||
const healthStatus = useOutletContext<HealthcheckReport>();
|
const healthStatus = useOutletContext<HealthcheckReport>();
|
||||||
const { websocket } = healthStatus;
|
const { websocket } = healthStatus;
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -65,22 +62,11 @@ const WebsocketPage = () => {
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<SectionLabel>Body</SectionLabel>
|
<SectionLabel>Body</SectionLabel>
|
||||||
<div
|
<div className="bg-surface-secondary border border-solid border-border rounded-lg text-sm p-6 font-mono">
|
||||||
css={{
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
borderRadius: 8,
|
|
||||||
fontSize: 14,
|
|
||||||
padding: 24,
|
|
||||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{websocket.body !== "" ? (
|
{websocket.body !== "" ? (
|
||||||
websocket.body
|
websocket.body
|
||||||
) : (
|
) : (
|
||||||
<span css={{ color: theme.palette.text.secondary }}>
|
<span className="text-content-secondary">No body message</span>
|
||||||
No body message
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import { GlobeIcon, HashIcon } from "lucide-react";
|
import { GlobeIcon, HashIcon } from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useOutletContext } from "react-router";
|
import { useOutletContext } from "react-router";
|
||||||
@@ -9,6 +8,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "#/components/Tooltip/Tooltip";
|
} from "#/components/Tooltip/Tooltip";
|
||||||
|
import { cn } from "#/utils/cn";
|
||||||
import { createDayString } from "#/utils/createDayString";
|
import { createDayString } from "#/utils/createDayString";
|
||||||
import { pageTitle } from "#/utils/page";
|
import { pageTitle } from "#/utils/page";
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +26,6 @@ const WorkspaceProxyPage: FC = () => {
|
|||||||
const healthStatus = useOutletContext<HealthcheckReport>();
|
const healthStatus = useOutletContext<HealthcheckReport>();
|
||||||
const { workspace_proxy } = healthStatus;
|
const { workspace_proxy } = healthStatus;
|
||||||
const { regions } = workspace_proxy.workspace_proxies;
|
const { regions } = workspace_proxy.workspace_proxies;
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -66,15 +65,10 @@ const WorkspaceProxyPage: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={region.id}
|
key={region.id}
|
||||||
css={{
|
className={cn(
|
||||||
borderRadius: 8,
|
"rounded-lg border border-solid text-sm",
|
||||||
border: `1px solid ${
|
region.healthy ? "border-border" : "border-border-warning",
|
||||||
region.healthy
|
)}
|
||||||
? theme.palette.divider
|
|
||||||
: theme.palette.warning.light
|
|
||||||
}`,
|
|
||||||
fontSize: 14,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<header className="p-6 flex items-center justify-between gap-6">
|
<header className="p-6 flex items-center justify-between gap-6">
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
@@ -85,9 +79,9 @@ const WorkspaceProxyPage: FC = () => {
|
|||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="leading-[160%]">
|
<div className="leading-relaxed">
|
||||||
<h4 className="font-medium m-0">{region.display_name}</h4>
|
<h4 className="font-medium m-0">{region.display_name}</h4>
|
||||||
<span css={{ color: theme.palette.text.secondary }}>
|
<span className="text-content-secondary">
|
||||||
{region.version}
|
{region.version}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,17 +126,7 @@ const WorkspaceProxyPage: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div
|
<div className="border-0 border-t border-solid border-border flex items-center justify-between py-3 px-6 text-xs text-content-secondary">
|
||||||
css={{
|
|
||||||
borderTop: `1px solid ${theme.palette.divider}`,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
padding: "8px 24px",
|
|
||||||
fontSize: 12,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{region.status?.status === "unregistered" ? (
|
{region.status?.status === "unregistered" ? (
|
||||||
<span>Has not connected yet</span>
|
<span>Has not connected yet</span>
|
||||||
) : warnings.length === 0 && errors.length === 0 ? (
|
) : warnings.length === 0 && errors.length === 0 ? (
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import { ExternalLinkIcon, PlusIcon } from "lucide-react";
|
import { ExternalLinkIcon, PlusIcon } from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
@@ -24,8 +23,6 @@ export const StarterTemplatePageView: FC<StarterTemplatePageViewProps> = ({
|
|||||||
starterTemplate,
|
starterTemplate,
|
||||||
error,
|
error,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Margins>
|
<Margins>
|
||||||
@@ -72,11 +69,7 @@ export const StarterTemplatePageView: FC<StarterTemplatePageViewProps> = ({
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
css={{
|
className="bg-surface-secondary border border-solid border-border rounded-lg"
|
||||||
background: theme.palette.background.paper,
|
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
borderRadius: 8,
|
|
||||||
}}
|
|
||||||
id="readme"
|
id="readme"
|
||||||
>
|
>
|
||||||
<div className="px-10 pt-10 pb-16 max-w-[800px] mx-auto">
|
<div className="px-10 pt-10 pb-16 max-w-[800px] mx-auto">
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ const UsersLatencyPanel: FC<UsersLatencyPanelProps> = ({
|
|||||||
.map((row) => (
|
.map((row) => (
|
||||||
<div
|
<div
|
||||||
key={row.user_id}
|
key={row.user_id}
|
||||||
className="flex justify-between items-center text-[14px] py-2"
|
className="flex justify-between items-center text-sm py-2"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar fallback={row.username} src={row.avatar_url} />
|
<Avatar fallback={row.username} src={row.avatar_url} />
|
||||||
@@ -392,7 +392,7 @@ const UsersActivityPanel: FC<UsersActivityPanelProps> = ({
|
|||||||
.map((row) => (
|
.map((row) => (
|
||||||
<div
|
<div
|
||||||
key={row.user_id}
|
key={row.user_id}
|
||||||
className="flex justify-between items-center text-[14px] py-2"
|
className="flex justify-between items-center text-sm py-2"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar fallback={row.username} src={row.avatar_url} />
|
<Avatar fallback={row.username} src={row.avatar_url} />
|
||||||
@@ -527,7 +527,7 @@ const TemplateParametersUsagePanel: FC<TemplateParametersUsagePanelProps> = ({
|
|||||||
>
|
>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="font-medium">{label}</div>
|
<div className="font-medium">{label}</div>
|
||||||
<p className="text-[14px] m-0 text-content-secondary max-w-[400px]">
|
<p className="text-sm m-0 text-content-secondary max-w-[400px]">
|
||||||
{parameter.description}
|
{parameter.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -706,7 +706,7 @@ const PanelTitle: FC<HTMLAttributes<HTMLDivElement>> = ({
|
|||||||
...attrs
|
...attrs
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div {...attrs} className={cn("text-[14px] font-medium", className)}>
|
<div {...attrs} className={cn("text-sm font-medium", className)}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -192,16 +192,8 @@ const TerminalPage: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{latency && isDebugging && (
|
{latency && isDebugging && (
|
||||||
<span
|
<span className="absolute bottom-6 right-6 text-content-disabled text-sm">
|
||||||
css={{
|
Latency: {latency.latencyMS.toFixed(0)}ms{" "}
|
||||||
position: "absolute",
|
|
||||||
bottom: 24,
|
|
||||||
right: 24,
|
|
||||||
color: theme.palette.text.disabled,
|
|
||||||
fontSize: 14,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Latency: {latency.latencyMS.toFixed(0)}ms
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import { type FC, type ReactNode, useState } from "react";
|
import { type FC, type ReactNode, useState } from "react";
|
||||||
import type { AlertProps } from "#/components/Alert/Alert";
|
import type { AlertProps } from "#/components/Alert/Alert";
|
||||||
import { Button, type ButtonProps } from "#/components/Button/Button";
|
import { Button, type ButtonProps } from "#/components/Button/Button";
|
||||||
@@ -9,6 +8,7 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "#/components/Popover/Popover";
|
} from "#/components/Popover/Popover";
|
||||||
import type { ThemeRole } from "#/theme/roles";
|
import type { ThemeRole } from "#/theme/roles";
|
||||||
|
import { cn } from "#/utils/cn";
|
||||||
|
|
||||||
export type NotificationItem = {
|
export type NotificationItem = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -23,13 +23,55 @@ type NotificationsProps = {
|
|||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Maps a ThemeRole severity to Tailwind classes for the role's outline
|
||||||
|
// color. These are the closest semantic matches available in the design
|
||||||
|
// token system.
|
||||||
|
const severityStyles: Record<ThemeRole, { svgColor: string; border: string }> =
|
||||||
|
{
|
||||||
|
error: {
|
||||||
|
svgColor: "[&_svg]:text-border-destructive",
|
||||||
|
border: "border-border-destructive",
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
svgColor: "[&_svg]:text-border-warning",
|
||||||
|
border: "border-border-warning",
|
||||||
|
},
|
||||||
|
notice: {
|
||||||
|
svgColor: "[&_svg]:text-border-pending",
|
||||||
|
border: "border-border-pending",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
svgColor: "[&_svg]:text-content-secondary",
|
||||||
|
border: "border-border",
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
svgColor: "[&_svg]:text-border-success",
|
||||||
|
border: "border-border-success",
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
svgColor: "[&_svg]:text-border-pending",
|
||||||
|
border: "border-border-pending",
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
svgColor: "[&_svg]:text-content-disabled",
|
||||||
|
border: "border-border",
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
svgColor: "[&_svg]:text-border-warning",
|
||||||
|
border: "border-border-warning",
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
svgColor: "[&_svg]:text-border-purple",
|
||||||
|
border: "border-border-purple",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const Notifications: FC<NotificationsProps> = ({
|
export const Notifications: FC<NotificationsProps> = ({
|
||||||
items,
|
items,
|
||||||
severity,
|
severity,
|
||||||
icon,
|
icon,
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||||
@@ -50,10 +92,10 @@ export const Notifications: FC<NotificationsProps> = ({
|
|||||||
<PopoverContent
|
<PopoverContent
|
||||||
align="end"
|
align="end"
|
||||||
collisionPadding={16}
|
collisionPadding={16}
|
||||||
className="max-w-[400px] p-0 w-auto bg-surface-secondary border-surface-quaternary text-sm text-content-primary"
|
className={cn(
|
||||||
style={{
|
"max-w-[400px] p-0 w-auto bg-surface-secondary text-sm text-content-primary",
|
||||||
borderColor: theme.roles[severity].outline,
|
severityStyles[severity].border,
|
||||||
}}
|
)}
|
||||||
>
|
>
|
||||||
{items.map((n) => (
|
{items.map((n) => (
|
||||||
<NotificationItem notification={n} key={n.title} />
|
<NotificationItem notification={n} key={n.title} />
|
||||||
@@ -76,10 +118,10 @@ const NotificationPill: FC<NotificationPillProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Pill
|
<Pill
|
||||||
icon={icon}
|
icon={icon}
|
||||||
css={(theme) => ({
|
className={cn(
|
||||||
"& svg": { color: theme.roles[severity].outline },
|
severityStyles[severity].svgColor,
|
||||||
borderColor: isOpen ? theme.roles[severity].outline : undefined,
|
isOpen && severityStyles[severity].border,
|
||||||
})}
|
)}
|
||||||
>
|
>
|
||||||
{items.length}
|
{items.length}
|
||||||
</Pill>
|
</Pill>
|
||||||
|
|||||||
@@ -196,10 +196,7 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({
|
|||||||
|
|
||||||
{pageNumberIsInvalid ? (
|
{pageNumberIsInvalid ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
css={(theme) => ({
|
className="border border-solid border-border rounded-lg"
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
})}
|
|
||||||
message="Page not found"
|
message="Page not found"
|
||||||
description="The page you are trying to access does not exist."
|
description="The page you are trying to access does not exist."
|
||||||
cta={
|
cta={
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export const WorkspacesTable: FC<WorkspacesTableProps> = ({
|
|||||||
/>
|
/>
|
||||||
<AvatarData
|
<AvatarData
|
||||||
title={
|
title={
|
||||||
<div className="flex items-center gap-0.5">
|
<div className="flex items-center gap-1">
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
{workspace.name}
|
{workspace.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user