mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
Reusable Table Icon Button
This commit is contained in:
parent
4d39a1a18f
commit
08d3b9f58b
5 changed files with 184 additions and 24 deletions
|
|
@ -3,7 +3,7 @@ import TeamInfoView from "@/components/team/team_info";
|
|||
import TeamSSOSettings from "@/components/TeamSSOSettings";
|
||||
import { isProxyAdminRole } from "@/utils/roles";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { ChevronDownIcon, ChevronRightIcon, PencilAltIcon, RefreshIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { ChevronDownIcon, ChevronRightIcon, RefreshIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionBody,
|
||||
|
|
@ -33,6 +33,7 @@ import {
|
|||
import { Button as Button2, Form, Input, Modal, Select as Select2, Switch, Tooltip, Typography } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { formatNumberWithCommas } from "../utils/dataUtils";
|
||||
import AgentSelector from "./agent_management/AgentSelector";
|
||||
import { fetchTeams } from "./common_components/fetch_teams";
|
||||
import ModelAliasManager from "./common_components/ModelAliasManager";
|
||||
import PremiumLoggingSettings from "./common_components/PremiumLoggingSettings";
|
||||
|
|
@ -44,7 +45,6 @@ import {
|
|||
import type { KeyResponse, Team } from "./key_team_helpers/key_list";
|
||||
import MCPServerSelector from "./mcp_server_management/MCPServerSelector";
|
||||
import MCPToolPermissions from "./mcp_server_management/MCPToolPermissions";
|
||||
import AgentSelector from "./agent_management/AgentSelector";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
import { Organization, fetchMCPAccessGroups, getGuardrailsList, teamDeleteCall } from "./networking";
|
||||
import NumericalInput from "./shared/numerical_input";
|
||||
|
|
@ -78,6 +78,7 @@ interface EditTeamModalProps {
|
|||
|
||||
import { updateExistingKeys } from "@/utils/dataUtils";
|
||||
import DeleteResourceModal from "./common_components/DeleteResourceModal";
|
||||
import TableIconActionButton from "./common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
|
||||
import { Member, teamCreateCall, v2TeamListCall } from "./networking";
|
||||
|
||||
interface TeamInfo {
|
||||
|
|
@ -947,28 +948,21 @@ const Teams: React.FC<TeamProps> = ({
|
|||
<TableCell>
|
||||
{userRole == "Admin" ? (
|
||||
<>
|
||||
<Tooltip title="Edit team">
|
||||
{" "}
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
className="cursor-pointer hover:text-blue-600"
|
||||
onClick={() => {
|
||||
setSelectedTeamId(team.team_id);
|
||||
setEditTeam(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete team">
|
||||
{" "}
|
||||
<Icon
|
||||
onClick={() => handleDelete(team)}
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
className="cursor-pointer hover:text-red-600"
|
||||
data-testid="delete-team-button"
|
||||
/>
|
||||
</Tooltip>
|
||||
<TableIconActionButton
|
||||
variant="Edit"
|
||||
onClick={() => {
|
||||
setSelectedTeamId(team.team_id);
|
||||
setEditTeam(true);
|
||||
}}
|
||||
dataTestId="edit-team-button"
|
||||
tooltipText="Edit team"
|
||||
/>
|
||||
<TableIconActionButton
|
||||
variant="Delete"
|
||||
onClick={() => handleDelete(team)}
|
||||
dataTestId="delete-team-button"
|
||||
tooltipText="Delete team"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</TableCell>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { PencilAltIcon } from "@heroicons/react/outline";
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import BaseActionButton from "./BaseActionButton";
|
||||
|
||||
describe("BaseActionButton", () => {
|
||||
it("should render", () => {
|
||||
const onClick = vi.fn();
|
||||
render(<BaseActionButton icon={PencilAltIcon} onClick={onClick} dataTestId="test-button" />);
|
||||
expect(screen.getByTestId("test-button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onClick when clicked", () => {
|
||||
const onClick = vi.fn();
|
||||
render(<BaseActionButton icon={PencilAltIcon} onClick={onClick} dataTestId="test-button" />);
|
||||
const button = screen.getByTestId("test-button");
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { cx } from "@/lib/cva.config";
|
||||
import { Icon } from "@tremor/react";
|
||||
import React from "react";
|
||||
|
||||
interface BaseActionButtonProps {
|
||||
icon: React.ComponentType<React.ComponentProps<"svg">>;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
dataTestId?: string;
|
||||
}
|
||||
|
||||
export default function BaseActionButton({ icon, onClick, className, disabled, dataTestId }: BaseActionButtonProps) {
|
||||
return disabled ? (
|
||||
<Icon icon={icon} size="sm" className={"opacity-50 cursor-not-allowed"} data-testid={dataTestId} />
|
||||
) : (
|
||||
<Icon
|
||||
icon={icon}
|
||||
size="sm"
|
||||
onClick={onClick}
|
||||
className={cx("cursor-pointer", className)}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import TableIconActionButton, { TableIconActionButtonMap } from "./TableIconActionButton";
|
||||
|
||||
describe("TableIconActionButton", () => {
|
||||
Object.keys(TableIconActionButtonMap).forEach((variant) => {
|
||||
it(`should render ${variant} button`, () => {
|
||||
render(<TableIconActionButton variant={variant} onClick={() => {}} dataTestId="test-button" />);
|
||||
expect(screen.getByTestId("test-button")).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId("test-button")).toHaveClass(TableIconActionButtonMap[variant].className!);
|
||||
});
|
||||
});
|
||||
|
||||
it("should have a tooltip", () => {
|
||||
render(<TableIconActionButton variant="Edit" onClick={() => {}} dataTestId="test-button" tooltipText="Edit" />);
|
||||
const button = screen.getByTestId("test-button");
|
||||
const tooltipWrapper = button.closest("span");
|
||||
expect(tooltipWrapper).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show tooltip when tooltipText is provided", async () => {
|
||||
render(
|
||||
<TableIconActionButton variant="Edit" onClick={() => {}} dataTestId="test-button" tooltipText="Edit item" />,
|
||||
);
|
||||
const button = screen.getByTestId("test-button");
|
||||
const buttonWrapper = button.closest("span");
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseEnter(buttonWrapper!);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Edit item")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render disabled state with disabled styling", () => {
|
||||
render(
|
||||
<TableIconActionButton variant="Edit" onClick={() => {}} dataTestId="test-button" disabled tooltipText="Edit" />,
|
||||
);
|
||||
const button = screen.getByTestId("test-button");
|
||||
expect(button).toHaveClass("opacity-50");
|
||||
expect(button).toHaveClass("cursor-not-allowed");
|
||||
});
|
||||
|
||||
it("should show disabledTooltipText when disabled and disabledTooltipText is provided", async () => {
|
||||
render(
|
||||
<TableIconActionButton
|
||||
variant="Edit"
|
||||
onClick={() => {}}
|
||||
dataTestId="test-button"
|
||||
disabled
|
||||
tooltipText="Edit"
|
||||
disabledTooltipText="Cannot edit"
|
||||
/>,
|
||||
);
|
||||
const button = screen.getByTestId("test-button");
|
||||
const buttonWrapper = button.closest("span");
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseEnter(buttonWrapper!);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Cannot edit")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { PencilAltIcon, PlayIcon, RefreshIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { Tooltip } from "antd";
|
||||
import BaseActionButton from "../BaseActionButton";
|
||||
|
||||
export interface TableIconActionButtonProps {
|
||||
onClick: () => void;
|
||||
tooltipText?: string;
|
||||
disabled?: boolean;
|
||||
disabledTooltipText?: string;
|
||||
dataTestId?: string;
|
||||
variant: keyof typeof TableIconActionButtonMap;
|
||||
}
|
||||
|
||||
export interface TableIconActionButtonBaseProps {
|
||||
icon: React.ComponentType<React.ComponentProps<"svg">>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TableIconActionButtonMap: Record<string, TableIconActionButtonBaseProps> = {
|
||||
Edit: { icon: PencilAltIcon, className: "hover:text-blue-600" },
|
||||
Delete: { icon: TrashIcon, className: "hover:text-red-600" },
|
||||
Test: { icon: PlayIcon, className: "hover:text-blue-600" },
|
||||
Regenerate: { icon: RefreshIcon, className: "hover:text-green-600" },
|
||||
};
|
||||
|
||||
export default function TableIconActionButton({
|
||||
onClick,
|
||||
tooltipText,
|
||||
disabled = false,
|
||||
disabledTooltipText,
|
||||
dataTestId,
|
||||
variant,
|
||||
}: TableIconActionButtonProps) {
|
||||
const { icon, className } = TableIconActionButtonMap[variant];
|
||||
return (
|
||||
<Tooltip title={disabled ? disabledTooltipText : tooltipText}>
|
||||
<span>
|
||||
<BaseActionButton
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
dataTestId={dataTestId}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue