chore: replace MUI Button - 1 (#17865)

This commit is contained in:
Bruno Quaresma
2025-05-16 12:31:32 -07:00
committed by GitHub
parent f8f4dc6875
commit 87a1ebc460
13 changed files with 129 additions and 183 deletions
@@ -1,11 +1,9 @@
import { useTheme } from "@emotion/react";
import Button from "@mui/material/Button";
import { Button } from "components/Button/Button";
import type { FC, ReactNode } from "react";
type NumberedPageButtonProps = {
pageNumber: number;
totalPages: number;
onClick?: () => void;
highlighted?: boolean;
disabled?: boolean;
@@ -68,23 +66,10 @@ const BasePageButton: FC<BasePageButtonProps> = ({
highlighted = false,
disabled = false,
}) => {
const theme = useTheme();
return (
<Button
css={
highlighted && {
borderColor: theme.roles.active.outline,
backgroundColor: theme.roles.active.background,
// Override the hover state with active colors, but not hover
// colors because clicking won't do anything.
"&:hover": {
borderColor: theme.roles.active.outline,
backgroundColor: theme.roles.active.background,
},
}
}
variant={highlighted ? "default" : "outline"}
size="icon"
aria-label={ariaLabel}
name={name}
disabled={disabled}
@@ -1,6 +1,5 @@
import { useTheme } from "@emotion/react";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import { Button } from "components/Button/Button";
import {
type ButtonHTMLAttributes,
type ReactNode,
@@ -32,7 +31,6 @@ function PaginationNavButtonCore({
disabledMessageTimeout = 3000,
...delegatedProps
}: PaginationNavButtonProps) {
const theme = useTheme();
const [showDisabledMessage, setShowDisabledMessage] = useState(false);
// Inline state sync - this is safe/recommended by the React team in this case
@@ -63,25 +61,10 @@ function PaginationNavButtonCore({
* (mostly for giving direct UI feedback to those actions)
*/}
<Button
aria-disabled={disabled}
css={
disabled && {
borderColor: theme.palette.divider,
color: theme.palette.text.disabled,
cursor: "default",
"&:hover": {
backgroundColor: theme.palette.background.default,
borderColor: theme.palette.divider,
},
}
}
onClick={() => {
if (disabled) {
setShowDisabledMessage(true);
} else {
onClick();
}
}}
variant="outline"
size="icon"
disabled={disabled}
onClick={onClick}
{...delegatedProps}
/>
</Tooltip>
@@ -9,7 +9,7 @@ import {
type SampleProps = Omit<PaginationWidgetBaseProps, "onPageChange">;
describe(PaginationWidgetBase.name, () => {
it("Should have its previous button be aria-disabled while on page 1", async () => {
it("Should have its previous button be disabled while on page 1", async () => {
const sampleProps: SampleProps[] = [
{ currentPage: 1, pageSize: 5, totalRecords: 6 },
{ currentPage: 1, pageSize: 50, totalRecords: 200 },
@@ -23,8 +23,7 @@ describe(PaginationWidgetBase.name, () => {
);
const prevButton = await screen.findByLabelText("Previous page");
expect(prevButton).not.toBeDisabled();
expect(prevButton).toHaveAttribute("aria-disabled", "true");
expect(prevButton).toBeDisabled();
await userEvent.click(prevButton);
expect(onPageChange).not.toHaveBeenCalled();
@@ -32,7 +31,7 @@ describe(PaginationWidgetBase.name, () => {
}
});
it("Should have its next button be aria-disabled while on last page", async () => {
it("Should have its next button be disabled while on last page", async () => {
const sampleProps: SampleProps[] = [
{ currentPage: 2, pageSize: 5, totalRecords: 6 },
{ currentPage: 4, pageSize: 50, totalRecords: 200 },
@@ -46,8 +45,7 @@ describe(PaginationWidgetBase.name, () => {
);
const button = await screen.findByLabelText("Next page");
expect(button).not.toBeDisabled();
expect(button).toHaveAttribute("aria-disabled", "true");
expect(button).toBeDisabled();
await userEvent.click(button);
expect(onPageChange).not.toHaveBeenCalled();
@@ -72,13 +70,11 @@ describe(PaginationWidgetBase.name, () => {
const nextButton = await screen.findByLabelText("Next page");
expect(prevButton).not.toBeDisabled();
expect(prevButton).toHaveAttribute("aria-disabled", "false");
await userEvent.click(prevButton);
expect(onPageChange).toHaveBeenCalledTimes(1);
expect(nextButton).not.toBeDisabled();
expect(nextButton).toHaveAttribute("aria-disabled", "false");
await userEvent.click(nextButton);
expect(onPageChange).toHaveBeenCalledTimes(2);
@@ -59,7 +59,7 @@ export const PaginationWidgetBase: FC<PaginationWidgetBaseProps> = ({
}
}}
>
<ChevronLeftIcon className="size-icon-sm" />
<ChevronLeftIcon />
</PaginationNavButton>
{isMobile ? (
@@ -86,7 +86,7 @@ export const PaginationWidgetBase: FC<PaginationWidgetBaseProps> = ({
}
}}
>
<ChevronRightIcon className="size-icon-sm" />
<ChevronRightIcon />
</PaginationNavButton>
</div>
);
@@ -1,7 +1,7 @@
import DownloadOutlined from "@mui/icons-material/DownloadOutlined";
import Button from "@mui/material/Button";
import { agentLogs } from "api/queries/workspaces";
import type { WorkspaceAgent, WorkspaceAgentLog } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { displayError } from "components/GlobalSnackbar/utils";
import { saveAs } from "file-saver";
import { type FC, useState } from "react";
@@ -35,10 +35,9 @@ export const DownloadAgentLogsButton: FC<DownloadAgentLogsButtonProps> = ({
return (
<Button
startIcon={<DownloadOutlined />}
disabled={!isConnected || isDownloading}
variant="text"
size="small"
variant="subtle"
size="sm"
onClick={async () => {
try {
setIsDownloading(true);
@@ -57,6 +56,7 @@ export const DownloadAgentLogsButton: FC<DownloadAgentLogsButtonProps> = ({
}
}}
>
<DownloadOutlined />
{isDownloading ? "Downloading..." : "Download logs"}
</Button>
);
@@ -2,15 +2,13 @@ import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen";
import SensorsIcon from "@mui/icons-material/Sensors";
import MUIButton from "@mui/material/Button";
import CircularProgress from "@mui/material/CircularProgress";
import FormControl from "@mui/material/FormControl";
import Link from "@mui/material/Link";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import MUITooltip from "@mui/material/Tooltip";
import { API } from "api/api";
import {
deleteWorkspacePortShare,
@@ -33,6 +31,12 @@ import {
HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip";
import { Spinner } from "components/Spinner/Spinner";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import {
Popover,
PopoverContent,
@@ -40,7 +44,12 @@ import {
} from "components/deprecated/Popover/Popover";
import { type FormikContextType, useFormik } from "formik";
import { type ClassName, useClassName } from "hooks/useClassName";
import { ChevronDownIcon, ExternalLinkIcon, X as XIcon } from "lucide-react";
import {
ChevronDownIcon,
ExternalLinkIcon,
ShareIcon,
X as XIcon,
} from "lucide-react";
import { useDashboard } from "modules/dashboard/useDashboard";
import { type FC, useState } from "react";
import { useMutation, useQuery } from "react-query";
@@ -77,26 +86,13 @@ export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
return (
<Popover>
<PopoverTrigger>
<MUIButton
disabled={!portsQuery.data}
size="small"
variant="text"
endIcon={<ChevronDownIcon className="size-4" />}
css={{ fontSize: 13, padding: "8px 12px" }}
startIcon={
portsQuery.data ? (
<div>
<span css={styles.portCount}>
{portsQuery.data.ports.length}
</span>
</div>
) : (
<CircularProgress size={10} />
)
}
>
<Button disabled={!portsQuery.data} size="sm" variant="subtle">
<Spinner loading={!portsQuery.data}>
<span css={styles.portCount}>{portsQuery.data?.ports.length}</span>
</Spinner>
Open ports
</MUIButton>
<ChevronDownIcon className="size-4" />
</Button>
</PopoverTrigger>
<PopoverContent horizontal="right" classes={{ paper }}>
<PortForwardPopoverView
@@ -203,14 +199,14 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
canSharePorts && template.max_port_share_level === "public";
const disabledPublicMenuItem = (
<Tooltip title="This workspace template does not allow sharing ports with unauthenticated users.">
<MUITooltip title="This workspace template does not allow sharing ports with unauthenticated users.">
{/* Tooltips don't work directly on disabled MenuItem components so you must wrap in div. */}
<div>
<MenuItem value="public" disabled>
Public
</MenuItem>
</div>
</Tooltip>
</MUITooltip>
);
return (
@@ -297,24 +293,17 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
required
css={styles.newPortInput}
/>
<MUIButton
type="submit"
size="small"
variant="text"
css={{
paddingLeft: 12,
paddingRight: 12,
minWidth: 0,
}}
>
<ExternalLinkIcon
className="size-icon-xs"
css={{
flexShrink: 0,
color: theme.palette.text.primary,
}}
/>
</MUIButton>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button type="submit" size="icon" variant="subtle">
<ExternalLinkIcon />
<span className="sr-only">Connect to port</span>
</Button>
</TooltipTrigger>
<TooltipContent>Connect to port</TooltipContent>
</Tooltip>
</TooltipProvider>
</form>
</Stack>
</Stack>
@@ -369,21 +358,29 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
alignItems="center"
>
{canSharePorts && (
<MUIButton
size="small"
variant="text"
onClick={async () => {
await upsertSharedPortMutation.mutateAsync({
agent_name: agent.name,
port: port.port,
protocol: listeningPortProtocol,
share_level: "authenticated",
});
await sharedPortsQuery.refetch();
}}
>
Share
</MUIButton>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="subtle"
onClick={async () => {
await upsertSharedPortMutation.mutateAsync({
agent_name: agent.name,
port: port.port,
protocol: listeningPortProtocol,
share_level: "authenticated",
});
await sharedPortsQuery.refetch();
}}
>
<ShareIcon />
<span className="sr-only">Share</span>
</Button>
</TooltipTrigger>
<TooltipContent>Share this port</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</Stack>
</Stack>
@@ -483,10 +480,9 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
)}
</Select>
</FormControl>
<MUIButton
size="small"
variant="text"
css={styles.deleteButton}
<Button
size="sm"
variant="subtle"
onClick={async () => {
await deleteSharedPortMutation.mutateAsync({
agent_name: agent.name,
@@ -502,7 +498,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
color: theme.palette.text.primary,
}}
/>
</MUIButton>
</Button>
</Stack>
</Stack>
);
@@ -617,11 +613,6 @@ const styles = {
},
}),
deleteButton: () => ({
minWidth: 30,
padding: 0,
}),
newPortForm: (theme) => ({
border: `1px solid ${theme.palette.divider}`,
borderRadius: "4px",
@@ -1,5 +1,5 @@
import type { Interpolation, Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import { Button } from "components/Button/Button";
import { CodeExample } from "components/CodeExample/CodeExample";
import {
HelpTooltipLink,
@@ -34,12 +34,12 @@ export const AgentSSHButton: FC<AgentSSHButtonProps> = ({
<Popover>
<PopoverTrigger>
<Button
size="small"
variant="text"
endIcon={<ChevronDownIcon className="size-4" />}
size="sm"
variant="subtle"
css={{ fontSize: 13, padding: "8px 12px" }}
>
Connect via SSH
<ChevronDownIcon className="size-4 ml-2" />
</Button>
</PopoverTrigger>
@@ -96,12 +96,12 @@ export const AgentDevcontainerSSHButton: FC<
<Popover>
<PopoverTrigger>
<Button
size="small"
variant="text"
endIcon={<ChevronDownIcon className="size-4" />}
size="sm"
variant="subtle"
css={{ fontSize: 13, padding: "8px 12px" }}
>
Connect via SSH
<ChevronDownIcon className="size-4 ml-2" />
</Button>
</PopoverTrigger>
@@ -1,7 +1,7 @@
import type { Interpolation, Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
import type { TemplateExample } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { Pill } from "components/Pill/Pill";
import type { FC, HTMLAttributes } from "react";
@@ -55,12 +55,10 @@ export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
</div>
<div css={styles.useButtonContainer}>
<Button
component={RouterLink}
fullWidth
to={`/templates/new?exampleId=${example.id}`}
>
Use template
<Button asChild className="w-full">
<RouterLink to={`/templates/new?exampleId=${example.id}`}>
Use template
</RouterLink>
</Button>
</div>
</div>
+4 -4
View File
@@ -1,11 +1,11 @@
import { useTheme } from "@emotion/react";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import Paper from "@mui/material/Paper";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import { createChat } from "api/queries/chats";
import type { Chat } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { Margins } from "components/Margins/Margins";
import { useAuthenticated } from "hooks";
import { SendIcon } from "lucide-react";
@@ -89,19 +89,19 @@ const ChatLanding: FC = () => {
sx={{ mb: 2 }}
>
<Button
variant="outlined"
variant="outline"
onClick={() => setInput("Help me work on issue #...")}
>
Work on Issue
</Button>
<Button
variant="outlined"
variant="outline"
onClick={() => setInput("Help me build a template for...")}
>
Build a Template
</Button>
<Button
variant="outlined"
variant="outline"
onClick={() => setInput("Help me start a new project using...")}
>
Start a Project
@@ -1,5 +1,4 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import MuiButton from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { requestOneTimePassword } from "api/queries/users";
import { ErrorAlert } from "components/Alert/ErrorAlert";
@@ -98,15 +97,9 @@ const RequestOTP: FC<RequestOTPProps> = ({
<Spinner loading={isRequesting} />
Reset password
</Button>
<MuiButton
component={RouterLink}
size="large"
fullWidth
variant="text"
to="/login"
>
Cancel
</MuiButton>
<Button asChild size="lg" variant="outline" className="w-full">
<RouterLink to="/login">Cancel</RouterLink>
</Button>
</Stack>
</Stack>
</fieldset>
@@ -151,9 +144,9 @@ const RequestOTPSuccess: FC<{ email: string }> = ({ email }) => {
Contact your deployment administrator if you encounter issues.
</p>
<MuiButton component={RouterLink} to="/login">
Back to login
</MuiButton>
<Button asChild variant="default">
<RouterLink to="/login">Back to login</RouterLink>
</Button>
</div>
</div>
);
@@ -1,7 +1,7 @@
import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined";
import Button from "@mui/material/Button";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { Button } from "components/Button/Button";
import { CheckIcon } from "lucide-react";
import { type FC, useRef, useState } from "react";
@@ -38,9 +38,10 @@ export const IntervalMenu: FC<IntervalMenuProps> = ({ value, onChange }) => {
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
onClick={() => setOpen(true)}
endIcon={<ExpandMoreOutlined />}
variant="outline"
>
{insightsIntervals[value].label}
<ExpandMoreOutlined className="size-icon-xs ml-1" />
</Button>
<Menu
id="interval-menu"
@@ -1,7 +1,7 @@
import type { Interpolation, Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
import type { TemplateExample } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { CodeExample } from "components/CodeExample/CodeExample";
import { Stack } from "components/Stack/Stack";
import { TableEmpty } from "components/TableEmpty/TableEmpty";
@@ -78,13 +78,10 @@ export const EmptyTemplates: FC<EmptyTemplatesProps> = ({
))}
</div>
<Button
size="small"
component={RouterLink}
to="/starter-templates"
css={{ borderRadius: 9999 }}
>
View all starter templates
<Button size="sm" asChild css={{ borderRadius: 9999 }}>
<RouterLink to="/starter-templates">
View all starter templates
</RouterLink>
</Button>
</Stack>
}
@@ -1,6 +1,5 @@
import type { Interpolation, Theme } from "@emotion/react";
import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined";
import MuiButton from "@mui/material/Button";
import Skeleton from "@mui/material/Skeleton";
import { hasError, isApiValidationError } from "api/errors";
import type { Template, TemplateExample } from "api/typesGenerated";
@@ -44,7 +43,7 @@ import { PlusIcon } from "lucide-react";
import { linkToTemplate, useLinks } from "modules/navigation";
import type { WorkspacePermissions } from "modules/permissions/workspaces";
import type { FC } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import { createDayString } from "utils/createDayString";
import { docs } from "utils/docs";
import {
@@ -163,19 +162,20 @@ const TemplateRow: FC<TemplateRowProps> = ({
<DeprecatedBadge />
) : workspacePermissions?.[template.organization_id]
?.createWorkspaceForUserID ? (
<MuiButton
size="small"
css={styles.actionButton}
className="actionButton"
startIcon={<ArrowForwardOutlined />}
<Button
asChild
variant="outline"
size="sm"
title={`Create a workspace using the ${template.display_name} template`}
onClick={(e) => {
e.stopPropagation();
navigate(`${templatePageLink}/workspace`);
}}
>
Create Workspace
</MuiButton>
<RouterLink to={`${templatePageLink}/workspace`}>
<ArrowForwardOutlined />
Create Workspace
</RouterLink>
</Button>
) : null}
</TableCell>
</TableRow>
@@ -204,18 +204,20 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
const isLoading = !templates;
const isEmpty = templates && templates.length === 0;
const createTemplateAction = (
<Button asChild size="lg">
<Link to="/starter-templates">
<PlusIcon />
New template
</Link>
</Button>
);
return (
<Margins>
<PageHeader actions={canCreateTemplates && createTemplateAction}>
<PageHeader
actions={
canCreateTemplates && (
<Button asChild size="lg">
<RouterLink to="/starter-templates">
<PlusIcon />
New template
</RouterLink>
</Button>
)
}
>
<PageHeaderTitle>
<Stack spacing={1} direction="row" alignItems="center">
Templates