mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: show sharing info at bottom of AppLink's tooltip (#21197)
for #19974 `ShareIcon`, which returns a MUI tooltip, had these problems: 1. There was no Storybook story which rendered the "Open external URL" tooltip 2. `AppLink` renders a button. If it has tooltip text, this button is also a Radix `TooltipTrigger`. Since `ShareIcon` is rendered as a child of the `AppLink` button, `ShareIcon`'s tooltip (a nested tooltip) was never appearing. I did try turning `ShareIcon` into a Radix tooltip as well, but I still couldn't get the nested tooltip's text to appear, and nested tooltips are not very accessible. AFAIK there's no way to focus a child element of a button. I've deleted the separate `ShareIcon` component and moved `ShareIcon`'s tooltip text to the bottom of `AppLink`'s tooltip: ## before <img width="175" height="121" alt="image" src="https://github.com/user-attachments/assets/ad17927e-c3d1-499b-83f8-a5832b777305" /> ^The `UsersIcon` on the right is a MUI tooltip trigger, but it can't receive focus. It's supposed to show the text "Shared with all authenticated users" when focused ## after <img width="228" height="121" alt="image" src="https://github.com/user-attachments/assets/adc202c1-57cd-4a80-8f94-f7f32897d286" />
This commit is contained in:
@@ -77,6 +77,19 @@ export const ExternalAppNotInstalled: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const ExternalAppShareable: Story = {
|
||||
args: {
|
||||
workspace: MockWorkspace,
|
||||
app: {
|
||||
...MockWorkspaceApp,
|
||||
url: "vscode://open",
|
||||
external: true,
|
||||
sharing_level: "authenticated",
|
||||
},
|
||||
agent: MockWorkspaceAgent,
|
||||
},
|
||||
};
|
||||
|
||||
export const SharingLevelOwner: Story = {
|
||||
args: {
|
||||
workspace: MockWorkspace,
|
||||
|
||||
@@ -9,13 +9,19 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { useProxy } from "contexts/ProxyContext";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import {
|
||||
Building2Icon,
|
||||
CircleAlertIcon,
|
||||
GlobeIcon,
|
||||
type LucideIcon,
|
||||
SquareArrowOutUpRightIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
import { isExternalApp, needsSessionToken } from "modules/apps/apps";
|
||||
import { useAppLink } from "modules/apps/useAppLink";
|
||||
import { type FC, type ReactNode, useState } from "react";
|
||||
import { AgentButton } from "../AgentButton";
|
||||
import { BaseIcon } from "./BaseIcon";
|
||||
import { ShareIcon } from "./ShareIcon";
|
||||
|
||||
export const DisplayAppNameMap: Record<TypesGen.DisplayApp, string> = {
|
||||
port_forwarding_helper: "Ports",
|
||||
@@ -115,13 +121,24 @@ export const AppLink: FC<AppLinkProps> = ({
|
||||
}
|
||||
|
||||
const canShare = app.sharing_level !== "owner";
|
||||
const { shareTooltip, shareIcon: ShareIcon } = canShare
|
||||
? app.external
|
||||
? {
|
||||
shareTooltip: "Open external URL",
|
||||
shareIcon: SquareArrowOutUpRightIcon,
|
||||
}
|
||||
: shareDetails[app.sharing_level]
|
||||
: {
|
||||
shareTooltip: null,
|
||||
shareIcon: null,
|
||||
};
|
||||
|
||||
const button = grouped ? (
|
||||
<DropdownMenuItem asChild>
|
||||
<a href={canClick ? link.href : undefined} onClick={link.onClick}>
|
||||
{icon}
|
||||
{link.label}
|
||||
{canShare && <ShareIcon app={app} />}
|
||||
{ShareIcon && <ShareIcon />}
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
@@ -129,7 +146,7 @@ export const AppLink: FC<AppLinkProps> = ({
|
||||
<a href={canClick ? link.href : undefined} onClick={link.onClick}>
|
||||
{icon}
|
||||
{link.label}
|
||||
{canShare && <ShareIcon app={app} />}
|
||||
{ShareIcon && <ShareIcon />}
|
||||
</a>
|
||||
</AgentButton>
|
||||
);
|
||||
@@ -146,6 +163,7 @@ export const AppLink: FC<AppLinkProps> = ({
|
||||
{app.tooltip}
|
||||
</Markdown>
|
||||
) : null}
|
||||
{shareTooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -153,3 +171,23 @@ export const AppLink: FC<AppLinkProps> = ({
|
||||
|
||||
return button;
|
||||
};
|
||||
|
||||
const shareDetails: {
|
||||
[SharingLevel in TypesGen.WorkspaceAppSharingLevel as Exclude<
|
||||
SharingLevel,
|
||||
"owner"
|
||||
>]: { shareTooltip: string; shareIcon: LucideIcon };
|
||||
} = {
|
||||
authenticated: {
|
||||
shareTooltip: "Shared with all authenticated users",
|
||||
shareIcon: UsersIcon,
|
||||
},
|
||||
organization: {
|
||||
shareTooltip: "Shared with organization members",
|
||||
shareIcon: Building2Icon,
|
||||
},
|
||||
public: {
|
||||
shareTooltip: "Shared publicly",
|
||||
shareIcon: GlobeIcon,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import {
|
||||
Building2Icon,
|
||||
GlobeIcon,
|
||||
SquareArrowOutUpRightIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
interface ShareIconProps {
|
||||
app: TypesGen.WorkspaceApp;
|
||||
}
|
||||
|
||||
export const ShareIcon = ({ app }: ShareIconProps) => {
|
||||
if (app.external) {
|
||||
return (
|
||||
<Tooltip title="Open external URL">
|
||||
<SquareArrowOutUpRightIcon />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
if (app.sharing_level === "authenticated") {
|
||||
return (
|
||||
<Tooltip title="Shared with all authenticated users">
|
||||
<UsersIcon />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
if (app.sharing_level === "organization") {
|
||||
return (
|
||||
<Tooltip title="Shared with organization members">
|
||||
<Building2Icon />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
if (app.sharing_level === "public") {
|
||||
return (
|
||||
<Tooltip title="Shared publicly">
|
||||
<GlobeIcon />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
Reference in New Issue
Block a user