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