diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index b7c578d8ec6..03de475c8c3 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -2218,11 +2218,6 @@ "count": 1 } }, - "src/components/ui/meter.tsx": { - "local/filename-pascal-case": { - "count": 1 - } - }, "src/components/ui/popover.tsx": { "local/filename-pascal-case": { "count": 1 @@ -2253,11 +2248,6 @@ "count": 1 } }, - "src/components/ui/sidebar.tsx": { - "local/filename-pascal-case": { - "count": 1 - } - }, "src/components/ui/skeleton.tsx": { "local/filename-pascal-case": { "count": 1 diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index 564f32e2573..d2a64b93384 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -17,7 +17,8 @@ "@tanstack/react-query": "5.100.7", "@tanstack/react-table": "8.21.3", "@types/papaparse": "5.5.2", - "cva": "1.0.0-beta.4", + "class-variance-authority": "0.7.1", + "clsx": "^2.1.1", "date-fns": "^4.4.0", "dayjs": "1.11.19", "jwt-decode": "4.0.0", @@ -5161,6 +5162,18 @@ "node": ">= 16" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -5299,26 +5312,6 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, - "node_modules/cva": { - "version": "1.0.0-beta.4", - "resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.4.tgz", - "integrity": "sha512-F/JS9hScapq4DBVQXcK85l9U91M6ePeXoBMSp7vypzShoefUBxjQTo3g3935PUHgQd+IW77DjbPRIxugy4/GCQ==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - }, - "peerDependencies": { - "typescript": ">= 4.5.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -12175,7 +12168,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index ff6448ad75c..ededdfb4606 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -33,7 +33,8 @@ "@tanstack/react-query": "5.100.7", "@tanstack/react-table": "8.21.3", "@types/papaparse": "5.5.2", - "cva": "1.0.0-beta.4", + "class-variance-authority": "0.7.1", + "clsx": "^2.1.1", "date-fns": "^4.4.0", "dayjs": "1.11.19", "jwt-decode": "4.0.0", diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx index b5c04029d69..9e8ea64fc45 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx @@ -5,6 +5,7 @@ import { Logo } from "@/components/molecules/logo/Logo"; import { Bot, Check, CircleCheck, Key, LayoutGrid } from "lucide-react"; import CreatedKeyDisplay from "@/components/shared/CreatedKeyDisplay"; import { Badge } from "@/components/ui/badge"; +import { StatusBadge } from "@/components/shared/table_cells/status_badge"; import { Button } from "@/components/ui/button"; import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; import { Input } from "@/components/ui/input"; @@ -764,9 +765,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok Custom / Other - - GENERIC - + For agents that don't follow a standard protocol, just needs a virtual key @@ -935,7 +934,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok )} - Recommended + diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/UserEnvVarsModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/UserEnvVarsModal.tsx index ea2addcd5f1..66b33ff2db8 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/UserEnvVarsModal.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/UserEnvVarsModal.tsx @@ -10,6 +10,7 @@ import { FormField } from "@/components/shared/form/FormField"; import { Alert, AlertTitle } from "@/components/shared/Alert"; import { PasswordInput } from "@/components/shared/PasswordInput"; import { Badge } from "@/components/ui/badge"; +import { StatusBadge } from "@/components/shared/table_cells/status_badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; @@ -133,7 +134,7 @@ const UserEnvVarsModal: React.FC = ({ server, open, acces
Set your credentials - Per-user +
{displayName}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx index aca1db0fbae..42be1b34f07 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx @@ -17,7 +17,7 @@ import { ComboboxValue, useComboboxAnchor, } from "@/components/ui/combobox"; -import { Meter, MeterIndicator, MeterTrack } from "@/components/ui/meter"; +import { Meter, MeterIndicator, MeterTrack } from "@/components/shared/Meter"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx index 6ae7b855f1e..4925e775ac7 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx @@ -94,7 +94,16 @@ export function ChatComposer({ /> )} - + { + if ((event.target as HTMLElement).closest("button")) { + return; + } + event.currentTarget.parentElement?.querySelector("[data-slot=input-group-control]")?.focus(); + }} + >
{tools}
{isLoading && onCancel ? ( diff --git a/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/add_policy_form.tsx b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/add_policy_form.tsx index 4214db277a3..403562bc7a3 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/add_policy_form.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/add_policy_form.tsx @@ -10,6 +10,7 @@ import { SearchSelect } from "@/components/shared/SearchSelect"; import { FieldGroup } from "@/components/shared/form/field"; import { FormField } from "@/components/shared/form/FormField"; import { Badge } from "@/components/ui/badge"; +import { StatusBadge } from "@/components/shared/table_cells/status_badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -466,9 +467,7 @@ const AddPolicyForm: React.FC = ({
{resolvedGuardrails.map((g) => ( - - {g} - + ))}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectDetailsPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectDetailsPage.tsx index d7a4fce4beb..f94240f3c9e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectDetailsPage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectDetailsPage.tsx @@ -9,7 +9,7 @@ import { StatusBadge } from "@/components/shared/table_cells/status_badge"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Meter, MeterIndicator, MeterTrack } from "@/components/ui/meter"; +import { Meter, MeterIndicator, MeterTrack } from "@/components/shared/Meter"; import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; import { EditProjectModal } from "./ProjectModals/EditProjectModal"; import { ProjectKeysSection } from "./ProjectKeysSection"; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EndpointUsage/components/EndpointUsageTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EndpointUsage/components/EndpointUsageTable.tsx index 384590f754d..3d19d825c0c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EndpointUsage/components/EndpointUsageTable.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EndpointUsage/components/EndpointUsageTable.tsx @@ -1,6 +1,6 @@ import React from "react"; import type { ColumnDef } from "@tanstack/react-table"; -import { Meter, MeterIndicator, MeterTrack } from "@/components/ui/meter"; +import { Meter, MeterIndicator, MeterTrack } from "@/components/shared/Meter"; import { DataTable } from "@/components/shared/DataTable"; import { MoneyCell } from "@/components/shared/table_cells"; import { MetricWithMetadata } from "@/components/UsagePage/types"; diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RoleMappings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RoleMappings.tsx index 2ea78f4b12b..966a2a9904d 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RoleMappings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RoleMappings.tsx @@ -1,7 +1,7 @@ import type { RoleMappings as RoleMappingsType } from "@/app/(dashboard)/hooks/sso/useSSOSettings"; import type { ColumnDef } from "@tanstack/react-table"; import { DataTable } from "@/components/shared/DataTable"; -import { Badge } from "@/components/ui/badge"; +import { StatusBadge } from "@/components/shared/table_cells/status_badge"; import { Card, CardContent } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Users } from "lucide-react"; @@ -34,9 +34,7 @@ export default function RoleMappings({ roleMappings }: { roleMappings: RoleMappi row.original.groups.length > 0 ? (
{row.original.groups.map((group, index) => ( - - {group} - + ))}
) : ( diff --git a/ui/litellm-dashboard/src/components/SidebarUsageCard.tsx b/ui/litellm-dashboard/src/components/SidebarUsageCard.tsx index 76240565cda..effb6c29e8e 100644 --- a/ui/litellm-dashboard/src/components/SidebarUsageCard.tsx +++ b/ui/litellm-dashboard/src/components/SidebarUsageCard.tsx @@ -2,7 +2,7 @@ import { useLicenseInfo } from "@/app/(dashboard)/hooks/license/useLicenseInfo"; import { formatExpirationStatus } from "@/utils/licenseUtils"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { Meter, MeterIndicator, MeterLabel, MeterTrack } from "@/components/ui/meter"; +import { Meter, MeterIndicator, MeterLabel, MeterTrack } from "@/components/shared/Meter"; import { useQuery } from "@tanstack/react-query"; import { Award, ChevronDown, Loader2 } from "lucide-react"; import { getRemainingUsers } from "./networking"; diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 7fef1e62a6a..b58d7bd0d02 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -20,7 +20,7 @@ import { SidebarMenuSub, SidebarSeparator, sidebarMenuButtonVariants, -} from "@/components/ui/sidebar"; +} from "@/components/shared/Sidebar"; import { Activity, BarChart3, diff --git a/ui/litellm-dashboard/src/components/shared/Alert.tsx b/ui/litellm-dashboard/src/components/shared/Alert.tsx index e69a79d4629..490fcba6ef5 100644 --- a/ui/litellm-dashboard/src/components/shared/Alert.tsx +++ b/ui/litellm-dashboard/src/components/shared/Alert.tsx @@ -1,25 +1,29 @@ import * as React from "react"; -import { type VariantProps } from "cva"; -import { cn, cva } from "@/lib/cva.config"; +import { cva, type VariantProps } from "class-variance-authority"; -const alertVariants = cva({ - base: "group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", - variants: { - variant: { - default: "bg-card text-card-foreground", - destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", - info: "border-info/20 bg-info/5 text-info *:[svg]:text-current", - success: "border-success/20 bg-success/5 text-success *:[svg]:text-current", - warning: "border-warning/20 bg-warning/5 text-warning *:[svg]:text-current", - error: - "border-destructive/20 bg-destructive/10 text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-destructive", +import { cn } from "@/lib/cva.config"; + +const alertVariants = cva( + "group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", + info: "border-info/20 bg-info/5 text-info *:[svg]:text-current", + success: "border-success/20 bg-success/5 text-success *:[svg]:text-current", + warning: "border-warning/20 bg-warning/5 text-warning *:[svg]:text-current", + error: + "border-destructive/20 bg-destructive/10 text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", }, }, - defaultVariants: { - variant: "default", - }, -}); +); type AlertProps = React.ComponentProps<"div"> & VariantProps; diff --git a/ui/litellm-dashboard/src/components/ui/meter.test.tsx b/ui/litellm-dashboard/src/components/shared/Meter.test.tsx similarity index 99% rename from ui/litellm-dashboard/src/components/ui/meter.test.tsx rename to ui/litellm-dashboard/src/components/shared/Meter.test.tsx index 70aedefee40..1fbb785e55d 100644 --- a/ui/litellm-dashboard/src/components/ui/meter.test.tsx +++ b/ui/litellm-dashboard/src/components/shared/Meter.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; -import { Meter, MeterIndicator, MeterLabel, MeterTrack } from "./meter"; +import { Meter, MeterIndicator, MeterLabel, MeterTrack } from "./Meter"; const renderMeter = (value: number, max: number) => render( diff --git a/ui/litellm-dashboard/src/components/ui/meter.tsx b/ui/litellm-dashboard/src/components/shared/Meter.tsx similarity index 91% rename from ui/litellm-dashboard/src/components/ui/meter.tsx rename to ui/litellm-dashboard/src/components/shared/Meter.tsx index ac18829efa9..26c60f3361f 100644 --- a/ui/litellm-dashboard/src/components/ui/meter.tsx +++ b/ui/litellm-dashboard/src/components/shared/Meter.tsx @@ -1,13 +1,13 @@ "use client"; import { Meter as MeterPrimitive } from "@base-ui/react/meter"; -import { type VariantProps } from "cva"; import * as React from "react"; -import { cn, cva } from "@/lib/cva.config"; +import { cva, type VariantProps } from "class-variance-authority"; -const meterIndicatorVariants = cva({ - base: "h-full rounded-full transition-[width] duration-300", +import { cn } from "@/lib/cva.config"; + +const meterIndicatorVariants = cva("h-full rounded-full transition-[width] duration-300", { variants: { tone: { default: "bg-primary", diff --git a/ui/litellm-dashboard/src/components/shared/Sidebar.test.tsx b/ui/litellm-dashboard/src/components/shared/Sidebar.test.tsx new file mode 100644 index 00000000000..8c0d2790489 --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/Sidebar.test.tsx @@ -0,0 +1,36 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { SidebarMenuButton, sidebarMenuButtonVariants } from "./Sidebar"; + +const CVA_CONFIG_KEYS = ["base", "variants", "defaultVariants"]; + +describe("sidebarMenuButtonVariants", () => { + it("emits its base classes rather than the names of its own config keys", () => { + const emitted = sidebarMenuButtonVariants({}).split(" "); + + expect(emitted).toContain("rounded-md"); + expect(emitted).toContain("text-sidebar-foreground/70"); + expect(CVA_CONFIG_KEYS.filter((key) => emitted.includes(key))).toEqual([]); + }); + + it("applies the isActive variant on top of the base classes", () => { + const active = sidebarMenuButtonVariants({ isActive: true }).split(" "); + + expect(active).toContain("bg-sidebar-accent"); + expect(active).toContain("rounded-md"); + expect(sidebarMenuButtonVariants({ isActive: false }).split(" ")).not.toContain("bg-sidebar-accent"); + }); +}); + +describe("SidebarMenuButton", () => { + it("renders the variant classes onto the button", () => { + render(Keys); + const button = screen.getByRole("button", { name: "Keys" }); + + expect(button).toHaveClass("bg-sidebar-accent", "rounded-md"); + for (const key of CVA_CONFIG_KEYS) { + expect(button).not.toHaveClass(key); + } + }); +}); diff --git a/ui/litellm-dashboard/src/components/ui/sidebar.tsx b/ui/litellm-dashboard/src/components/shared/Sidebar.tsx similarity index 91% rename from ui/litellm-dashboard/src/components/ui/sidebar.tsx rename to ui/litellm-dashboard/src/components/shared/Sidebar.tsx index a0c94ab6fee..06e19d1b674 100644 --- a/ui/litellm-dashboard/src/components/ui/sidebar.tsx +++ b/ui/litellm-dashboard/src/components/shared/Sidebar.tsx @@ -2,9 +2,10 @@ import * as React from "react"; import { Button as ButtonPrimitive } from "@base-ui/react/button"; -import { type VariantProps } from "cva"; -import { cn, cva } from "@/lib/cva.config"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/cva.config"; type SidebarContextValue = { collapsed: boolean }; const SidebarContext = React.createContext({ collapsed: false }); @@ -136,8 +137,8 @@ const SidebarMenuBadge = React.forwardRefsvg]:size-[18px] [&>svg]:shrink-0", "group-data-[collapsed=true]/sidebar:mx-auto group-data-[collapsed=true]/sidebar:size-9 group-data-[collapsed=true]/sidebar:justify-center group-data-[collapsed=true]/sidebar:gap-0 group-data-[collapsed=true]/sidebar:px-0", - ].join(" "), - variants: { - isActive: { - true: "bg-sidebar-accent text-sidebar-accent-foreground before:absolute before:inset-y-1.5 before:left-0 before:w-[3px] before:rounded-r-full before:bg-sidebar-primary group-data-[collapsed=true]/sidebar:before:hidden", - false: "", - }, - size: { - default: "h-[34px]", - sub: "h-[34px]", + ], + { + variants: { + isActive: { + true: "bg-sidebar-accent text-sidebar-accent-foreground before:absolute before:inset-y-1.5 before:left-0 before:w-[3px] before:rounded-r-full before:bg-sidebar-primary group-data-[collapsed=true]/sidebar:before:hidden", + false: "", + }, + size: { + default: "h-[34px]", + sub: "h-[34px]", + }, }, + defaultVariants: { isActive: false, size: "default" }, }, - defaultVariants: { isActive: false, size: "default" }, -}); +); type SidebarMenuButtonProps = ButtonPrimitive.Props & VariantProps; diff --git a/ui/litellm-dashboard/src/components/shared/form/field.tsx b/ui/litellm-dashboard/src/components/shared/form/field.tsx index 36ce691827c..a5bf896313a 100644 --- a/ui/litellm-dashboard/src/components/shared/form/field.tsx +++ b/ui/litellm-dashboard/src/components/shared/form/field.tsx @@ -1,11 +1,12 @@ "use client"; import * as React from "react"; -import { type VariantProps } from "cva"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; -import { cn, cva } from "@/lib/cva.config"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/cva.config"; const FieldSet = React.forwardRef>( ({ className, ...props }, ref) => ( @@ -51,8 +52,7 @@ const FieldGroup = React.forwardRef.sr-only]:w-auto", diff --git a/ui/litellm-dashboard/src/components/shared/table_cells/spend_budget_cell.tsx b/ui/litellm-dashboard/src/components/shared/table_cells/spend_budget_cell.tsx index 943b9aa766c..127c7202121 100644 --- a/ui/litellm-dashboard/src/components/shared/table_cells/spend_budget_cell.tsx +++ b/ui/litellm-dashboard/src/components/shared/table_cells/spend_budget_cell.tsx @@ -1,7 +1,7 @@ "use client"; import { InheritedBudgetHint, type InheritedBudgetGate } from "@/components/shared/InheritedBudgetHint"; -import { Meter, MeterIndicator, MeterTrack } from "@/components/ui/meter"; +import { Meter, MeterIndicator, MeterTrack } from "@/components/shared/Meter"; import { formatNumberWithCommas, getSpendString } from "@/utils/dataUtils"; interface SpendBudgetCellProps { diff --git a/ui/litellm-dashboard/src/components/shared/table_cells/status_badge.tsx b/ui/litellm-dashboard/src/components/shared/table_cells/status_badge.tsx index dbd4d511d89..cc7f32d0bd6 100644 --- a/ui/litellm-dashboard/src/components/shared/table_cells/status_badge.tsx +++ b/ui/litellm-dashboard/src/components/shared/table_cells/status_badge.tsx @@ -22,11 +22,16 @@ interface StatusBadgeProps { label: string; tooltip?: React.ReactNode; dataTestId?: string; + className?: string; } -export function StatusBadge({ tone, label, tooltip, dataTestId }: StatusBadgeProps) { +export function StatusBadge({ tone, label, tooltip, dataTestId, className }: StatusBadgeProps) { const badge = ( - + {label} ); diff --git a/ui/litellm-dashboard/src/components/ui/badge.tsx b/ui/litellm-dashboard/src/components/ui/badge.tsx index ec5417d227a..0117fd4252e 100644 --- a/ui/litellm-dashboard/src/components/ui/badge.tsx +++ b/ui/litellm-dashboard/src/components/ui/badge.tsx @@ -1,43 +1,49 @@ -import * as React from "react"; import { mergeProps } from "@base-ui/react/merge-props"; import { useRender } from "@base-ui/react/use-render"; -import { type VariantProps } from "cva"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn, cva } from "@/lib/cva.config"; +import { cn } from "@/lib/cva.config"; -const badgeVariants = cva({ - base: "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", - variants: { - variant: { - default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", - secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", - destructive: - "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", - success: "bg-success/10 text-success dark:bg-success/20 [a]:hover:bg-success/20", - warning: "bg-warning/10 text-warning dark:bg-warning/20 [a]:hover:bg-warning/20", - info: "bg-info/10 text-info dark:bg-info/20 [a]:hover:bg-info/20", - outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", - ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", - link: "text-primary underline-offset-4 hover:underline", +const badgeVariants = cva( + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", + destructive: + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", + }, + }, + defaultVariants: { + variant: "default", }, }, - defaultVariants: { - variant: "default", - }, -}); - -type BadgeProps = useRender.ComponentProps<"span"> & VariantProps; - -const Badge = React.forwardRef( - ({ className, variant = "default", render, ...props }, ref) => - useRender({ - defaultTagName: "span", - ref, - props: mergeProps<"span">({ className: cn(badgeVariants({ variant }), className) }, props), - render, - state: { slot: "badge", variant }, - }), ); -Badge.displayName = "Badge"; + +function Badge({ + className, + variant = "default", + render, + ...props +}: useRender.ComponentProps<"span"> & VariantProps) { + return useRender({ + defaultTagName: "span", + props: mergeProps<"span">( + { + className: cn(badgeVariants({ variant }), className), + }, + props, + ), + render, + state: { + slot: "badge", + variant, + }, + }); +} export { Badge, badgeVariants }; diff --git a/ui/litellm-dashboard/src/components/ui/button-group.tsx b/ui/litellm-dashboard/src/components/ui/button-group.tsx index bfbd6577044..4ad0b493e65 100644 --- a/ui/litellm-dashboard/src/components/ui/button-group.tsx +++ b/ui/litellm-dashboard/src/components/ui/button-group.tsx @@ -1,24 +1,26 @@ import { mergeProps } from "@base-ui/react/merge-props"; import { useRender } from "@base-ui/react/use-render"; -import { type VariantProps } from "cva"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn, cva } from "@/lib/cva.config"; +import { cn } from "@/lib/cva.config"; import { Separator } from "@/components/ui/separator"; -const buttonGroupVariants = cva({ - base: "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", - variants: { - orientation: { - horizontal: - "*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0", - vertical: - "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0", +const buttonGroupVariants = cva( + "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", + { + variants: { + orientation: { + horizontal: + "*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0", + vertical: + "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0", + }, + }, + defaultVariants: { + orientation: "horizontal", }, }, - defaultVariants: { - orientation: "horizontal", - }, -}); +); function ButtonGroup({ className, diff --git a/ui/litellm-dashboard/src/components/ui/button.tsx b/ui/litellm-dashboard/src/components/ui/button.tsx index b60160567b6..4fb8eb0d27b 100644 --- a/ui/litellm-dashboard/src/components/ui/button.tsx +++ b/ui/litellm-dashboard/src/components/ui/button.tsx @@ -1,57 +1,51 @@ -import * as React from "react"; import { Button as ButtonPrimitive } from "@base-ui/react/button"; -import { type VariantProps } from "cva"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn, cva } from "@/lib/cva.config"; +import { cn } from "@/lib/cva.config"; -const buttonVariants = cva({ - base: "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/80", - outline: - "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", - ghost: - "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", - destructive: - "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", - link: "text-primary underline-offset-4 hover:underline", +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/80", + outline: + "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: + "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5", + lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + icon: "size-9", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md", + "icon-lg": "size-10", + }, }, - size: { - default: - "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", - xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", - sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5", - lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", - icon: "size-9", - "icon-xs": - "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3", - "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md", - "icon-lg": "size-10", + defaultVariants: { + variant: "default", + size: "default", }, }, - defaultVariants: { - variant: "default", - size: "default", - }, -}); - -type ButtonProps = ButtonPrimitive.Props & VariantProps; - -const Button = React.forwardRef( - ({ className, variant = "default", size = "default", ...props }, ref) => { - return ( - - ); - }, ); -Button.displayName = "Button"; + +function Button({ + className, + variant = "default", + size = "default", + ...props +}: ButtonPrimitive.Props & VariantProps) { + return ; +} export { Button, buttonVariants }; diff --git a/ui/litellm-dashboard/src/components/ui/input-group.tsx b/ui/litellm-dashboard/src/components/ui/input-group.tsx index f1a5c356f9a..78eb24f97ad 100644 --- a/ui/litellm-dashboard/src/components/ui/input-group.tsx +++ b/ui/litellm-dashboard/src/components/ui/input-group.tsx @@ -1,9 +1,9 @@ "use client"; import * as React from "react"; -import { type VariantProps } from "cva"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn, cva } from "@/lib/cva.config"; +import { cn } from "@/lib/cva.config"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -22,21 +22,23 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) { ); } -const inputGroupAddonVariants = cva({ - base: "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", - variants: { - align: { - "inline-start": "order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]", - "inline-end": "order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]", - "block-start": - "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2", - "block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2", +const inputGroupAddonVariants = cva( + "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", + { + variants: { + align: { + "inline-start": "order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]", + "inline-end": "order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]", + "block-start": + "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2", + "block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2", + }, + }, + defaultVariants: { + align: "inline-start", }, }, - defaultVariants: { - align: "inline-start", - }, -}); +); function InputGroupAddon({ className, @@ -53,15 +55,14 @@ function InputGroupAddon({ if ((e.target as HTMLElement).closest("button")) { return; } - e.currentTarget.parentElement?.querySelector("[data-slot=input-group-control]")?.focus(); + e.currentTarget.parentElement?.querySelector("input")?.focus(); }} {...props} /> ); } -const inputGroupButtonVariants = cva({ - base: "flex items-center gap-2 text-sm shadow-none", +const inputGroupButtonVariants = cva("flex items-center gap-2 text-sm shadow-none", { variants: { size: { xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5", @@ -75,16 +76,18 @@ const inputGroupButtonVariants = cva({ }, }); -const InputGroupButton = React.forwardRef< - React.ComponentRef, - Omit, "size" | "type"> & - VariantProps & { - type?: "button" | "submit" | "reset"; - } ->(({ className, type = "button", variant = "ghost", size = "xs", ...props }, ref) => { +function InputGroupButton({ + className, + type = "button", + variant = "ghost", + size = "xs", + ...props +}: Omit, "size" | "type"> & + VariantProps & { + type?: "button" | "submit" | "reset"; + }) { return (