From 8bcdcd6c2d68a00693c6597e999ad5f8be490711 Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 16:04:49 +0000 Subject: [PATCH 01/14] feat: migrate Alert component from MUI to shadcn - Replace MUI Alert with shadcn-based implementation using class-variance-authority - Maintain backward compatibility with existing Alert API (severity, dismissible, actions, onDismiss) - Update AlertTitle imports across codebase to use new implementation - Add proper Tailwind CSS styling with dark mode support - Preserve accessibility features and smooth animations - Support all existing severity levels: info, success, warning, error Co-authored-by: jaaydenh <1858163+jaaydenh@users.noreply.github.com> --- site/src/components/Alert/Alert.tsx | 105 ++++++++++++++---- site/src/components/Alert/ErrorAlert.tsx | 3 +- .../GitDeviceAuth/GitDeviceAuth.tsx | 4 +- .../modules/provisioners/ProvisionerAlert.tsx | 8 +- .../ChangeWorkspaceVersionDialog.tsx | 4 +- .../OverviewPage/OverviewPageView.tsx | 4 +- site/src/pages/SetupPage/SetupPageView.tsx | 3 +- site/src/pages/WorkspacePage/Workspace.tsx | 3 +- 8 files changed, 97 insertions(+), 37 deletions(-) diff --git a/site/src/components/Alert/Alert.tsx b/site/src/components/Alert/Alert.tsx index 2673cd9bc4f8a..0106173dad52f 100644 --- a/site/src/components/Alert/Alert.tsx +++ b/site/src/components/Alert/Alert.tsx @@ -1,22 +1,51 @@ -import MuiAlert, { - type AlertColor as MuiAlertColor, - type AlertProps as MuiAlertProps, -} from "@mui/material/Alert"; import Collapse from "@mui/material/Collapse"; +import { cva, type VariantProps } from "class-variance-authority"; import { Button } from "components/Button/Button"; import { type FC, + forwardRef, type PropsWithChildren, type ReactNode, useState, } from "react"; -export type AlertColor = MuiAlertColor; +import { cn } from "utils/cn"; -export type AlertProps = MuiAlertProps & { +const alertVariants = cva("relative w-full rounded-lg border p-4 text-left", { + variants: { + variant: { + default: "bg-surface-primary text-content-primary border-border-default", + info: "bg-blue-50 text-blue-900 border-blue-200 dark:bg-blue-950 dark:text-blue-100 dark:border-blue-800", + success: + "bg-green-50 text-green-900 border-green-200 dark:bg-green-950 dark:text-green-100 dark:border-green-800", + warning: + "bg-yellow-50 text-yellow-900 border-yellow-200 dark:bg-yellow-950 dark:text-yellow-100 dark:border-yellow-800", + error: + "bg-red-50 text-red-900 border-red-200 dark:bg-red-950 dark:text-red-100 dark:border-red-800", + }, + }, + defaultVariants: { + variant: "default", + }, +}); + +// Map MUI severity to our variant +const severityToVariant = { + info: "info", + success: "success", + warning: "warning", + error: "error", +} as const; + +export type AlertColor = "info" | "success" | "warning" | "error"; + +export type AlertProps = { actions?: ReactNode; dismissible?: boolean; onDismiss?: () => void; -}; + severity?: AlertColor; + children?: ReactNode; + className?: string; +} & VariantProps; export const Alert: FC = ({ children, @@ -24,7 +53,9 @@ export const Alert: FC = ({ dismissible, severity = "info", onDismiss, - ...alertProps + className, + variant, + ...props }) => { const [open, setOpen] = useState(true); @@ -35,14 +66,21 @@ export const Alert: FC = ({ return null; } + // Use severity to determine variant if variant is not explicitly provided + const finalVariant = + variant || + (severity in severityToVariant ? severityToVariant[severity] : "default"); + return ( - +
+
+
{children}
+
{/* CTAs passed in by the consumer */} {actions} @@ -60,22 +98,43 @@ export const Alert: FC = ({ Dismiss )} - - } - > - {children} - +
+
+
); }; export const AlertDetail: FC = ({ children }) => { return ( - ({ color: theme.palette.text.secondary, fontSize: 13 })} - data-chromatic="ignore" - > + {children} ); }; + +// Export AlertTitle and AlertDescription for compatibility +export const AlertTitle = forwardRef< + HTMLHeadingElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +export const AlertDescription = forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); + +AlertDescription.displayName = "AlertDescription"; diff --git a/site/src/components/Alert/ErrorAlert.tsx b/site/src/components/Alert/ErrorAlert.tsx index 2a8da27e035ba..7129bbc07bfca 100644 --- a/site/src/components/Alert/ErrorAlert.tsx +++ b/site/src/components/Alert/ErrorAlert.tsx @@ -1,8 +1,7 @@ -import AlertTitle from "@mui/material/AlertTitle"; import { getErrorDetail, getErrorMessage, getErrorStatus } from "api/errors"; import type { FC } from "react"; import { Link } from "../Link/Link"; -import { Alert, AlertDetail, type AlertProps } from "./Alert"; +import { Alert, AlertDetail, AlertTitle, type AlertProps } from "./Alert"; type ErrorAlertProps = Readonly< Omit & { error: unknown } diff --git a/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx b/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx index 7b3d8091abfeb..e8e74c1fe42a0 100644 --- a/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx +++ b/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx @@ -1,11 +1,11 @@ import type { Interpolation, Theme } from "@emotion/react"; -import AlertTitle from "@mui/material/AlertTitle"; + import CircularProgress from "@mui/material/CircularProgress"; import Link from "@mui/material/Link"; import type { ApiErrorResponse } from "api/errors"; import type { ExternalAuthDevice } from "api/typesGenerated"; import { isAxiosError } from "axios"; -import { Alert, AlertDetail } from "components/Alert/Alert"; +import { Alert, AlertDetail, AlertTitle } from "components/Alert/Alert"; import { CopyButton } from "components/CopyButton/CopyButton"; import { ExternalLinkIcon } from "lucide-react"; import type { FC } from "react"; diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index 2160b4e7b3ebf..ebadeb7593ae8 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -1,6 +1,10 @@ import type { Theme } from "@emotion/react"; -import AlertTitle from "@mui/material/AlertTitle"; -import { Alert, type AlertColor, AlertDetail } from "components/Alert/Alert"; +import { + Alert, + type AlertColor, + AlertDetail, + AlertTitle, +} from "components/Alert/Alert"; import { ProvisionerTag } from "modules/provisioners/ProvisionerTag"; import type { FC } from "react"; export enum AlertVariant { diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx index ecedc5aef6b5f..3f06933dc04d3 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx @@ -1,11 +1,11 @@ import { css } from "@emotion/css"; -import AlertTitle from "@mui/material/AlertTitle"; + import Autocomplete from "@mui/material/Autocomplete"; import CircularProgress from "@mui/material/CircularProgress"; import TextField from "@mui/material/TextField"; import { templateVersions } from "api/queries/templates"; import type { TemplateVersion, Workspace } from "api/typesGenerated"; -import { Alert } from "components/Alert/Alert"; +import { Alert, AlertTitle } from "components/Alert/Alert"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; diff --git a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx index c43d77efe92e2..10d850f05271d 100644 --- a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx @@ -1,4 +1,4 @@ -import AlertTitle from "@mui/material/AlertTitle"; + import type { DAUsResponse, Experiment, @@ -15,7 +15,7 @@ import { Stack } from "components/Stack/Stack"; import type { FC } from "react"; import { useDeploymentOptions } from "utils/deployOptions"; import { docs } from "utils/docs"; -import { Alert } from "../../../components/Alert/Alert"; +import { Alert, AlertTitle } from "../../../components/Alert/Alert"; import OptionsTable from "../OptionsTable"; import { UserEngagementChart } from "./UserEngagementChart"; diff --git a/site/src/pages/SetupPage/SetupPageView.tsx b/site/src/pages/SetupPage/SetupPageView.tsx index 28e750bfb31dc..15dee54c0ab60 100644 --- a/site/src/pages/SetupPage/SetupPageView.tsx +++ b/site/src/pages/SetupPage/SetupPageView.tsx @@ -1,4 +1,3 @@ -import AlertTitle from "@mui/material/AlertTitle"; import Autocomplete from "@mui/material/Autocomplete"; import Checkbox from "@mui/material/Checkbox"; import Link from "@mui/material/Link"; @@ -7,7 +6,7 @@ import TextField from "@mui/material/TextField"; import { countries } from "api/countriesGenerated"; import type * as TypesGen from "api/typesGenerated"; import { isAxiosError } from "axios"; -import { Alert, AlertDetail } from "components/Alert/Alert"; +import { Alert, AlertDetail, AlertTitle } from "components/Alert/Alert"; import { Button } from "components/Button/Button"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { FormFields, VerticalForm } from "components/Form/Form"; diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 83b8a6397104c..e41fe2b7624b3 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -1,6 +1,5 @@ -import AlertTitle from "@mui/material/AlertTitle"; import type * as TypesGen from "api/typesGenerated"; -import { Alert, AlertDetail } from "components/Alert/Alert"; +import { Alert, AlertDetail, AlertTitle } from "components/Alert/Alert"; import { SidebarIconButton } from "components/FullPageLayout/Sidebar"; import { useSearchParamsKey } from "hooks/useSearchParamsKey"; import { BlocksIcon, HistoryIcon } from "lucide-react"; From c90d70c43ec21c3f73c504553da660eaa7e6c516 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 18 Jun 2025 21:41:11 +0000 Subject: [PATCH 02/14] fix: cleanup --- site/src/components/Alert/Alert.tsx | 101 +++++++----------- site/src/components/Alert/ErrorAlert.tsx | 2 +- .../OverviewPage/OverviewPageView.tsx | 1 - 3 files changed, 42 insertions(+), 62 deletions(-) diff --git a/site/src/components/Alert/Alert.tsx b/site/src/components/Alert/Alert.tsx index 0106173dad52f..c609ea1dbba7a 100644 --- a/site/src/components/Alert/Alert.tsx +++ b/site/src/components/Alert/Alert.tsx @@ -1,4 +1,3 @@ -import Collapse from "@mui/material/Collapse"; import { cva, type VariantProps } from "class-variance-authority"; import { Button } from "components/Button/Button"; import { @@ -10,23 +9,23 @@ import { } from "react"; import { cn } from "utils/cn"; -const alertVariants = cva("relative w-full rounded-lg border p-4 text-left", { - variants: { - variant: { - default: "bg-surface-primary text-content-primary border-border-default", - info: "bg-blue-50 text-blue-900 border-blue-200 dark:bg-blue-950 dark:text-blue-100 dark:border-blue-800", - success: - "bg-green-50 text-green-900 border-green-200 dark:bg-green-950 dark:text-green-100 dark:border-green-800", - warning: - "bg-yellow-50 text-yellow-900 border-yellow-200 dark:bg-yellow-950 dark:text-yellow-100 dark:border-yellow-800", - error: - "bg-red-50 text-red-900 border-red-200 dark:bg-red-950 dark:text-red-100 dark:border-red-800", +const alertVariants = cva( + "relative w-full rounded-lg border border-solid p-4 text-left", + { + variants: { + variant: { + default: "border-border-default", + info: "border-surface-sky", + success: "border-surface-green", + warning: "border-border-warning", + error: "border-border-destructive", + }, }, - }, - defaultVariants: { - variant: "default", - }, -}); + defaultVariants: { + variant: "default", + }, + } +) // Map MUI severity to our variant const severityToVariant = { @@ -59,9 +58,6 @@ export const Alert: FC = ({ }) => { const [open, setOpen] = useState(true); - // Can't only rely on MUI's hiding behavior inside flex layouts, because even - // though MUI will make a dismissed alert have zero height, the alert will - // still behave as a flex child and introduce extra row/column gaps if (!open) { return null; } @@ -72,36 +68,33 @@ export const Alert: FC = ({ (severity in severityToVariant ? severityToVariant[severity] : "default"); return ( - -
-
-
{children}
-
- {/* CTAs passed in by the consumer */} - {actions} +
+
+
{children}
+
+ {/* CTAs passed in by the consumer */} + {actions} - {/* close CTA */} - {dismissible && ( - - )} -
+ {dismissible && ( + + )}
- +
); }; @@ -124,17 +117,5 @@ export const AlertTitle = forwardRef< {...props} /> )); -AlertTitle.displayName = "AlertTitle"; - -export const AlertDescription = forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -AlertDescription.displayName = "AlertDescription"; +AlertTitle.displayName = "AlertTitle"; diff --git a/site/src/components/Alert/ErrorAlert.tsx b/site/src/components/Alert/ErrorAlert.tsx index 7129bbc07bfca..1d1408ae70500 100644 --- a/site/src/components/Alert/ErrorAlert.tsx +++ b/site/src/components/Alert/ErrorAlert.tsx @@ -1,7 +1,7 @@ import { getErrorDetail, getErrorMessage, getErrorStatus } from "api/errors"; import type { FC } from "react"; import { Link } from "../Link/Link"; -import { Alert, AlertDetail, AlertTitle, type AlertProps } from "./Alert"; +import { Alert, AlertDetail, type AlertProps, AlertTitle } from "./Alert"; type ErrorAlertProps = Readonly< Omit & { error: unknown } diff --git a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx index 10d850f05271d..46ced42e8c504 100644 --- a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx @@ -1,4 +1,3 @@ - import type { DAUsResponse, Experiment, From 07b9b570d9718edd866e88ac687aed7536f41723 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 20 Jun 2025 12:37:39 +0000 Subject: [PATCH 03/14] fix: update color --- site/src/components/Alert/Alert.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/Alert/Alert.tsx b/site/src/components/Alert/Alert.tsx index c609ea1dbba7a..c42476ffdd36f 100644 --- a/site/src/components/Alert/Alert.tsx +++ b/site/src/components/Alert/Alert.tsx @@ -15,7 +15,7 @@ const alertVariants = cva( variants: { variant: { default: "border-border-default", - info: "border-surface-sky", + info: "border-highlight-sky", success: "border-surface-green", warning: "border-border-warning", error: "border-border-destructive", From c36c9bd4f7577a5dafcebe7b7929c9900a208ca1 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 5 Dec 2025 15:45:52 +0000 Subject: [PATCH 04/14] chore: cleanup --- site/src/components/Alert/Alert.tsx | 65 +++++++++++++------ site/src/components/Badge/Badge.tsx | 2 +- .../GitDeviceAuth/GitDeviceAuth.tsx | 1 - .../ChangeWorkspaceVersionDialog.tsx | 1 - .../WorkspaceNotifications/Notifications.tsx | 2 +- ...orkspaceParametersPageViewExperimental.tsx | 2 +- .../WorkspaceSchedulePage.tsx | 2 +- site/tailwind.config.js | 2 +- 8 files changed, 50 insertions(+), 27 deletions(-) diff --git a/site/src/components/Alert/Alert.tsx b/site/src/components/Alert/Alert.tsx index c42476ffdd36f..6392f98b46d45 100644 --- a/site/src/components/Alert/Alert.tsx +++ b/site/src/components/Alert/Alert.tsx @@ -1,5 +1,12 @@ import { cva, type VariantProps } from "class-variance-authority"; import { Button } from "components/Button/Button"; +import { + CircleAlertIcon, + CircleCheckIcon, + InfoIcon, + TriangleAlertIcon, + XIcon, +} from "lucide-react"; import { type FC, forwardRef, @@ -14,20 +21,19 @@ const alertVariants = cva( { variants: { variant: { - default: "border-border-default", - info: "border-highlight-sky", - success: "border-surface-green", - warning: "border-border-warning", - error: "border-border-destructive", + default: "border-border-default bg-surface-secondary", + info: "border-border-pending bg-surface-secondary", + success: "border-border-green bg-surface-green", + warning: "border-border-warning bg-surface-orange", + error: "border-border-destructive bg-surface-red", }, }, defaultVariants: { variant: "default", }, - } -) + }, +); -// Map MUI severity to our variant const severityToVariant = { info: "info", success: "success", @@ -35,6 +41,14 @@ const severityToVariant = { error: "error", } as const; +const variantIcons = { + default: { icon: InfoIcon, className: "text-content-secondary" }, + info: { icon: InfoIcon, className: "text-highlight-sky" }, + success: { icon: CircleCheckIcon, className: "text-content-success" }, + warning: { icon: TriangleAlertIcon, className: "text-content-warning" }, + error: { icon: CircleAlertIcon, className: "text-content-destructive" }, +} as const; + export type AlertColor = "info" | "success" | "warning" | "error"; export type AlertProps = { @@ -67,29 +81,40 @@ export const Alert: FC = ({ variant || (severity in severityToVariant ? severityToVariant[severity] : "default"); + const { icon: Icon, className: iconClassName } = variantIcons[finalVariant]; + return (
-
-
{children}
-
- {/* CTAs passed in by the consumer */} +
+
+ +
{children}
+
+
{actions} {dismissible && ( )}
@@ -100,22 +125,22 @@ export const Alert: FC = ({ export const AlertDetail: FC = ({ children }) => { return ( - + {children} ); }; -// Export AlertTitle and AlertDescription for compatibility export const AlertTitle = forwardRef< HTMLHeadingElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( -
)); - -AlertTitle.displayName = "AlertTitle"; diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index ca6a08eb6040a..317f1e04e481a 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -24,7 +24,7 @@ const badgeVariants = cva( "border border-solid border-border-destructive bg-surface-red text-highlight-red shadow", green: "border border-solid border-border-green bg-surface-green text-highlight-green shadow", - info: "border border-solid border-border-sky bg-surface-sky text-highlight-sky shadow", + info: "border border-solid border-border-pending bg-surface-sky text-highlight-sky shadow", }, size: { xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5", diff --git a/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx b/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx index e8e74c1fe42a0..41faa5f878cf4 100644 --- a/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx +++ b/site/src/components/GitDeviceAuth/GitDeviceAuth.tsx @@ -1,5 +1,4 @@ import type { Interpolation, Theme } from "@emotion/react"; - import CircularProgress from "@mui/material/CircularProgress"; import Link from "@mui/material/Link"; import type { ApiErrorResponse } from "api/errors"; diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx index 3f06933dc04d3..2a0d89878d0a1 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/ChangeWorkspaceVersionDialog.tsx @@ -1,5 +1,4 @@ import { css } from "@emotion/css"; - import Autocomplete from "@mui/material/Autocomplete"; import CircularProgress from "@mui/material/CircularProgress"; import TextField from "@mui/material/TextField"; diff --git a/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx index 308a763716969..b2989fab130db 100644 --- a/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx @@ -105,7 +105,7 @@ const NotificationItem: FC = ({ notification }) => { }; export const NotificationActionButton: FC = (props) => { - return