From 9fd2cc78fe1a4a360d4d47de30e445aa4bf1d073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kayla=20=E3=81=AF=E3=81=AA?= Date: Thu, 7 May 2026 11:09:25 -0600 Subject: [PATCH] refactor(site): migrate more styles from emotion to tailwind (#24914) --- site/src/components/Avatar/AvatarCard.tsx | 10 +- .../Dialogs/DeleteDialog/DeleteDialog.tsx | 6 +- site/src/components/FullPageLayout/Topbar.tsx | 6 +- .../LinearProgress/LinearProgress.stories.tsx | 2 +- .../components/Markdown/InlineMarkdown.tsx | 2 +- site/src/components/Markdown/Markdown.tsx | 2 +- site/src/modules/provisioners/Provisioner.tsx | 33 +-- .../ExternalAuthSettingsPageView.tsx | 15 +- .../NotificationsPage/Troubleshooting.tsx | 11 +- site/src/pages/HealthPage/Content.tsx | 210 ++++++++++-------- site/src/pages/HealthPage/WebsocketPage.tsx | 18 +- .../pages/HealthPage/WorkspaceProxyPage.tsx | 32 +-- .../StarterTemplatePageView.tsx | 9 +- .../TemplateInsightsPage.tsx | 8 +- site/src/pages/TerminalPage/TerminalPage.tsx | 12 +- .../WorkspaceNotifications/Notifications.tsx | 62 +++++- .../WorkspacesPage/WorkspacesPageView.tsx | 5 +- .../pages/WorkspacesPage/WorkspacesTable.tsx | 2 +- 18 files changed, 210 insertions(+), 235 deletions(-) diff --git a/site/src/components/Avatar/AvatarCard.tsx b/site/src/components/Avatar/AvatarCard.tsx index bb4e514543..192e6220d7 100644 --- a/site/src/components/Avatar/AvatarCard.tsx +++ b/site/src/components/Avatar/AvatarCard.tsx @@ -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 = ({ subtitle, maxWidth = "none", }) => { - const theme = useTheme(); - return (
= ({ {subtitle && ( -
+
{subtitle}
)} diff --git a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx index d2d49662cf..209aaf0ffe 100644 --- a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx +++ b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx @@ -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 = ({ +export const DeleteDialog: React.FC = ({ isOpen, onCancel, onConfirm, @@ -37,7 +37,7 @@ export const DeleteDialog: FC = ({ const [isFocused, setIsFocused] = useState(false); const deletionConfirmed = name === userConfirmationText; - const onSubmit = (event: FormEvent) => { + const onSubmit = (event: React.SubmitEvent) => { event.preventDefault(); if (deletionConfirmed) { onConfirm(); diff --git a/site/src/components/FullPageLayout/Topbar.tsx b/site/src/components/FullPageLayout/Topbar.tsx index f687051fdd..39fccef4ae 100644 --- a/site/src/components/FullPageLayout/Topbar.tsx +++ b/site/src/components/FullPageLayout/Topbar.tsx @@ -1,4 +1,3 @@ -import { useTheme } from "@emotion/react"; import { cloneElement, type FC, @@ -51,10 +50,9 @@ export const TopbarData: FC> = (props) => { export const TopbarDivider: FC< Omit, "children"> -> = (props) => { - const theme = useTheme(); +> = ({ className, ...props }) => { return ( - + / ); diff --git a/site/src/components/LinearProgress/LinearProgress.stories.tsx b/site/src/components/LinearProgress/LinearProgress.stories.tsx index 426f3cea7b..506d2bbd97 100644 --- a/site/src/components/LinearProgress/LinearProgress.stories.tsx +++ b/site/src/components/LinearProgress/LinearProgress.stories.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import LinearProgress from "./LinearProgress"; const meta: Meta = { - title: "Components/LinearProgress", + title: "components/LinearProgress", component: LinearProgress, args: { variant: "determinate", diff --git a/site/src/components/Markdown/InlineMarkdown.tsx b/site/src/components/Markdown/InlineMarkdown.tsx index 34801ec8d1..0d97dece9f 100644 --- a/site/src/components/Markdown/InlineMarkdown.tsx +++ b/site/src/components/Markdown/InlineMarkdown.tsx @@ -56,7 +56,7 @@ export const InlineMarkdown: FC = (props) => { code: ({ node, className, children, style, ...props }) => ( {children} diff --git a/site/src/components/Markdown/Markdown.tsx b/site/src/components/Markdown/Markdown.tsx index 3332e4936a..c76d062d1c 100644 --- a/site/src/components/Markdown/Markdown.tsx +++ b/site/src/components/Markdown/Markdown.tsx @@ -85,7 +85,7 @@ export const Markdown: FC = (props) => { ) : ( {children} diff --git a/site/src/modules/provisioners/Provisioner.tsx b/site/src/modules/provisioners/Provisioner.tsx index 5da261fbbf..dd54cc33bb 100644 --- a/site/src/modules/provisioners/Provisioner.tsx +++ b/site/src/modules/provisioners/Provisioner.tsx @@ -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 = ({ provisioner, warnings, }) => { - const theme = useTheme(); const daemonScope = provisioner.tags.scope || "organization"; const iconScope = daemonScope === "organization" ? ( @@ -36,20 +35,16 @@ export const Provisioner: FC = ({ return (
-
+

{provisioner.name}

- + {provisioner.version}
@@ -71,17 +66,7 @@ export const Provisioner: FC = ({
-
+
{warnings && warnings.length > 0 ? (
{warnings.map((warning) => ( @@ -92,7 +77,7 @@ export const Provisioner: FC = ({ No warnings )} {provisioner.last_seen_at && ( - + Last seen {createDayString(provisioner.last_seen_at)} )} diff --git a/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx index 5c2a86df56..29a3ac506a 100644 --- a/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.tsx @@ -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<
- +
ID diff --git a/site/src/pages/DeploymentSettingsPage/NotificationsPage/Troubleshooting.tsx b/site/src/pages/DeploymentSettingsPage/NotificationsPage/Troubleshooting.tsx index a388e62b11..7e19e1cd2e 100644 --- a/site/src/pages/DeploymentSettingsPage/NotificationsPage/Troubleshooting.tsx +++ b/site/src/pages/DeploymentSettingsPage/NotificationsPage/Troubleshooting.tsx @@ -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 = ({ }), }); - const theme = useTheme(); return ( <> -
+
Send a test notification to troubleshoot your notification settings.
diff --git a/site/src/pages/HealthPage/Content.tsx b/site/src/pages/HealthPage/Content.tsx index f3c1632be9..1fa90b91c4 100644 --- a/site/src/pages/HealthPage/Content.tsx +++ b/site/src/pages/HealthPage/Content.tsx @@ -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> = (props) => { +export const Header: FC> = ({ + className, + style, + children, + ...props +}) => { return (
+ > + {children} +
); }; -export const HeaderTitle: FC> = (props) => { +export const HeaderTitle: FC> = ({ + className, + children, + ...props +}) => { return (

+ > + {children} +

); }; @@ -44,11 +59,18 @@ interface HealthIconProps { } export const HealthIcon: FC = ({ size, severity }) => { - const theme = useTheme(); - const color = healthyColor(theme, severity); const Icon = severity === "error" ? CircleAlertIcon : CircleCheckIcon; - return ; + return ( + + ); }; interface HealthyDotProps { @@ -56,72 +78,96 @@ interface HealthyDotProps { } export const HealthyDot: FC = ({ severity }) => { - const theme = useTheme(); - return (
+ ); +}; + +export const Main: FC> = ({ + className, + style, + children, + ...props +}) => { + return ( +
+ {...props} + > + {children} +
); }; -export const Main: FC> = (props) => { +export const GridData: FC> = ({ + className, + children, + ...props +}) => { return (
+ > + {children} +
); }; -export const GridData: FC> = (props) => { - return ( -
- ); -}; - -export const GridDataLabel: FC> = (props) => { - const theme = useTheme(); +export const GridDataLabel: FC> = ({ + className, + children, + ...props +}) => { return ( + > + {children} + ); }; -export const GridDataValue: FC> = (props) => { - const theme = useTheme(); +export const GridDataValue: FC> = ({ + className, + children, + ...props +}) => { return ( - + + {children} + ); }; -export const SectionLabel: FC> = (props) => { +export const SectionLabel: FC> = ({ + className, + children, + ...props +}) => { return ( -

+

+ {children} +

); }; @@ -129,23 +175,18 @@ type PillProps = React.ComponentPropsWithRef<"div"> & { icon: ReactElement>; }; -export const Pill: React.FC = ({ icon, children, ...divProps }) => { - const theme = useTheme(); - +export const Pill: React.FC = ({ + className, + icon, + children, + ...divProps +}) => { return (
{cloneElement(icon, { className: "size-[14px]" })} @@ -178,16 +219,13 @@ export const BooleanPill: FC = ({ children, ...divProps }) => { - const theme = useTheme(); - const color = value ? theme.roles.success.outline : theme.roles.error.outline; - return ( + ) : ( - + ) } {...divProps} @@ -199,21 +237,13 @@ export const BooleanPill: FC = ({ type LogsProps = HTMLAttributes & { lines: readonly string[] }; -export const Logs: FC = ({ lines, ...divProps }) => { - const theme = useTheme(); - +export const Logs: FC = ({ className, lines, ...divProps }) => { return (
{lines.map((line, index) => ( @@ -222,9 +252,7 @@ export const Logs: FC = ({ lines, ...divProps }) => { ))} {lines.length === 0 && ( - - No logs available - + No logs available )}
); diff --git a/site/src/pages/HealthPage/WebsocketPage.tsx b/site/src/pages/HealthPage/WebsocketPage.tsx index 46c4cba4c7..5a2c5928b2 100644 --- a/site/src/pages/HealthPage/WebsocketPage.tsx +++ b/site/src/pages/HealthPage/WebsocketPage.tsx @@ -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(); const { websocket } = healthStatus; - const theme = useTheme(); return ( <> @@ -65,22 +62,11 @@ const WebsocketPage = () => {
Body -
+
{websocket.body !== "" ? ( websocket.body ) : ( - - No body message - + No body message )}
diff --git a/site/src/pages/HealthPage/WorkspaceProxyPage.tsx b/site/src/pages/HealthPage/WorkspaceProxyPage.tsx index 2a500375dc..da2e490b1f 100644 --- a/site/src/pages/HealthPage/WorkspaceProxyPage.tsx +++ b/site/src/pages/HealthPage/WorkspaceProxyPage.tsx @@ -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(); const { workspace_proxy } = healthStatus; const { regions } = workspace_proxy.workspace_proxies; - const theme = useTheme(); return ( <> @@ -66,15 +65,10 @@ const WorkspaceProxyPage: FC = () => { return (
@@ -85,9 +79,9 @@ const WorkspaceProxyPage: FC = () => { alt="" />
-
+

{region.display_name}

- + {region.version}
@@ -132,17 +126,7 @@ const WorkspaceProxyPage: FC = () => {
-
+
{region.status?.status === "unregistered" ? ( Has not connected yet ) : warnings.length === 0 && errors.length === 0 ? ( diff --git a/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx b/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx index d6e500154f..65da413afe 100644 --- a/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx +++ b/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx @@ -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 = ({ starterTemplate, error, }) => { - const theme = useTheme(); - if (error) { return ( @@ -72,11 +69,7 @@ export const StarterTemplatePageView: FC = ({
diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index cc3565b0ef..d023cd4add 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -336,7 +336,7 @@ const UsersLatencyPanel: FC = ({ .map((row) => (
@@ -392,7 +392,7 @@ const UsersActivityPanel: FC = ({ .map((row) => (
@@ -527,7 +527,7 @@ const TemplateParametersUsagePanel: FC = ({ >
{label}
-

+

{parameter.description}

@@ -706,7 +706,7 @@ const PanelTitle: FC> = ({ ...attrs }) => { return ( -
+
{children}
); diff --git a/site/src/pages/TerminalPage/TerminalPage.tsx b/site/src/pages/TerminalPage/TerminalPage.tsx index 3e00fc802f..44c8f1040c 100644 --- a/site/src/pages/TerminalPage/TerminalPage.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.tsx @@ -192,16 +192,8 @@ const TerminalPage: FC = () => {
{latency && isDebugging && ( - - Latency: {latency.latencyMS.toFixed(0)}ms + + Latency: {latency.latencyMS.toFixed(0)}ms{" "} )} diff --git a/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx index 56ecd90121..a1446dc951 100644 --- a/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx @@ -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 = + { + 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 = ({ items, severity, icon, }) => { const [isOpen, setIsOpen] = useState(false); - const theme = useTheme(); return ( @@ -50,10 +92,10 @@ export const Notifications: FC = ({ {items.map((n) => ( @@ -76,10 +118,10 @@ const NotificationPill: FC = ({ return ( ({ - "& svg": { color: theme.roles[severity].outline }, - borderColor: isOpen ? theme.roles[severity].outline : undefined, - })} + className={cn( + severityStyles[severity].svgColor, + isOpen && severityStyles[severity].border, + )} > {items.length} diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index 3cc866b919..f208ed6235 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -196,10 +196,7 @@ export const WorkspacesPageView: FC = ({ {pageNumberIsInvalid ? ( ({ - 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={ diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 41ef51ab29..bcf0571d90 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -207,7 +207,7 @@ export const WorkspacesTable: FC = ({ /> +
{workspace.name}