mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
refactor(ui): reuse CopyButton and lift TooltipProvider in CopyableBadge
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
aa8c64555a
commit
60dbafa255
3 changed files with 30 additions and 51 deletions
|
|
@ -27,6 +27,7 @@ import ObjectPermissionsView from "../object_permissions_view";
|
|||
import { CopyableBadge } from "../shared/copyable_badge";
|
||||
import NumericalInput from "../shared/numerical_input";
|
||||
import MemberModal from "../team/EditMembership";
|
||||
import { TooltipProvider } from "../ui/tooltip";
|
||||
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
|
||||
|
||||
interface OrganizationInfoProps {
|
||||
|
|
@ -302,11 +303,13 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
|
|||
</Card>
|
||||
<Card>
|
||||
<Text>Teams</Text>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{orgData.teams?.map((team, index) => (
|
||||
<CopyableBadge key={index} value={teamAliasMap[team.team_id] || team.team_id} />
|
||||
))}
|
||||
</div>
|
||||
<TooltipProvider delay={300}>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{orgData.teams?.map((team, index) => (
|
||||
<CopyableBadge key={index} value={teamAliasMap[team.team_id] || team.team_id} />
|
||||
))}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</Card>
|
||||
|
||||
<ObjectPermissionsView
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { CopyableBadge } from "./copyable_badge";
|
||||
|
||||
const { copyToClipboardMock } = vi.hoisted(() => ({ copyToClipboardMock: vi.fn() }));
|
||||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
vi.mock("@/utils/dataUtils", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/utils/dataUtils")>()),
|
||||
copyToClipboard: copyToClipboardMock,
|
||||
}));
|
||||
beforeEach(() => {
|
||||
writeText.mockClear();
|
||||
Object.defineProperty(navigator, "clipboard", {
|
||||
configurable: true,
|
||||
value: { writeText },
|
||||
});
|
||||
});
|
||||
|
||||
describe("CopyableBadge", () => {
|
||||
it("renders the value and truncates it", () => {
|
||||
|
|
@ -19,14 +21,12 @@ describe("CopyableBadge", () => {
|
|||
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" />);
|
||||
it("copies the full value when the copy button is clicked", async () => {
|
||||
render(<CopyableBadge value="team-alias-long-name" />);
|
||||
|
||||
await user.click(screen.getByTestId("team-badge"));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Copy team-alias-long-name" }));
|
||||
|
||||
expect(copyToClipboardMock).toHaveBeenCalledWith("team-alias-long-name");
|
||||
await waitFor(() => expect(writeText).toHaveBeenCalledWith("team-alias-long-name"));
|
||||
});
|
||||
|
||||
it("exposes a copy affordance via accessible label", () => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
"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 CopyButton from "@/components/shared/CopyButton";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/cva.config";
|
||||
import { copyToClipboard } from "@/utils/dataUtils";
|
||||
|
||||
interface CopyableBadgeProps {
|
||||
value: string;
|
||||
|
|
@ -21,37 +19,15 @@ export function CopyableBadge({
|
|||
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}>
|
||||
<Tag color={color} className="m-0 inline-flex max-w-full items-center gap-1" data-testid={dataTestId}>
|
||||
<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 className="max-w-full cursor-default border-0 bg-transparent p-0">
|
||||
<span className={cn("block truncate", maxWidthClassName)}>{value}</span>
|
||||
</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>
|
||||
<TooltipContent>{value}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<CopyButton value={value} label={`Copy ${value}`} iconClassName="size-3" />
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue