refactor: replace remaining MUI tooltips (#20938)

closes #19974 

This PR replaces all remaining MUI tooltips with our shadcn tooltip
component, and uncomments the linter restriction on importing the MUI
tooltip
This commit is contained in:
Andrew Aquino
2025-12-15 08:12:27 -08:00
committed by GitHub
parent b073357414
commit 13fbbcd279
11 changed files with 210 additions and 144 deletions
+1 -1
View File
@@ -99,7 +99,7 @@
// "@mui/material/TextField": "Use shadcn/ui Input component instead.",
// "@mui/material/ToggleButton": "Use shadcn/ui Toggle or custom component instead.",
// "@mui/material/ToggleButtonGroup": "Use shadcn/ui Toggle or custom component instead.",
// "@mui/material/Tooltip": "Use shadcn/ui Tooltip component instead.",
"@mui/material/Tooltip": "Use components/Tooltip/Tooltip instead.",
"@mui/material/Typography": "Use native HTML elements instead. Eg: <span>, <p>, <h1>, etc.",
// "@mui/material/useMediaQuery": "Use Tailwind responsive classes or custom hook instead.",
// "@mui/system": "Use Tailwind CSS instead.",
@@ -5,13 +5,17 @@ import type { InputBaseComponentProps } from "@mui/material/InputBase";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import TextField, { type TextFieldProps } from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import type { TemplateVersionParameter } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { MemoizedMarkdown } from "components/Markdown/Markdown";
import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { CircleAlertIcon, SettingsIcon } from "lucide-react";
import { type FC, type ReactNode, useState } from "react";
import type {
@@ -136,25 +140,41 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter, isPreset }) => {
{displayName}
{!parameter.required && (
<Tooltip title="If no value is specified, the system will default to the value set by the administrator.">
<span css={styles.optionalLabel}>(optional)</span>
<Tooltip>
<TooltipTrigger asChild>
<span css={styles.optionalLabel}>(optional)</span>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
If no value is specified, the system will default to the value set
by the administrator.
</TooltipContent>
</Tooltip>
)}
{!parameter.mutable && (
<Tooltip title="This value cannot be modified after the workspace has been created.">
<Pill
type="warning"
icon={<CircleAlertIcon className="size-icon-xs" />}
>
Immutable
</Pill>
<Tooltip>
<TooltipTrigger asChild>
<Pill
type="warning"
icon={<CircleAlertIcon className="size-icon-xs" />}
>
Immutable
</Pill>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
This value cannot be modified after the workspace has been created.
</TooltipContent>
</Tooltip>
)}
{isPreset && (
<Tooltip title="This value was set by a preset">
<Pill type="info" icon={<SettingsIcon className="size-icon-xs" />}>
Preset
</Pill>
<Tooltip>
<TooltipTrigger asChild>
<Pill type="info" icon={<SettingsIcon className="size-icon-xs" />}>
Preset
</Pill>
</TooltipTrigger>
<TooltipContent side="bottom">
This value was set by a preset
</TooltipContent>
</Tooltip>
)}
</span>
@@ -328,14 +348,15 @@ const RichParameterField: FC<RichParameterInputProps> = ({
css={{ padding: small ? undefined : "4px 0" }}
>
{small ? (
<Tooltip
title={
<Tooltip>
<TooltipTrigger asChild>
<div>{option.name}</div>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
<MemoizedMarkdown>
{option.description}
</MemoizedMarkdown>
}
>
<div>{option.name}</div>
</TooltipContent>
</Tooltip>
) : (
<>
@@ -1,4 +1,3 @@
import MuiTooltip from "@mui/material/Tooltip";
import type { WorkspaceAgentLogSource } from "api/typesGenerated";
import { Badge } from "components/Badge/Badge";
import type { Line } from "components/Logs/LogLine";
@@ -125,21 +124,18 @@ export const AgentLogs = forwardRef<List, AgentLogsProps>(
maxLineNumber={logs.length}
style={style}
sourceIcon={
<MuiTooltip
title={
<>
{logSource.display_name}
{assignedIcon && (
<i>
<br />
No icon specified!
</i>
)}
</>
}
>
{icon}
</MuiTooltip>
<Tooltip>
<TooltipTrigger asChild>{icon}</TooltipTrigger>
<TooltipContent side="bottom">
{logSource.display_name}
{assignedIcon && (
<i>
<br />
No icon specified!
</i>
)}
</TooltipContent>
</Tooltip>
}
/>
);
@@ -155,7 +155,7 @@ export const AppLink: FC<AppLinkProps> = ({
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent>
<TooltipContent className="max-w-xs">
{primaryTooltip ? (
primaryTooltip
) : app.tooltip ? (
@@ -1,6 +1,10 @@
import Tooltip from "@mui/material/Tooltip";
import type { Workspace } from "api/typesGenerated";
import { Badge } from "components/Badge/Badge";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import type { FC } from "react";
import {
DATE_FORMAT,
@@ -16,34 +20,32 @@ export const WorkspaceDormantBadge: FC<WorkspaceDormantBadgeProps> = ({
workspace,
}) => {
return workspace.deleting_at ? (
<Tooltip
title={
<>
This workspace has not been used for{" "}
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been
marked dormant. It is scheduled to be deleted on{" "}
{formatDateTime(workspace.deleting_at, DATE_FORMAT.FULL_DATETIME)}.
</>
}
>
<Badge role="status" variant="destructive" size="xs">
Deletion Pending
</Badge>
<Tooltip>
<TooltipTrigger asChild>
<Badge role="status" variant="destructive" size="xs">
Deletion Pending
</Badge>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
This workspace has not been used for{" "}
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been marked
dormant. It is scheduled to be deleted on{" "}
{formatDateTime(workspace.deleting_at, DATE_FORMAT.FULL_DATETIME)}.
</TooltipContent>
</Tooltip>
) : (
<Tooltip
title={
<>
This workspace has not been used for{" "}
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been
marked dormant. It is not scheduled for auto-deletion but will become
a candidate if auto-deletion is enabled on this template.
</>
}
>
<Badge role="status" variant="warning" size="xs">
Dormant
</Badge>
<Tooltip>
<TooltipTrigger asChild>
<Badge role="status" variant="warning" size="xs">
Dormant
</Badge>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
This workspace has not been used for{" "}
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been marked
dormant. It is not scheduled for auto-deletion but will become a
candidate if auto-deletion is enabled on this template.
</TooltipContent>
</Tooltip>
);
};
@@ -1,7 +1,6 @@
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
import Collapse from "@mui/material/Collapse";
import Link from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import type { AuditLog, BuildReason } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
@@ -9,6 +8,11 @@ import { Stack } from "components/Stack/Stack";
import { StatusPill } from "components/StatusPill/StatusPill";
import { TableCell } from "components/Table/Table";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { InfoIcon, NetworkIcon } from "lucide-react";
import { type FC, useState } from "react";
import { Link as RouterLink } from "react-router";
@@ -128,8 +132,15 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
{/* With multi-org, there is not enough space so show
everything in a tooltip. */}
{showOrgDetails ? (
<Tooltip
title={
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon
css={(theme) => ({
color: theme.palette.info.light,
})}
/>
</TooltipTrigger>
<TooltipContent side="bottom">
<div css={styles.auditLogInfoTooltip}>
{auditLog.ip && (
<div>
@@ -181,13 +192,7 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
</div>
)}
</div>
}
>
<InfoIcon
css={(theme) => ({
color: theme.palette.info.light,
})}
/>
</TooltipContent>
</Tooltip>
) : (
<Stack direction="row" spacing={1} alignItems="baseline">
@@ -1,12 +1,16 @@
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
import Link from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import type { ConnectionLog } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { Stack } from "components/Stack/Stack";
import { StatusPill } from "components/StatusPill/StatusPill";
import { TableCell } from "components/Table/Table";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { InfoIcon, NetworkIcon } from "lucide-react";
import type { FC } from "react";
import { Link as RouterLink } from "react-router";
@@ -87,8 +91,15 @@ export const ConnectionLogRow: FC<ConnectionLogRowProps> = ({
label={isWeb ? "HTTP Status Code" : "SSH Exit Code"}
/>
)}
<Tooltip
title={
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon
css={(theme) => ({
color: theme.palette.info.light,
})}
/>
</TooltipTrigger>
<TooltipContent side="bottom">
<div css={styles.connectionLogInfoTooltip}>
{connectionLog.ip && (
<div>
@@ -133,13 +144,7 @@ export const ConnectionLogRow: FC<ConnectionLogRowProps> = ({
</div>
)}
</div>
}
>
<InfoIcon
css={(theme) => ({
color: theme.palette.info.light,
})}
/>
</TooltipContent>
</Tooltip>
</Stack>
</Stack>
@@ -1,6 +1,8 @@
import {
MockOrganizationMember,
MockOrganizationMember2,
MockOwnerRole,
MockUserAdminRole,
MockUserOwner,
} from "testHelpers/entities";
import type { Meta, StoryObj } from "@storybook/react-vite";
@@ -19,7 +21,11 @@ const meta: Meta<typeof OrganizationMembersPageView> = {
canViewMembers: true,
me: MockUserOwner,
members: [
{ ...MockOrganizationMember, groups: [] },
{
...MockOrganizationMember,
global_roles: [MockOwnerRole, MockUserAdminRole],
groups: [],
},
{ ...MockOrganizationMember2, groups: [] },
],
membersQuery: {
@@ -1,5 +1,4 @@
import Checkbox from "@mui/material/Checkbox";
import Tooltip from "@mui/material/Tooltip";
import type { SlimRole } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { CollapsibleSummary } from "components/CollapsibleSummary/CollapsibleSummary";
@@ -16,6 +15,11 @@ import {
PopoverContent,
PopoverTrigger,
} from "components/Popover/Popover";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { UserIcon } from "lucide-react";
import { type FC, useEffect, useState } from "react";
@@ -130,18 +134,21 @@ const EnabledEditRolesButton: FC<EditRolesButtonProps> = ({
return (
<Popover>
<PopoverTrigger asChild>
<Tooltip title="Edit user roles">
<Button
variant="subtle"
aria-label="Edit user roles"
size="icon"
className="text-content-secondary hover:text-content-primary"
>
<EditSquare />
</Button>
</Tooltip>
</PopoverTrigger>
<Tooltip>
<PopoverTrigger asChild>
<TooltipTrigger asChild>
<Button
variant="subtle"
aria-label="Edit user roles"
size="icon"
className="text-content-secondary hover:text-content-primary"
>
<EditSquare />
</Button>
</TooltipTrigger>
</PopoverTrigger>
<TooltipContent side="bottom">Edit user roles</TooltipContent>
</Tooltip>
<PopoverContent
align="start"
@@ -14,7 +14,6 @@
* users like that, though, know that it will be painful
*/
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import MUITooltip from "@mui/material/Tooltip";
import type { LoginType, SlimRole } from "api/typesGenerated";
import { Pill } from "components/Pill/Pill";
import { TableCell } from "components/Table/Table";
@@ -88,9 +87,14 @@ export const UserRoleCell: FC<UserRoleCellProps> = ({
}
>
{mainDisplayRole.global ? (
<MUITooltip title="This user has this role for all organizations.">
<span>{displayName}*</span>
</MUITooltip>
<Tooltip>
<TooltipTrigger asChild>
<span>{displayName}*</span>
</TooltipTrigger>
<TooltipContent side="bottom" sideOffset={8}>
This user has this role for all organizations.
</TooltipContent>
</Tooltip>
) : (
displayName
)}
@@ -130,9 +134,9 @@ const OverflowRolePill: FC<OverflowRolePillProps> = ({ roles }) => {
css={role.global ? styles.globalRoleBadge : styles.roleBadge}
>
{role.global ? (
<MUITooltip title="This user has this role for all organizations.">
<span>{role.display_name || role.name}*</span>
</MUITooltip>
<span title="This user has this role for all organizations.">
{role.display_name || role.name}*
</span>
) : (
role.display_name || role.name
)}
@@ -1,7 +1,6 @@
import type { Interpolation, Theme } from "@emotion/react";
import IconButton from "@mui/material/IconButton";
import Link, { type LinkProps } from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import { visuallyHidden } from "@mui/utils";
import { getErrorMessage } from "api/errors";
import {
@@ -11,6 +10,11 @@ import {
import type { Template, Workspace } from "api/typesGenerated";
import { TopbarData, TopbarIcon } from "components/FullPageLayout/Topbar";
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import dayjs, { type Dayjs } from "dayjs";
import { useTime } from "hooks/useTime";
import { ClockIcon, MinusIcon, PlusIcon } from "lucide-react";
@@ -45,19 +49,22 @@ const WorkspaceScheduleContainer: FC<WorkspaceScheduleContainerProps> = ({
return (
<TopbarData>
<Tooltip title="Schedule">
{onClickIcon ? (
<button
type="button"
data-testid="schedule-icon-button"
onClick={onClickIcon}
css={styles.scheduleIconButton}
>
{icon}
</button>
) : (
icon
)}
<Tooltip>
<TooltipTrigger asChild>
{onClickIcon ? (
<button
type="button"
data-testid="schedule-icon-button"
onClick={onClickIcon}
css={styles.scheduleIconButton}
>
{icon}
</button>
) : (
icon
)}
</TooltipTrigger>
<TooltipContent side="bottom">Schedule</TooltipContent>
</Tooltip>
{children}
</TopbarData>
@@ -200,31 +207,39 @@ const AutostopDisplay: FC<AutostopDisplayProps> = ({
const controls = canUpdateSchedule && canEditDeadline(workspace) && (
<div css={styles.scheduleControls}>
<Tooltip title="Subtract 1 hour from deadline">
<IconButton
disabled={!deadlineMinusEnabled}
size="small"
css={styles.scheduleButton}
onClick={() => {
handleDeadlineChange(deadline.subtract(1, "h"));
}}
>
<MinusIcon className="size-icon-xs" />
<span style={visuallyHidden}>Subtract 1 hour</span>
</IconButton>
<Tooltip>
<TooltipTrigger asChild>
<IconButton
disabled={!deadlineMinusEnabled}
size="small"
css={styles.scheduleButton}
onClick={() => {
handleDeadlineChange(deadline.subtract(1, "h"));
}}
>
<MinusIcon className="size-icon-xs" />
<span style={visuallyHidden}>Subtract 1 hour from deadline</span>
</IconButton>
</TooltipTrigger>
<TooltipContent side="bottom">
Subtract 1 hour from deadline
</TooltipContent>
</Tooltip>
<Tooltip title="Add 1 hour to deadline">
<IconButton
disabled={!deadlinePlusEnabled}
size="small"
css={styles.scheduleButton}
onClick={() => {
handleDeadlineChange(deadline.add(1, "h"));
}}
>
<PlusIcon className="size-icon-xs" />
<span style={visuallyHidden}>Add 1 hour</span>
</IconButton>
<Tooltip>
<TooltipTrigger asChild>
<IconButton
disabled={!deadlinePlusEnabled}
size="small"
css={styles.scheduleButton}
onClick={() => {
handleDeadlineChange(deadline.add(1, "h"));
}}
>
<PlusIcon className="size-icon-xs" />
<span style={visuallyHidden}>Add 1 hour to deadline</span>
</IconButton>
</TooltipTrigger>
<TooltipContent side="bottom">Add 1 hour to deadline</TooltipContent>
</Tooltip>
</div>
);
@@ -232,7 +247,12 @@ const AutostopDisplay: FC<AutostopDisplayProps> = ({
if (tooltip) {
return (
<WorkspaceScheduleContainer onClickIcon={onClickScheduleIcon}>
<Tooltip title={tooltip}>{display}</Tooltip>
<Tooltip>
<TooltipTrigger asChild>{display}</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
{tooltip}
</TooltipContent>
</Tooltip>
{controls}
</WorkspaceScheduleContainer>
);