feat(ui): truncate team names with copy tooltip on org info page

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
ryan 2026-07-22 23:28:15 +00:00
parent c2c12d70b5
commit aa8c64555a
3 changed files with 100 additions and 3 deletions

View file

@ -24,6 +24,7 @@ import {
organizationUpdateCall,
} from "../networking";
import ObjectPermissionsView from "../object_permissions_view";
import { CopyableBadge } from "../shared/copyable_badge";
import NumericalInput from "../shared/numerical_input";
import MemberModal from "../team/EditMembership";
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
@ -303,9 +304,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
<Text>Teams</Text>
<div className="mt-2 flex flex-wrap gap-2">
{orgData.teams?.map((team, index) => (
<Badge key={index} color="red">
{teamAliasMap[team.team_id] || team.team_id}
</Badge>
<CopyableBadge key={index} value={teamAliasMap[team.team_id] || team.team_id} />
))}
</div>
</Card>

View file

@ -0,0 +1,41 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { CopyableBadge } from "./copyable_badge";
const { copyToClipboardMock } = vi.hoisted(() => ({ copyToClipboardMock: vi.fn() }));
vi.mock("@/utils/dataUtils", async (importOriginal) => ({
...(await importOriginal<typeof import("@/utils/dataUtils")>()),
copyToClipboard: copyToClipboardMock,
}));
describe("CopyableBadge", () => {
it("renders the value and truncates it", () => {
render(<CopyableBadge value="app-aigateway-inference-producttech-product-default" />);
const label = screen.getByText("app-aigateway-inference-producttech-product-default");
expect(label.className).toContain("truncate");
expect(label.className).toContain("max-w-[220px]");
});
it("copies the full value when clicked", async () => {
copyToClipboardMock.mockResolvedValue(true);
const user = userEvent.setup();
render(<CopyableBadge value="team-alias-long-name" dataTestId="team-badge" />);
await user.click(screen.getByTestId("team-badge"));
expect(copyToClipboardMock).toHaveBeenCalledWith("team-alias-long-name");
});
it("exposes a copy affordance via accessible label", () => {
render(<CopyableBadge value="my-team" />);
expect(screen.getByRole("button", { name: "Copy my-team" })).toBeInTheDocument();
});
it("honors a custom max width", () => {
render(<CopyableBadge value="short" maxWidthClassName="max-w-[120px]" />);
expect(screen.getByText("short").className).toContain("max-w-[120px]");
});
});

View file

@ -0,0 +1,57 @@
"use client";
import { Tag } from "antd";
import { Check, Copy } from "lucide-react";
import * as React from "react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/cva.config";
import { copyToClipboard } from "@/utils/dataUtils";
interface CopyableBadgeProps {
value: string;
color?: string;
maxWidthClassName?: string;
dataTestId?: string;
}
export function CopyableBadge({
value,
color = "red",
maxWidthClassName = "max-w-[220px]",
dataTestId,
}: CopyableBadgeProps) {
const [copied, setCopied] = React.useState(false);
const handleCopy = async () => {
const success = await copyToClipboard(value);
if (success) {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
return (
<TooltipProvider delay={300}>
<Tooltip>
<TooltipTrigger
aria-label={`Copy ${value}`}
data-testid={dataTestId}
onClick={handleCopy}
className="inline-flex max-w-full cursor-pointer border-0 bg-transparent p-0"
>
<Tag color={color} className="m-0 max-w-full">
<span className={cn("block truncate", maxWidthClassName)}>{value}</span>
</Tag>
</TooltipTrigger>
<TooltipContent>
<span className="inline-flex items-center gap-1.5">
{copied ? <Check className="size-3 shrink-0" /> : <Copy className="size-3 shrink-0" />}
<span className="font-mono break-all">{value}</span>
<span className="opacity-70">{copied ? "Copied" : "Click to copy"}</span>
</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}