Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
refactor: update app buttons to use the new button component
  • Loading branch information
BrunoQuaresma committed May 5, 2025
commit 5734f98eff516d1ce5de7face58ddd8faf06e31f
11 changes: 6 additions & 5 deletions site/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const buttonVariants = cva(
text-sm font-semibold font-medium cursor-pointer no-underline
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link
disabled:pointer-events-none disabled:text-content-disabled
[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:p-0.5`,
[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:p-0.5
[&_img]:pointer-events-none [&_img]:shrink-0 [&_img]:p-0.5`,
{
variants: {
variant: {
Expand All @@ -28,11 +29,11 @@ const buttonVariants = cva(
},

size: {
lg: "min-w-20 h-10 px-3 py-2 [&_svg]:size-icon-lg",
sm: "min-w-20 h-8 px-2 py-1.5 text-xs [&_svg]:size-icon-sm",
lg: "min-w-20 h-10 px-3 py-2 [&_svg]:size-icon-lg [&_img]:size-icon-lg",
sm: "min-w-20 h-8 px-2 py-1.5 text-xs [&_svg]:size-icon-sm [&_img]:size-icon-sm",
xs: "min-w-8 py-1 px-2 text-2xs rounded-md",
icon: "size-8 px-1.5 [&_svg]:size-icon-sm",
"icon-lg": "size-10 px-2 [&_svg]:size-icon-lg",
icon: "size-8 px-1.5 [&_svg]:size-icon-sm [&_img]:size-icon-sm",
"icon-lg": "size-10 px-2 [&_svg]:size-icon-lg [&_img]:size-icon-lg",
},
},
defaultVariants: {
Expand Down
39 changes: 24 additions & 15 deletions site/src/components/ExternalImage/ExternalImage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { useTheme } from "@emotion/react";
import { type ImgHTMLAttributes, forwardRef } from "react";
import { getExternalImageStylesFromUrl } from "theme/externalImages";
import {
type ExternalImageModeStyles,
getExternalImageStylesFromUrl,
} from "theme/externalImages";

export const ExternalImage = forwardRef<
HTMLImageElement,
ImgHTMLAttributes<HTMLImageElement>
>((attrs, ref) => {
const theme = useTheme();
type ExternalImageProps = ImgHTMLAttributes<HTMLImageElement> & {
mode?: ExternalImageModeStyles;
};

return (
// biome-ignore lint/a11y/useAltText: no reasonable alt to provide
<img
ref={ref}
css={getExternalImageStylesFromUrl(theme.externalImages, attrs.src)}
{...attrs}
/>
);
});
export const ExternalImage = forwardRef<HTMLImageElement, ExternalImageProps>(
({ mode, ...imgProps }, ref) => {
const theme = useTheme();

return (
// biome-ignore lint/a11y/useAltText: alt should be passed in as a prop
<img
ref={ref}
css={getExternalImageStylesFromUrl(
mode ?? theme.externalImages,
imgProps.src,
)}
{...imgProps}
/>
);
},
);
31 changes: 0 additions & 31 deletions site/src/modules/resources/AgentButton.tsx

This file was deleted.

40 changes: 21 additions & 19 deletions site/src/modules/resources/AgentDevcontainerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Link from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import type {
Workspace,
WorkspaceAgent,
Expand All @@ -8,10 +6,16 @@ import type {
import { ExternalLinkIcon } from "lucide-react";
import type { FC } from "react";
import { portForwardURL } from "utils/portForward";
import { AgentButton } from "./AgentButton";
import { AgentDevcontainerSSHButton } from "./SSHButton/SSHButton";
import { TerminalLink } from "./TerminalLink/TerminalLink";
import { VSCodeDevContainerButton } from "./VSCodeDevContainerButton/VSCodeDevContainerButton";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { Button } from "components/Button/Button";

type AgentDevcontainerCardProps = {
agent: WorkspaceAgent;
Expand Down Expand Up @@ -74,29 +78,27 @@ export const AgentDevcontainerCard: FC<AgentDevcontainerCardProps> = ({
const linkDest = hasHostBind
? portForwardURL(
wildcardHostname,
port.host_port!,
port.host_port,
agent.name,
workspace.name,
workspace.owner_name,
location.protocol === "https" ? "https" : "http",
)
: "";
return (
<Tooltip key={portLabel} title={helperText}>
<span>
<Link
key={portLabel}
color="inherit"
component={AgentButton}
underline="none"
startIcon={<ExternalLinkIcon className="size-icon-sm" />}
disabled={!hasHostBind}
href={linkDest}
>
{portLabel}
</Link>
</span>
</Tooltip>
<TooltipProvider key={portLabel}>
<Tooltip>
<TooltipTrigger>
<Button disabled={!hasHostBind} asChild>
<a href={linkDest}>
<ExternalLinkIcon />
{portLabel}
</a>
</Button>
</TooltipTrigger>
<TooltipContent>{helperText}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
})}
</div>
Expand Down
50 changes: 30 additions & 20 deletions site/src/modules/resources/AppLink/AppLink.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { useTheme } from "@emotion/react";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import CircularProgress from "@mui/material/CircularProgress";
import Link from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import { API } from "api/api";
import type * as TypesGen from "api/typesGenerated";
import { displayError } from "components/GlobalSnackbar/utils";
import { useProxy } from "contexts/ProxyContext";
import { type FC, type MouseEvent, useState } from "react";
import { type FC, useState } from "react";
import { createAppLinkHref } from "utils/apps";
import { generateRandomString } from "utils/random";
import { AgentButton } from "../AgentButton";
import { BaseIcon } from "./BaseIcon";
import { ShareIcon } from "./ShareIcon";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { Button } from "components/Button/Button";

export const DisplayAppNameMap: Record<TypesGen.DisplayApp, string> = {
port_forwarding_helper: "Ports",
Expand Down Expand Up @@ -112,22 +116,13 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
canClick = false;
}

const isPrivateApp = app.sharing_level === "owner";
const canShare = app.sharing_level !== "owner";

return (
<Tooltip title={primaryTooltip}>
<Link
color="inherit"
component={AgentButton}
startIcon={icon}
endIcon={isPrivateApp ? undefined : <ShareIcon app={app} />}
disabled={!canClick}
const button = (
<Button disabled={!canClick} asChild>
<a
href={href}
css={{
pointerEvents: canClick ? undefined : "none",
textDecoration: "none !important",
}}
onClick={async (event: MouseEvent<HTMLElement>) => {
onClick={async (event) => {
if (!canClick) {
return;
}
Expand Down Expand Up @@ -187,8 +182,23 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
}
}}
>
{icon}
{appDisplayName}
</Link>
</Tooltip>
{canShare && <ShareIcon app={app} />}
</a>
</Button>
);

if (primaryTooltip) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent>{primaryTooltip}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

return button;
};
5 changes: 5 additions & 0 deletions site/src/modules/resources/AppLink/BaseIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { useTheme } from "@emotion/react";
import ComputerIcon from "@mui/icons-material/Computer";
import type { WorkspaceApp } from "api/typesGenerated";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import type { FC } from "react";
import { forDarkThemes, forLightThemes } from "theme/externalImages";

interface BaseIconProps {
app: WorkspaceApp;
onIconPathError?: () => void;
}

export const BaseIcon: FC<BaseIconProps> = ({ app, onIconPathError }) => {
const theme = useTheme();

return app.icon ? (
<ExternalImage
mode={theme.palette.mode === "dark" ? forLightThemes : forDarkThemes}
alt={`${app.display_name} Icon`}
src={app.icon}
style={{ pointerEvents: "none" }}
Expand Down
37 changes: 17 additions & 20 deletions site/src/modules/resources/TerminalLink/TerminalLink.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Link from "@mui/material/Link";
import { TerminalIcon } from "components/Icons/TerminalIcon";
import type { FC, MouseEvent } from "react";
import { generateRandomString } from "utils/random";
import { AgentButton } from "../AgentButton";
import { DisplayAppNameMap } from "../AppLink/AppLink";
import { Button } from "components/Button/Button";

const Language = {
terminalTitle: (identifier: string): string => `Terminal - ${identifier}`,
Expand Down Expand Up @@ -39,23 +38,21 @@ export const TerminalLink: FC<TerminalLinkProps> = ({
}/terminal?${params.toString()}`;

return (
<Link
underline="none"
color="inherit"
component={AgentButton}
startIcon={<TerminalIcon />}
href={href}
onClick={(event: MouseEvent<HTMLElement>) => {
event.preventDefault();
window.open(
href,
Language.terminalTitle(generateRandomString(12)),
"width=900,height=600",
);
}}
data-testid="terminal"
>
{DisplayAppNameMap.web_terminal}
</Link>
<Button asChild>
<a
href={href}
onClick={(event: MouseEvent<HTMLElement>) => {
event.preventDefault();
window.open(
href,
Language.terminalTitle(generateRandomString(12)),
"width=900,height=600",
);
}}
>
<TerminalIcon />
{DisplayAppNameMap.web_terminal}
</a>
</Button>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import type { DisplayApp } from "api/typesGenerated";
import { VSCodeIcon } from "components/Icons/VSCodeIcon";
import { VSCodeInsidersIcon } from "components/Icons/VSCodeInsidersIcon";
import { type FC, useRef, useState } from "react";
import { AgentButton } from "../AgentButton";
import { DisplayAppNameMap } from "../AppLink/AppLink";
import { Button } from "components/Button/Button";

export interface VSCodeDesktopButtonProps {
userName: string;
Expand Down Expand Up @@ -51,21 +51,20 @@ export const VSCodeDesktopButton: FC<VSCodeDesktopButtonProps> = (props) => {
<VSCodeInsidersButton {...props} />
)}

<AgentButton
<Button
aria-controls={
isVariantMenuOpen ? "vscode-variant-button-menu" : undefined
}
aria-expanded={isVariantMenuOpen ? "true" : undefined}
aria-label="select VSCode variant"
aria-haspopup="menu"
disableRipple
onClick={() => {
setIsVariantMenuOpen(true);
}}
css={{ paddingLeft: 0, paddingRight: 0 }}
>
<KeyboardArrowDownIcon css={{ fontSize: 16 }} />
</AgentButton>
</Button>
</ButtonGroup>

<Menu
Expand Down Expand Up @@ -114,8 +113,7 @@ const VSCodeButton: FC<VSCodeDesktopButtonProps> = ({
const [loading, setLoading] = useState(false);

return (
<AgentButton
startIcon={<VSCodeIcon />}
<Button
disabled={loading}
onClick={() => {
setLoading(true);
Expand Down Expand Up @@ -145,8 +143,9 @@ const VSCodeButton: FC<VSCodeDesktopButtonProps> = ({
});
}}
>
<VSCodeIcon />
{DisplayAppNameMap.vscode}
</AgentButton>
</Button>
);
};

Expand All @@ -159,8 +158,7 @@ const VSCodeInsidersButton: FC<VSCodeDesktopButtonProps> = ({
const [loading, setLoading] = useState(false);

return (
<AgentButton
startIcon={<VSCodeInsidersIcon />}
<Button
disabled={loading}
onClick={() => {
setLoading(true);
Expand Down Expand Up @@ -189,7 +187,8 @@ const VSCodeInsidersButton: FC<VSCodeDesktopButtonProps> = ({
});
}}
>
<VSCodeInsidersIcon />
{DisplayAppNameMap.vscode_insiders}
</AgentButton>
</Button>
);
};
Loading