fix(ui): restore hover feedback and dark-mode variants lost in the token migration (#37579)

* fix(ui): restore hover feedback and dark-mode variants lost in the token migration

PR #37576 mapped hardcoded Tailwind palette classes onto semantic tokens. Two-tone
hover pairs collapsed onto a single token, so 116 hover utilities across 49 files
became identical to their base class and produced no visible feedback, and in seven
files a dark: variant was dropped while its hardcoded light partner survived, leaving
those elements stuck light in dark mode.

Hover states now follow the alpha-step idiom the shadcn primitives already use
(hover:bg-primary/80, hover:bg-success/20): a duplicated hover:text-X or hover:bg-X
becomes /80, hover:border-border becomes hover:border-ring, and a duplicate is
dropped where another hover utility on the element already carries the change. One
transition-colors that no longer animated anything is removed.

For the dark-mode gaps, indigo maps onto info and amber onto warning. There is no
purple token in globals.css, so the purple sites keep their palette classes and get
their dark: partner back.

* fix(ui): add an eslint rule that fails a hover: utility identical to its base

The token migration collapsed two-tone hover pairs by hand, so nothing catches
the next one. `local/no-noop-hover-variant` reads every string literal and
template chunk and errors when a `hover:X` sits alongside a bare `X`, which is
exactly the shape that renders no hover feedback. It ships at error with no
suppression baseline, so the eleven sites that already carried a dead hover
before the migration are fixed here too.

The rule reads one class string at a time, so a base class supplied by a
different ternary branch than its hover partner is left alone: a selected row
whose resting colour already matches its hover colour is deliberate, not a bug.
This commit is contained in:
yuneng-jiang 2026-08-19 22:52:36 -07:00 • committed by GitHub
parent 47a7e1742e
commit 93c1461074
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 269 additions and 157 deletions

View file

@ -22,6 +22,7 @@ const eslintConfig = [
"local/no-large-inline-object-arg": "warn",
"local/no-long-condition-chain": "warn",
"local/no-complex-jsx-arrow": ["error", { maxStatements: 2 }],
"local/no-noop-hover-variant": "error",
"@typescript-eslint/no-explicit-any": "warn",
"no-console": ["warn", { allow: ["warn", "error"] }],
"@typescript-eslint/no-unused-vars": "off",
@ -81,6 +82,10 @@ const eslintConfig = [
"no-restricted-syntax": "off",
},
},
{
files: ["tests/eslint-rules/**/*.{ts,tsx}"],
rules: { "local/no-noop-hover-variant": "off" },
},
{
files: ["src/**/*.test.{ts,tsx}", "tests/**/*.{ts,tsx}"],
plugins: { "testing-library": testingLibrary, "jest-dom": jestDom },

View file

@ -2,6 +2,7 @@ import noLargeInlineObjectArg from "./no-large-inline-object-arg.mjs";
import noLongConditionChain from "./no-long-condition-chain.mjs";
import noComplexJsxArrow from "./no-complex-jsx-arrow.mjs";
import filenamePascalCase from "./filename-pascal-case.mjs";
import noNoopHoverVariant from "./no-noop-hover-variant.mjs";
const plugin = {
rules: {
@ -9,6 +10,7 @@ const plugin = {
"no-long-condition-chain": noLongConditionChain,
"no-complex-jsx-arrow": noComplexJsxArrow,
"filename-pascal-case": filenamePascalCase,
"no-noop-hover-variant": noNoopHoverVariant,
},
};

View file

@ -0,0 +1,40 @@
const noopHovers = (value) => {
const tokens = value.split(/\s+/).filter(Boolean);
const bare = new Set(tokens.filter((t) => !t.includes(":")));
return tokens
.filter((t) => t.startsWith("hover:"))
.map((t) => [t, t.slice("hover:".length)])
.filter(([, base]) => bare.has(base));
};
const rule = {
meta: {
type: "problem",
docs: {
description:
"Disallow a hover: utility whose value is identical to the base utility in the same class string, which renders no hover feedback.",
},
schema: [],
messages: {
noop: "`{{hover}}` is identical to the base `{{base}}`, so hovering changes nothing. Give it a distinct value (e.g. `{{hover}}/80`) or drop it.",
},
},
create(context) {
const check = (node, value) => {
if (typeof value !== "string" || !value.includes("hover:")) return;
for (const [hover, base] of noopHovers(value)) {
context.report({ node, messageId: "noop", data: { hover, base } });
}
};
return {
Literal(node) {
check(node, node.value);
},
TemplateElement(node) {
check(node, node.value.cooked);
},
};
},
};
export default rule;

View file

@ -758,7 +758,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
<div className="mb-1 px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase">
Not listed?
</div>
<SelectItem value={CUSTOM_AGENT_TYPE} className="focus:bg-warning/10 dark:focus:**:text-amber-400">
<SelectItem value={CUSTOM_AGENT_TYPE} className="focus:bg-warning/10">
<span className="flex items-center gap-3">
<LayoutGrid className="size-4.5 shrink-0 text-warning" />
<span className="block">
@ -877,7 +877,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
<div>
{/* Agent name chip */}
<div className="mb-6 flex justify-center">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700 dark:bg-purple-950 dark:text-purple-300">
<Bot className="size-3.5" />
{agentName}
</Badge>
@ -906,7 +906,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
<div
className={`cursor-pointer rounded-lg border-2 p-4 transition-colors ${
keyAssignOption === "create_new"
? "border-indigo-600 bg-indigo-50"
? "border-info bg-info/10"
: "border-border bg-background hover:border-muted-foreground/40"
}`}
onClick={() => setKeyAssignOption("create_new")}
@ -916,7 +916,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
<RadioGroupItem value="create_new" aria-label="Create a new key for this agent" />
<div className="flex-1">
<div className="flex items-center gap-2">
<Key className="size-4 text-indigo-600" />
<Key className="size-4 text-info" />
<span className="font-medium text-foreground">Create a new key for this agent</span>
</div>
<p className="mt-1 text-sm text-muted-foreground">A dedicated key scoped to this agent.</p>
@ -943,7 +943,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
<div
className={`cursor-pointer rounded-lg border-2 p-4 transition-colors ${
keyAssignOption === "existing_key"
? "border-indigo-600 bg-indigo-50"
? "border-info bg-info/10"
: "border-border bg-background hover:border-muted-foreground/40"
}`}
onClick={() => setKeyAssignOption("existing_key")}
@ -993,7 +993,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
<CircleCheck className="mb-4 size-12 text-success" />
<h3 className="mb-2 text-xl font-semibold text-foreground">Agent Created!</h3>
<div className="mb-4 flex justify-center">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700 dark:bg-purple-950 dark:text-purple-300">
<Bot className="size-3.5" />
{createdAgentName}
</Badge>

View file

@ -131,7 +131,7 @@ const SkillsFieldArray = () => {
<Button
type="button"
variant="ghost"
className="mt-4 text-destructive hover:text-destructive"
className="mt-4 text-destructive hover:text-destructive/80"
onClick={() => remove(index)}
>
<Trash2 />
@ -184,7 +184,7 @@ const StaticHeadersFieldArray = () => {
variant="ghost"
size="icon"
aria-label="Remove static header"
className="text-destructive hover:text-destructive"
className="text-destructive hover:text-destructive/80"
onClick={() => remove(index)}
>
<Trash2 />

View file

@ -100,7 +100,7 @@ const ProviderDiscountTable: React.FC<ProviderDiscountTableProps> = ({
size="icon-sm"
aria-label={`Save discount for ${displayName}`}
onClick={() => handleSaveEdit(row.provider)}
className="cursor-pointer text-success hover:text-success"
className="cursor-pointer text-success hover:text-success/80"
>
<Check className="size-5" />
</Button>
@ -122,7 +122,7 @@ const ProviderDiscountTable: React.FC<ProviderDiscountTableProps> = ({
size="icon-sm"
aria-label={`Edit discount for ${displayName}`}
onClick={() => handleStartEdit(row.provider, row.discount)}
className="cursor-pointer text-info hover:text-info"
className="cursor-pointer text-info hover:text-info/80"
>
<SquarePen className="size-5" />
</Button>

View file

@ -152,7 +152,7 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
size="icon-sm"
aria-label={`Save margin for ${displayName}`}
onClick={() => handleSaveEdit(row.provider)}
className="cursor-pointer text-success hover:text-success"
className="cursor-pointer text-success hover:text-success/80"
>
<Check className="size-5" />
</Button>
@ -174,7 +174,7 @@ const ProviderMarginTable: React.FC<ProviderMarginTableProps> = ({
size="icon-sm"
aria-label={`Edit margin for ${displayName}`}
onClick={() => handleStartEdit(row.provider, row.margin)}
className="cursor-pointer text-info hover:text-info"
className="cursor-pointer text-info hover:text-info/80"
>
<SquarePen className="size-5" />
</Button>

View file

@ -199,7 +199,7 @@ const STATUS_CONFIG: Record<GuardrailStatus, { label: string; bg: string; text:
};
const TEAM_COLORS: Record<string, string> = {
"ML Platform": "bg-purple-100 text-purple-700",
"ML Platform": "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300",
"Data Science": "bg-info/15 text-info",
Security: "bg-destructive/15 text-destructive",
"Customer Success": "bg-warning/15 text-warning",
@ -311,7 +311,7 @@ function GuardrailCard({
return (
<div
className={`bg-card border rounded-lg p-4 transition-all ${
isSelected ? "border-info ring-1 ring-blue-200" : "border-border"
isSelected ? "border-info ring-1 ring-info/30" : "border-border"
}`}
>
<div className="flex items-start justify-between gap-4">
@ -358,7 +358,7 @@ function GuardrailCard({
<button
type="button"
onClick={onApprove}
className="text-xs bg-success hover:bg-success text-white px-3 py-1.5 rounded-md transition-colors font-medium"
className="text-xs bg-success hover:bg-success/80 text-white px-3 py-1.5 rounded-md transition-colors font-medium"
>
Approve
</button>
@ -600,7 +600,7 @@ function DetailPanel({
setNewStaticHeaderValue("");
}
}}
className="text-xs font-medium text-info hover:text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors shrink-0"
className="text-xs font-medium text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors shrink-0"
>
Add
</button>
@ -671,7 +671,7 @@ function DetailPanel({
setNewExtraHeader("");
}
}}
className="text-xs font-medium text-info hover:text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors"
className="text-xs font-medium text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors"
>
Add
</button>
@ -682,7 +682,7 @@ function DetailPanel({
<button
type="button"
onClick={() => setConfigExpanded(!configExpanded)}
className="w-full flex items-center justify-between px-3 py-2 text-left text-xs font-semibold text-foreground bg-muted hover:bg-muted transition-colors"
className="w-full flex items-center justify-between px-3 py-2 text-left text-xs font-semibold text-foreground bg-muted hover:bg-border transition-colors"
>
<span>Equivalent config</span>
{configExpanded ? (
@ -727,7 +727,7 @@ function DetailPanel({
<button
type="button"
onClick={onApprove}
className="flex-1 flex items-center justify-center gap-1.5 bg-success hover:bg-success text-white text-sm font-medium py-2 rounded-md transition-colors"
className="flex-1 flex items-center justify-center gap-1.5 bg-success hover:bg-success/80 text-white text-sm font-medium py-2 rounded-md transition-colors"
>
<CheckIcon className="h-4 w-4" />
Approve
@ -793,7 +793,7 @@ function ConfirmDialog({ action, guardrailName, onConfirm, onCancel }: ConfirmDi
type="button"
onClick={onConfirm}
className={`flex-1 text-white text-sm font-medium py-2 rounded-md transition-colors ${
isApprove ? "bg-success hover:bg-success" : "bg-destructive hover:bg-destructive"
isApprove ? "bg-success hover:bg-success/80" : "bg-destructive hover:bg-destructive/80"
}`}
>
{isApprove ? "Approve" : "Reject"}
@ -1015,7 +1015,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
<button
type="button"
onClick={() => setIsSubmitModalOpen(true)}
className="ml-auto flex items-center gap-2 bg-info hover:bg-info text-white text-sm font-medium px-4 py-2 rounded-md transition-colors"
className="ml-auto flex items-center gap-2 bg-info hover:bg-info/80 text-white text-sm font-medium px-4 py-2 rounded-md transition-colors"
>
<PlusIcon className="h-4 w-4" />
Add Guardrail

View file

@ -144,14 +144,14 @@ const createEmptyToolPermissionConfig = (): ToolPermissionConfig => ({
});
const getStepIndicatorClass = (isDone: boolean, isCurrent: boolean): string => {
if (isDone) return "bg-indigo-600 text-white";
if (isCurrent) return "bg-background text-indigo-600 border-2 border-indigo-600";
if (isDone) return "bg-info text-white";
if (isCurrent) return "bg-background text-info border-2 border-info";
return "bg-muted text-muted-foreground border border-border";
};
const getStepTitleClass = (isDone: boolean, isCurrent: boolean): string => {
if (isCurrent) return "font-semibold text-foreground";
if (isDone) return "font-medium text-indigo-600";
if (isDone) return "font-medium text-info";
return "font-medium text-muted-foreground";
};
@ -1126,7 +1126,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
>
{isDone ? "\u2713" : index + 1}
</div>
{!isLast && <div className={`min-h-4 w-px flex-1 ${isDone ? "bg-indigo-600" : "bg-border"}`} />}
{!isLast && <div className={`min-h-4 w-px flex-1 ${isDone ? "bg-info" : "bg-border"}`} />}
</div>
{/* Step content */}
@ -1142,7 +1142,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
{step.optional && !isCurrent && (
<span className="text-[11px] text-muted-foreground">optional</span>
)}
{isDone && <span className="text-[11px] text-indigo-600 hover:underline">Edit</span>}
{isDone && <span className="text-[11px] text-info hover:underline">Edit</span>}
</div>
{/* Expanded form content for current step */}

View file

@ -167,7 +167,7 @@ const DictField: React.FC<DictFieldProps> = ({ field, fullFieldKey, control, val
<Button
variant="ghost"
size="sm"
className="text-destructive hover:text-destructive"
className="text-destructive hover:text-destructive/80"
onClick={() => removeEntry(entry.id, entry.key)}
>
Remove

View file

@ -204,7 +204,7 @@ const LLMJudgeFields: React.FC<LLMJudgeFieldsProps> = ({ availableModels, contro
variant="ghost"
size="sm"
aria-label="Remove criterion"
className="mb-1 text-destructive hover:text-destructive"
className="mb-1 text-destructive hover:text-destructive/80"
onClick={() => setCriteria(criteria.filter((_, position) => position !== index))}
>
<X className="size-4" />

View file

@ -14,7 +14,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt
<span className="text-sm font-medium text-foreground flex items-center">
{label}
<SimpleTooltip content={tooltip}>
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
);
@ -30,7 +30,7 @@ const AwsSigV4Fields: React.FC = () => (
href="https://docs.litellm.ai/docs/mcp_aws_sigv4"
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info"
className="text-info hover:text-info/80"
>
View docs &rarr;
</a>

View file

@ -652,7 +652,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
MCP Server Name
<SimpleTooltip content="Best practice: Use a descriptive name that indicates the server's purpose (e.g., 'GitHub_MCP', 'Email_Service'). Cannot contain spaces or hyphens; use underscores instead. Names must comply with SEP-986 and will be rejected if invalid (https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names).">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -673,7 +673,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Alias
<SimpleTooltip content="A short, unique identifier for this server. Defaults to the server name if not provided. Cannot contain spaces or hyphens; use underscores instead.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -792,7 +792,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Max Concurrent Requests (optional)
<SimpleTooltip content="Maximum number of tool calls LiteLLM will run against this server at the same time. Additional calls wait for a free slot. Leave blank for no limit.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -859,7 +859,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Authentication Value
<SimpleTooltip content="Token, password, or header value to send with each request for the selected auth type.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}

View file

@ -30,7 +30,7 @@ export default function DcrBridgeToggle({
<span className="text-sm font-medium text-foreground flex items-center">
Gateway-hosted sign-in (DCR bridge)
<SimpleTooltip content="Lets OAuth-only clients like Claude Desktop register and sign in through the gateway. Turn off to relay the upstream server's own OAuth metadata instead (for clients pre-registered with the upstream IdP).">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}

View file

@ -54,7 +54,7 @@ const EnvVarsSection: React.FC = () => {
</>
}
>
<Info className="size-4 text-info hover:text-info cursor-help" />
<Info className="size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</div>
<span className="mb-3 block text-xs text-muted-foreground">

View file

@ -20,7 +20,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt
<span className="text-sm font-medium text-foreground flex items-center">
{label}
<SimpleTooltip content={tooltip}>
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
);

View file

@ -201,7 +201,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Allow All LiteLLM Keys
<SimpleTooltip content="When enabled, every API key can access this MCP server.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
<p className="text-sm text-muted-foreground mt-1">
@ -218,7 +218,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Internal network only
<SimpleTooltip content="When on, only requests from within your internal network are accepted. Turn off to allow external clients (other clusters, ChatGPT, etc). API key authentication is always required regardless of this setting.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
<p className="text-sm text-muted-foreground mt-1">
@ -236,7 +236,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Delegate auth to upstream (PKCE passthrough)
<SimpleTooltip content="When on, LiteLLM skips its own API key/SSO check for this server and lets the client complete PKCE directly with the upstream MCP server. Only honored when Auth Type is oauth2. No spend tracking or per-key rate limiting will run on this route.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
<p className="text-sm text-muted-foreground mt-1">
@ -261,7 +261,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
OAuth pass-through
<SimpleTooltip content="When on, this server is treated as an OAuth pass-through: the gateway proxies the upstream /.well-known/oauth-protected-resource metadata, emits spec-compliant 401 challenges when no bearer is supplied, and propagates upstream 401/403 responses. Only honored when Auth Type is None and 'Authorization' is in Extra Headers.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
<p className="text-sm text-muted-foreground mt-1">
@ -296,7 +296,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
MCP Access Groups
<SimpleTooltip content="Specify access groups for this MCP server. Users must be in at least one of these groups to access the server.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -318,7 +318,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Extra Headers
<SimpleTooltip content="Forward custom headers from incoming requests to this MCP server (e.g., Authorization, X-Custom-Header, User-Agent)">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
{mcpServer?.extra_headers && mcpServer.extra_headers.length > 0 && (
<span className="ml-2 text-xs bg-info/15 text-info px-2 py-1 rounded-full">
@ -347,7 +347,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Static Headers
<SimpleTooltip content="Send these key-value headers with every request to this MCP server.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
</FieldLabel>

View file

@ -123,7 +123,7 @@ function ConfirmDialog({ action, serverName, isCurrentlyActive, onConfirm, onCan
type="button"
onClick={() => onConfirm(isApprove ? undefined : reviewNotes || undefined)}
className={`flex-1 text-white text-sm font-medium py-2 rounded-md transition-colors ${
isApprove ? "bg-success hover:bg-success" : "bg-destructive hover:bg-destructive"
isApprove ? "bg-success hover:bg-success/80" : "bg-destructive hover:bg-destructive/80"
}`}
>
{isApprove ? "Approve" : "Reject"}
@ -235,7 +235,7 @@ function SubmissionRulesPanel({ requiredFields, onChange, onSave, isSaving }: Su
await onSave();
setExpanded(false);
}}
className="px-4 py-1.5 text-sm font-medium text-white bg-info hover:bg-info disabled:opacity-50 rounded-md transition-colors"
className="px-4 py-1.5 text-sm font-medium text-white bg-info hover:bg-info/80 disabled:opacity-50 rounded-md transition-colors"
>
{isSaving ? "Saving…" : "Save Rules"}
</button>
@ -322,7 +322,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe
<button
type="button"
onClick={onApprove}
className="text-xs bg-success hover:bg-success text-white px-3 py-1.5 rounded-md transition-colors font-medium"
className="text-xs bg-success hover:bg-success/80 text-white px-3 py-1.5 rounded-md transition-colors font-medium"
>
Approve
</button>
@ -341,7 +341,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe
<button
type="button"
onClick={onApprove}
className="text-xs bg-success hover:bg-success text-white px-3 py-1.5 rounded-md transition-colors font-medium"
className="text-xs bg-success hover:bg-success/80 text-white px-3 py-1.5 rounded-md transition-colors font-medium"
>
Re-approve
</button>
@ -383,7 +383,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe
<button
type="button"
onClick={onApprove}
className="text-xs bg-success hover:bg-success text-white px-3 py-1.5 rounded-md transition-colors font-medium"
className="text-xs bg-success hover:bg-success/80 text-white px-3 py-1.5 rounded-md transition-colors font-medium"
>
Approve
</button>
@ -392,7 +392,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe
<button
type="button"
onClick={onApprove}
className="text-xs bg-success hover:bg-success text-white px-3 py-1.5 rounded-md transition-colors font-medium"
className="text-xs bg-success hover:bg-success/80 text-white px-3 py-1.5 rounded-md transition-colors font-medium"
>
Re-approve
</button>

View file

@ -87,7 +87,9 @@ function MCPToolList({ serverId, serverName, accessToken, selectedTools, onToggl
<span className="inline-block w-2 h-2 rounded-full bg-info shrink-0" />
{serverName}
{selectedSet.size > 0 && (
<span className="ml-1 text-xs text-purple-600 font-semibold">{selectedSet.size} selected</span>
<span className="ml-1 text-xs text-purple-600 font-semibold dark:text-purple-400">
{selectedSet.size} selected
</span>
)}
</span>
<span className="text-muted-foreground text-xs">{expanded ? "▲" : "▼"}</span>
@ -110,12 +112,14 @@ function MCPToolList({ serverId, serverName, accessToken, selectedTools, onToggl
type="button"
onClick={() => onToggle({ server_id: serverId, tool_name: tool.name })}
className={`flex items-start justify-between px-3 py-2 rounded-lg text-left transition-colors ${
selected ? "bg-purple-50 border border-purple-300" : "bg-card border border-border hover:bg-muted"
selected
? "bg-purple-50 border border-purple-300 dark:bg-purple-950 dark:border-purple-700"
: "bg-card border border-border hover:bg-muted"
}`}
>
<div className="min-w-0 flex-1">
<p
className={`text-sm font-medium leading-tight ${selected ? "text-purple-800" : "text-foreground"}`}
className={`text-sm font-medium leading-tight ${selected ? "text-purple-800 dark:text-purple-200" : "text-foreground"}`}
>
{tool.name}
</p>
@ -125,7 +129,11 @@ function MCPToolList({ serverId, serverName, accessToken, selectedTools, onToggl
</p>
)}
</div>
{selected && <span className="text-purple-500 text-xs font-semibold ml-2 shrink-0 mt-0.5">✓</span>}
{selected && (
<span className="text-purple-500 text-xs font-semibold ml-2 shrink-0 mt-0.5 dark:text-purple-400">
✓
</span>
)}
</button>
);
})}
@ -271,15 +279,19 @@ function CreateToolsetModal({ open, onClose, onSave, accessToken, initialToolset
key={idx}
type="button"
onClick={() => handleToggleTool(tool)}
className="w-full flex items-center justify-between px-3 py-1.5 rounded-lg border border-purple-200 bg-purple-50 hover:bg-destructive/10 hover:border-destructive/20 group transition-colors"
className="w-full flex items-center justify-between px-3 py-1.5 rounded-lg border border-purple-200 bg-purple-50 hover:bg-destructive/10 hover:border-destructive/20 group transition-colors dark:border-purple-800 dark:bg-purple-950"
>
<div className="min-w-0 text-left">
<span className="text-xs font-medium text-purple-800 group-hover:text-destructive truncate block">
<span className="text-xs font-medium text-purple-800 group-hover:text-destructive truncate block dark:text-purple-200">
{displayToolName(serverPrefixById.get(tool.server_id), tool.tool_name)}
</span>
<span className="text-[10px] text-purple-400 truncate block">{tool.server_id.slice(0, 8)}…</span>
<span className="text-[10px] text-purple-400 truncate block dark:text-purple-500">
{tool.server_id.slice(0, 8)}…
</span>
</div>
<span className="ml-2 text-purple-300 group-hover:text-destructive text-xs shrink-0">✕</span>
<span className="ml-2 text-purple-300 group-hover:text-destructive text-xs shrink-0 dark:text-purple-600">
✕
</span>
</button>
))
)}

View file

@ -54,7 +54,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt
<span className="text-sm font-medium text-foreground flex items-center">
{label}
<SimpleTooltip content={tooltip}>
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
);
@ -190,7 +190,7 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
href={docsUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-info hover:text-info ml-2 font-normal"
className="text-xs text-info hover:text-info/80 ml-2 font-normal"
onClick={(e) => e.stopPropagation()}
>
Create OAuth App →

View file

@ -70,7 +70,7 @@ const OpenAPIFormSection: React.FC<OpenAPIFormSectionProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
OpenAPI Spec URL
<SimpleTooltip content="URL to an OpenAPI specification (JSON or YAML). MCP tools will be automatically generated from the API endpoints defined in the spec.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}

View file

@ -29,7 +29,7 @@ const OpenApiByokFields: React.FC = () => {
<span className="text-sm font-medium text-foreground flex items-center gap-2">
BYOK (Bring Your Own Key)
<SimpleTooltip content="When enabled, each user provides their own API key for this service. Keys are stored per-user and never shared.">
<Info className="size-4 text-info hover:text-info cursor-help" />
<Info className="size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -65,7 +65,7 @@ const OpenApiByokFields: React.FC = () => {
<span className="text-sm font-medium text-foreground">
Access Description
<SimpleTooltip content="List of permissions shown to users in the connection modal (e.g. 'Create and manage Jira issues')">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -85,7 +85,7 @@ const OpenApiByokFields: React.FC = () => {
<span className="text-sm font-medium text-foreground">
API Key Help URL
<SimpleTooltip content="Optional link shown to users to help them find their API key">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}

View file

@ -38,7 +38,7 @@ const StdioConfiguration: React.FC<StdioConfigurationProps> = ({ isVisible, requ
<span className="text-sm font-medium text-foreground flex items-center">
Stdio Configuration (JSON)
<SimpleTooltip content="Paste your stdio MCP server configuration in JSON format. You can use the full mcpServers structure from config.yaml or just the inner server configuration.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}

View file

@ -21,7 +21,7 @@ const TokenEndpointAuthMethodField: React.FC<TokenEndpointAuthMethodFieldProps>
<span className="text-sm font-medium text-foreground flex items-center">
Token Endpoint Auth Method (optional)
<SimpleTooltip content="How the proxy authenticates to the upstream OAuth token endpoint. Client Secret Basic sends the client credentials in an HTTP Basic Authorization header; leave blank to use the default, Client Secret Post, which sends them in the request body.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}

View file

@ -26,7 +26,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt
<span className="text-sm font-medium text-foreground flex items-center">
{label}
<SimpleTooltip content={tooltip}>
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
);

View file

@ -275,7 +275,7 @@ export function ToolTestPanel({
className={`px-2 py-1 text-xs font-medium rounded transition-colors ${
viewMode === "formatted"
? "bg-success/15 text-success"
: "text-success hover:text-success"
: "text-success hover:text-success/80"
}`}
>
Formatted
@ -283,7 +283,7 @@ export function ToolTestPanel({
<button
onClick={() => setViewMode("json")}
className={`px-2 py-1 text-xs font-medium rounded transition-colors ${
viewMode === "json" ? "bg-success/15 text-success" : "text-success hover:text-success"
viewMode === "json" ? "bg-success/15 text-success" : "text-success hover:text-success/80"
}`}
>
JSON
@ -413,7 +413,7 @@ export function ToolTestPanel({
href={part}
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info underline break-all"
className="text-info hover:text-info/80 underline break-all"
>
{part}
</a>
@ -512,7 +512,7 @@ export function ToolTestPanel({
href={content.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-xs text-info hover:text-info hover:underline mt-1 transition-colors"
className="inline-flex items-center text-xs text-info hover:underline mt-1"
>
View Resource
<svg className="ml-1 h-3 w-3" fill="currentColor" viewBox="0 0 20 20">

View file

@ -198,7 +198,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
const LiteLLMProxyTab = () => (
<div className="flex w-full flex-col gap-6">
<div className="bg-linear-to-r from-emerald-50 to-green-50 p-6 rounded-lg border border-success/15">
<div className="bg-linear-to-r from-success/15 to-success/5 p-6 rounded-lg border border-success/15">
<div className="flex items-center gap-3 mb-3">
<Zap className="text-success" size={24} />
<h4 className="mb-0 text-xl font-semibold text-success">LiteLLM Proxy API Integration</h4>
@ -268,7 +268,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
const OpenAITab = () => (
<div className="flex w-full flex-col gap-6">
<div className="bg-linear-to-r from-blue-50 to-indigo-50 p-6 rounded-lg border border-info/15">
<div className="bg-linear-to-r from-info/15 to-info/5 p-6 rounded-lg border border-info/15">
<div className="flex items-center gap-3 mb-3">
<Code className="text-info" size={24} />
<h4 className="mb-0 text-xl font-semibold text-info">OpenAI Responses API Integration</h4>
@ -292,7 +292,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
href="https://platform.openai.com/api-keys"
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info inline-flex items-center gap-1"
className="text-info hover:text-info/80 inline-flex items-center gap-1"
>
OpenAI platform <ExternalLinkIcon size={12} />
</a>
@ -348,12 +348,12 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
const CursorTab = () => (
<div className="flex w-full flex-col gap-6">
<div className="bg-linear-to-r from-purple-50 to-blue-50 p-6 rounded-lg border border-purple-100">
<div className="bg-linear-to-r from-purple-50 to-blue-50 p-6 rounded-lg border border-purple-100 dark:from-purple-950 dark:to-blue-950 dark:border-purple-900">
<div className="flex items-center gap-3 mb-3">
<Terminal className="text-purple-600" size={24} />
<h4 className="mb-0 text-xl font-semibold text-purple-900">Cursor IDE Integration</h4>
<Terminal className="text-purple-600 dark:text-purple-400" size={24} />
<h4 className="mb-0 text-xl font-semibold text-purple-900 dark:text-purple-100">Cursor IDE Integration</h4>
</div>
<span className="text-purple-700">
<span className="text-purple-700 dark:text-purple-300">
Use tools directly from Cursor IDE with LiteLLM MCP. Enable your AI assistant to perform real-world tasks
without leaving your coding environment.
</span>
@ -381,7 +381,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
<code className="bg-muted px-2 py-1 rounded-sm">Ctrl+S</code>
</span>
<FeatureCard
icon={<Code className="text-purple-600" size={16} />}
icon={<Code className="text-purple-600 dark:text-purple-400" size={16} />}
title="Configuration"
description="Cursor MCP configuration"
serverName="Zapier Gmail"
@ -412,7 +412,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
const StreamableHTTPTab = () => (
<div className="flex w-full flex-col gap-6">
<div className="bg-linear-to-r from-green-50 to-teal-50 p-6 rounded-lg border border-success/15">
<div className="bg-linear-to-r from-success/15 to-success/5 p-6 rounded-lg border border-success/15">
<div className="flex items-center gap-3 mb-3">
<Globe className="text-success" size={24} />
<h4 className="mb-0 text-xl font-semibold text-success">Streamable HTTP Transport</h4>
@ -449,7 +449,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
<div className="mt-4">
<Button
variant="link"
className="p-0 h-auto text-info hover:text-info"
className="p-0 h-auto text-info hover:text-info/80"
nativeButton={false}
render={
<a

View file

@ -864,7 +864,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
OpenAPI Spec URL
<SimpleTooltip content="URL to an OpenAPI specification (JSON or YAML). MCP tools will be automatically generated from the API endpoints defined in the spec.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -887,7 +887,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Max Concurrent Requests (optional)
<SimpleTooltip content="Maximum number of tool calls LiteLLM will run against this server at the same time. Additional calls wait for a free slot. Leave blank for no limit.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1008,7 +1008,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Authentication Value
<SimpleTooltip content="Token, password, or header value to send with each request for the selected auth type.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1063,7 +1063,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
href="https://docs.litellm.ai/docs/mcp_aws_sigv4"
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info"
className="text-info hover:text-info/80"
>
View docs &rarr;
</a>
@ -1073,7 +1073,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
AWS Region
<SimpleTooltip content="AWS region for SigV4 signing (e.g., us-east-1)">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1092,7 +1092,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
AWS Service Name
<SimpleTooltip content="AWS service name for SigV4 signing. Defaults to 'bedrock-agentcore'.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1111,7 +1111,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
AWS Access Key ID
<SimpleTooltip content="Optional. If not provided, falls back to the boto3 credential chain (IAM role, env vars, etc.).">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1130,7 +1130,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
AWS Secret Access Key
<SimpleTooltip content="Optional. Required if AWS Access Key ID is provided.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1149,7 +1149,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
AWS Session Token
<SimpleTooltip content="Optional. Only needed for temporary STS credentials.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1168,7 +1168,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
AWS Role ARN
<SimpleTooltip content="Optional. IAM role ARN to assume via STS before signing. If set, LiteLLM calls sts:AssumeRole to get temporary credentials.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
@ -1187,7 +1187,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
AWS Session Name
<SimpleTooltip content="Optional. Session name for the AssumeRole call — appears in CloudTrail logs. Auto-generated if omitted.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}

View file

@ -199,7 +199,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
type="button"
variant="ghost"
size="xs"
className="h-auto p-0 text-xs text-info hover:bg-transparent hover:text-info"
className="h-auto p-0 text-xs text-info hover:bg-transparent hover:text-info/80"
/>
}
>

View file

@ -168,7 +168,7 @@ const AdditionalModelSettings: React.FC<AdditionalModelSettingsProps> = ({
href="https://docs.litellm.ai/docs/proxy/keys_teams_router_settings"
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info"
className="text-info hover:text-info/80"
>
Learn more
</a>

View file

@ -472,7 +472,7 @@ export default function AgentBuilderView({
<span>
Agent Builder is experimental and may change or be removed without notice. We’d love your feedback—email us
at{" "}
<a href="mailto:product@berri.ai" className="font-medium text-warning underline hover:text-warning">
<a href="mailto:product@berri.ai" className="font-medium text-warning underline hover:text-warning/80">
product@berri.ai
</a>
.

View file

@ -1625,7 +1625,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<Button
type="button"
size="xs"
className="rounded-lg bg-info px-3 py-1 text-xs font-medium text-white hover:bg-info"
className="rounded-lg bg-info px-3 py-1 text-xs font-medium text-white hover:bg-info/80"
onClick={() => setByokModalServer(server)}
>
Connect
@ -1816,7 +1816,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div className="mb-4">
{uploadedImages.length === 0 ? (
<label
className="flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted px-4 py-8 text-center hover:border-border"
className="flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted px-4 py-8 text-center hover:border-ring"
onDragOver={(event) => event.preventDefault()}
onDrop={(event) => {
event.preventDefault();
@ -1869,7 +1869,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
</Button>
</div>
))}
<label className="flex h-32 w-32 cursor-pointer flex-col items-center justify-center rounded-md border-2 border-dashed border-border hover:border-border">
<label className="flex h-32 w-32 cursor-pointer flex-col items-center justify-center rounded-md border-2 border-dashed border-border hover:border-ring">
<ImageIcon className="size-6 text-muted-foreground" aria-hidden="true" />
<p className="mt-1 text-xs text-muted-foreground">Add more</p>
<input
@ -1892,7 +1892,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div className="mb-4">
{!uploadedAudio ? (
<label
className="flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted px-4 py-8 text-center hover:border-border"
className="flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted px-4 py-8 text-center hover:border-ring"
onDragOver={(event) => event.preventDefault()}
onDrop={(event) => {
event.preventDefault();
@ -1972,7 +1972,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
</div>
<button
type="button"
className="text-xs text-info hover:text-info"
className="text-xs text-info hover:text-info/80"
onClick={() => codeInterpreter.setEnabled(false)}
>
Disable

View file

@ -186,7 +186,7 @@ const CodeInterpreterOutput: React.FC<CodeInterpreterOutputProps> = ({ code, ann
type="button"
variant="ghost"
size="xs"
className="h-auto gap-1 px-1 py-0 text-xs text-info hover:text-info"
className="h-auto gap-1 px-1 py-0 text-xs text-info hover:text-info/80"
onClick={() => void handleDownload(annotation)}
>
<Download className="size-3" />

View file

@ -79,7 +79,7 @@ const CodeInterpreterTool: React.FC<CodeInterpreterToolProps> = ({
href={GITHUB_FEATURE_REQUEST_URL}
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info underline"
className="text-info hover:text-info/80 underline"
>
Request support for other providers
</a>

View file

@ -749,7 +749,7 @@ export default function ComplianceUI({
<button
type="button"
onClick={() => setShowGuardrailDropdown(!showGuardrailDropdown)}
className="w-full flex items-center justify-between border border-border rounded-lg px-3 py-2 text-sm text-left hover:border-border transition-colors"
className="w-full flex items-center justify-between border border-border rounded-lg px-3 py-2 text-sm text-left hover:border-ring transition-colors"
>
<span className={selectedGuardrails.length > 0 ? "text-foreground" : "text-muted-foreground/70"}>
{selectedGuardrails.length > 0 ? `${selectedGuardrails.length} selected` : "None selected"}
@ -815,7 +815,7 @@ export default function ComplianceUI({
<button
type="button"
onClick={() => batchAbortControllerRef.current?.abort()}
className="flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap bg-destructive text-white hover:bg-destructive"
className="flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap bg-destructive text-white hover:bg-destructive/80"
>
<Square className="w-3.5 h-3.5" /> Stop
</button>
@ -824,7 +824,7 @@ export default function ComplianceUI({
type="button"
onClick={runTests}
disabled={selectedPromptIds.size === 0 || disabledPersonalKeyCreation}
className={`flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap ${selectedPromptIds.size === 0 || disabledPersonalKeyCreation ? "bg-muted text-muted-foreground/70 cursor-not-allowed" : "bg-info text-white hover:bg-info"}`}
className={`flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap ${selectedPromptIds.size === 0 || disabledPersonalKeyCreation ? "bg-muted text-muted-foreground/70 cursor-not-allowed" : "bg-info text-white hover:bg-info/80"}`}
>
<Play className="w-3.5 h-3.5" /> Simulate ({selectedPromptIds.size})
</button>
@ -879,7 +879,7 @@ export default function ComplianceUI({
<button
type="button"
onClick={selectAll}
className="text-[11px] font-medium text-info hover:text-info"
className="text-[11px] font-medium text-info hover:text-info/80"
>
Select All
</button>
@ -974,7 +974,7 @@ export default function ComplianceUI({
<button
type="button"
onClick={downloadCsvTemplate}
className="flex items-center gap-1 text-[10px] font-medium text-info hover:text-info"
className="flex items-center gap-1 text-[10px] font-medium text-info hover:text-info/80"
>
<Download className="w-3 h-3" /> Download Template
</button>
@ -1069,7 +1069,7 @@ export default function ComplianceUI({
e.stopPropagation();
toggleFrameworkPrompts(fw);
}}
className="text-[10px] font-medium text-info hover:text-info px-1.5 py-0.5 rounded-sm hover:bg-info/10 shrink-0"
className="text-[10px] font-medium text-info px-1.5 py-0.5 rounded-sm hover:bg-info/10 shrink-0"
>
{fwSelectedCount === fwPromptCount ? "Clear" : "All"}
</button>
@ -1124,7 +1124,7 @@ export default function ComplianceUI({
<button
type="button"
onClick={() => toggleCategoryPrompts(category)}
className="text-[10px] font-medium text-info hover:text-info shrink-0 whitespace-nowrap"
className="text-[10px] font-medium text-info hover:text-info/80 shrink-0 whitespace-nowrap"
>
{allCatSelected ? "Clear" : "Select all"}
</button>
@ -1316,7 +1316,7 @@ export default function ComplianceUI({
type="button"
onClick={runQuickTest}
disabled={!quickTestInput.trim() || isQuickTesting || disabledPersonalKeyCreation}
className={`w-full mt-2 flex items-center justify-center gap-1.5 py-2.5 rounded-lg text-sm font-medium transition-colors ${!quickTestInput.trim() || isQuickTesting || disabledPersonalKeyCreation ? "bg-muted text-muted-foreground/70 cursor-not-allowed" : "bg-info text-white hover:bg-info"}`}
className={`w-full mt-2 flex items-center justify-center gap-1.5 py-2.5 rounded-lg text-sm font-medium transition-colors ${!quickTestInput.trim() || isQuickTesting || disabledPersonalKeyCreation ? "bg-muted text-muted-foreground/70 cursor-not-allowed" : "bg-info text-white hover:bg-info/80"}`}
>
{isQuickTesting ? <Loader2 className="w-4 h-4 animate-spin" /> : <Send className="w-4 h-4" />}{" "}
{testButtonLabel}

View file

@ -129,13 +129,13 @@ interface ModePickerProps {
const modeCardClass = (isSelected: boolean) =>
[
"relative flex-1 cursor-pointer rounded-xl border-2 px-5 py-6 transition-all",
isSelected ? "border-indigo-600 bg-indigo-50" : "border-border bg-background",
isSelected ? "border-info bg-info/10" : "border-border bg-background",
].join(" ");
const modeIconClass = (isSelected: boolean) =>
[
"mb-4 flex size-10 items-center justify-center rounded-[10px]",
isSelected ? "bg-indigo-100 text-indigo-600" : "bg-muted text-muted-foreground",
isSelected ? "bg-info/15 text-info" : "bg-muted text-muted-foreground",
].join(" ");
const ModePicker: React.FC<ModePickerProps> = ({ selected, onSelect }) => (
@ -340,7 +340,7 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
<ModePicker selected={selectedMode} onSelect={setSelectedMode} />
{selectedMode === "flow_builder" && (
<Alert variant="info" className="mt-4 border border-indigo-200 bg-indigo-50">
<Alert variant="info" className="mt-4 border border-info/20 bg-info/10">
<AlertTitle>
You&apos;ll be redirected to the full-screen Flow Builder to design your policy logic visually.
</AlertTitle>

View file

@ -421,7 +421,7 @@ const AiSuggestionModal: React.FC<AiSuggestionModalProps> = ({
<div
key={suggestion.template_id}
className={`rounded-xl border-2 transition-all ${
isSelected ? "border-info bg-blue-50/60 shadow-xs" : "border-border hover:border-border hover:shadow-xs"
isSelected ? "border-info bg-blue-50/60 shadow-xs" : "border-border hover:border-ring hover:shadow-xs"
}`}
>
<div className="p-4 cursor-pointer" onClick={() => toggleTemplate(suggestion.template_id)}>
@ -890,7 +890,7 @@ const AiSuggestionModal: React.FC<AiSuggestionModalProps> = ({
))}
</div>
{attackExamples.length < MAX_EXAMPLES && (
<button onClick={handleAddExample} className="text-sm text-info hover:text-info mt-2 font-medium">
<button onClick={handleAddExample} className="text-sm text-info hover:text-info/80 mt-2 font-medium">
+ Add another example
</button>
)}

View file

@ -155,7 +155,7 @@ const CreateTagModal: React.FC<CreateTagModalProps> = ({ visible, onCancel, onSu
href="https://github.com/BerriAI/litellm/issues/new"
target="_blank"
rel="noopener noreferrer"
className="text-info underline hover:text-info"
className="text-info underline hover:text-info/80"
>
create a GitHub issue
</a>

View file

@ -122,7 +122,7 @@ const TagEditForm: React.FC<TagEditFormProps> = ({ tag, seedBudgetFields, userMo
href="https://github.com/BerriAI/litellm/issues/new"
target="_blank"
rel="noopener noreferrer"
className="text-info underline hover:text-info"
className="text-info underline hover:text-info/80"
>
create a GitHub issue
</a>

View file

@ -484,7 +484,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
<span>Get Started by Tracking cost per {capitalizedEntityLabel} </span>
<a
href="https://docs.litellm.ai/docs/proxy/enterprise#spend-tracking"
className="text-info hover:text-info ml-1"
className="text-info hover:text-info/80 ml-1"
>
here
</a>

View file

@ -428,7 +428,7 @@ export default function UserInfoView({
<Button
variant="secondary"
onClick={() => setIsDeleteModalOpen(true)}
className="flex items-center text-destructive border-destructive hover:text-destructive hover:border-destructive"
className="flex items-center text-destructive border-destructive hover:bg-destructive/10"
>
<Trash2 />
Delete User

View file

@ -518,7 +518,7 @@ export default function ChatConversationPage() {
variant="ghost"
size="icon-xs"
onClick={() => setStorageBannerDismissed(true)}
className="text-warning hover:bg-warning/15 hover:text-warning"
className="text-warning hover:bg-warning/15 "
>
<X className="size-3.5" />
</Button>
@ -585,7 +585,7 @@ export default function ChatConversationPage() {
}
}
}}
className="absolute bottom-[100px] left-1/2 -translate-x-1/2 z-10 rounded-full border bg-background/75 text-muted-foreground shadow-sm backdrop-blur-md hover:bg-background/95 hover:text-muted-foreground"
className="absolute bottom-[100px] left-1/2 -translate-x-1/2 z-10 rounded-full border bg-background/75 text-muted-foreground shadow-sm backdrop-blur-md hover:bg-background/95"
aria-label="Scroll to bottom"
>
<ChevronDown className="h-3 w-3" />

View file

@ -280,7 +280,7 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
<button
onClick={handleAddLink}
disabled={!newLink.url || !newLink.displayName}
className={`flex items-center px-4 py-2 rounded-md text-sm ${!newLink.url || !newLink.displayName ? "bg-border text-muted-foreground cursor-not-allowed" : "bg-success text-white hover:bg-success"}`}
className={`flex items-center px-4 py-2 rounded-md text-sm ${!newLink.url || !newLink.displayName ? "bg-border text-muted-foreground cursor-not-allowed" : "bg-success text-white hover:bg-success/80"}`}
>
<PlusCircleIcon className="w-4 h-4 mr-1" />
Add Link
@ -312,7 +312,7 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
<div className="flex space-x-2">
<button
onClick={handleSaveRearranging}
className="text-xs bg-success text-white px-3 py-1.5 rounded-sm hover:bg-success"
className="text-xs bg-success text-white px-3 py-1.5 rounded-sm hover:bg-success/80"
>
Save Order
</button>

View file

@ -50,10 +50,10 @@ export const HelpLink: React.FC<HelpLinkProps> = ({
"inline-flex items-center gap-1.5 transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-1 rounded-sm";
const variantClasses = {
inline: "text-info hover:text-info text-sm font-medium hover:underline",
inline: "text-info text-sm font-medium hover:underline",
subtle: "text-muted-foreground hover:text-foreground text-xs",
button:
"text-info hover:text-info border border-border hover:border-border px-3 py-1.5 rounded-md bg-card hover:bg-accent text-sm font-medium shadow-xs",
"text-info border border-border px-3 py-1.5 rounded-md bg-card hover:bg-accent text-sm font-medium shadow-xs",
};
return (

View file

@ -1,3 +1,3 @@
/** Shared styling for Docs / Blog in the top nav (product navigation zone). */
export const NAV_PRODUCT_LINK_CLASS =
"inline-flex h-9 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-sm font-medium leading-none text-foreground transition-colors hover:bg-accent hover:text-foreground";
"inline-flex h-9 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-sm font-medium leading-none text-foreground transition-colors hover:bg-accent ";

View file

@ -195,7 +195,7 @@ const SidebarAccountMenu: React.FC<SidebarAccountMenuProps> = ({ onLogout, colla
<div className="flex flex-col px-3 py-2">
<InfoRow icon={<Crown className="size-[17px]" />} label="Tier">
{premiumUser ? (
<Badge variant="outline" className="gap-1 border-amber-500/30 bg-amber-500/10 text-warning">
<Badge variant="outline" className="gap-1 border-warning/30 bg-warning/10 text-warning">
<Crown />
Premium
</Badge>

View file

@ -85,7 +85,7 @@ export default function SidebarUsageCard({ accessToken, collapsed, onExpandRail
variant="outline"
onClick={onExpandRail}
title="Enterprise usage"
className="h-9 w-full rounded-lg border-sidebar-border bg-sidebar text-sidebar-primary shadow-none hover:bg-sidebar-accent hover:text-sidebar-primary"
className="h-9 w-full rounded-lg border-sidebar-border bg-sidebar text-sidebar-primary shadow-none hover:bg-sidebar-accent"
>
<Award className="size-[18px]" strokeWidth={1.75} />
</Button>

View file

@ -173,7 +173,7 @@ export const ToolPoliciesPanel: React.FC<ToolPoliciesPanelProps> = ({ accessToke
<button
type="button"
onClick={() => scrollToToolRow(tool.tool_id)}
className="text-warning hover:text-warning font-medium text-xs whitespace-nowrap"
className="text-warning hover:text-warning/80 font-medium text-xs whitespace-nowrap"
>
Review
</button>

View file

@ -116,7 +116,7 @@ const KeywordTierRules: React.FC<KeywordTierRulesProps> = ({ rules, onChange, ti
<Button
variant="ghost"
size="icon"
className="text-destructive hover:text-destructive"
className="text-destructive hover:text-destructive/80"
aria-label={`Remove keyword rule ${index + 1}`}
onClick={() => removeRule(rule.id)}
>

View file

@ -486,7 +486,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
<div className="flex items-center">
<span className="text-xs text-muted-foreground truncate max-w-[150px]">{record.invitation_link}</span>
<CopyToClipboard text={record.invitation_link} onCopy={() => toast.success("Invitation link copied!")}>
<button className="ml-1 text-info text-xs hover:text-info">Copy</button>
<button className="ml-1 text-info text-xs hover:text-info/80">Copy</button>
</CopyToClipboard>
</div>
</div>

View file

@ -75,7 +75,7 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
href="https://docs.litellm.ai/docs/proxy/pass_through_guardrails#field-level-targeting"
target="_blank"
rel="noopener noreferrer"
className="text-info underline hover:text-info"
className="text-info underline hover:text-info/80"
>
(Learn More)
</a>

View file

@ -82,7 +82,7 @@ export function BudgetWindowsEditor({ value, onChange }: BudgetWindowsEditorProp
<Button
variant="ghost"
size="sm"
className="px-1 text-destructive hover:text-destructive"
className="px-1 text-destructive hover:text-destructive/80"
onClick={() => removeWindow(idx)}
>
✕

View file

@ -147,7 +147,7 @@ const MCPToolPermissions: React.FC<MCPToolPermissionsProps> = ({
<>
<button
type="button"
className="text-sm text-info hover:text-info font-medium"
className="text-sm text-info hover:text-info/80 font-medium"
onClick={() => handleSelectAll(server.server_id)}
disabled={isLoading}
>
@ -155,7 +155,7 @@ const MCPToolPermissions: React.FC<MCPToolPermissionsProps> = ({
</button>
<button
type="button"
className="text-sm text-info hover:text-info font-medium"
className="text-sm text-info hover:text-info/80 font-medium"
onClick={() => handleDeselectAll(server.server_id)}
disabled={isLoading}
>

View file

@ -191,7 +191,7 @@ export const ByokCredentialModal: React.FC<ByokCredentialModalProps> = ({ server
href={server.byok_api_key_help_url}
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info text-sm mt-2 flex items-center gap-1"
className="text-info hover:text-info/80 text-sm mt-2 flex items-center gap-1"
>
Where do I find my API key? <Link2 className="size-3.5" />
</a>
@ -223,7 +223,7 @@ export const ByokCredentialModal: React.FC<ByokCredentialModalProps> = ({ server
<button
onClick={handleAuthorize}
disabled={loading}
className="w-full bg-info hover:bg-info disabled:opacity-60 text-white font-medium py-3 px-6 rounded-xl flex items-center justify-center gap-2 transition-colors"
className="w-full bg-info hover:bg-info/80 disabled:opacity-60 text-white font-medium py-3 px-6 rounded-xl flex items-center justify-center gap-2 transition-colors"
>
<Lock className="size-4" /> Connect &amp; Authorize
</button>

View file

@ -314,7 +314,7 @@ export const getHealthChecksTableColumns = ({
<DetailButton
label="View response details"
testId="view-health-success-btn"
className="text-success hover:bg-success/10 hover:text-success"
className="text-success hover:bg-success/10 "
onClick={() => onShowSuccess(displayName, successResponse)}
/>
)}
@ -351,7 +351,7 @@ export const getHealthChecksTableColumns = ({
<DetailButton
label="View full error details"
testId="view-health-error-btn"
className="text-destructive hover:bg-destructive/10 hover:text-destructive"
className="text-destructive hover:bg-destructive/10 "
onClick={() => onShowError(displayName, cleanedError, fullError)}
/>
)}

View file

@ -192,7 +192,10 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
{/* Clear filters button */}
{(searchTerm || selectedProvider || selectedMode || selectedFeature) && (
<div className="flex items-end">
<button onClick={resetFilters} className="text-info hover:text-info text-sm underline h-10 flex items-center">
<button
onClick={resetFilters}
className="text-info hover:text-info/80 text-sm underline h-10 flex items-center"
>
Clear Filters
</button>
</div>

View file

@ -206,7 +206,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
<button
onClick={handleAddAlias}
disabled={!newAlias.aliasName || !newAlias.targetModelGroup}
className={`flex items-center px-4 py-2 rounded-md text-sm ${!newAlias.aliasName || !newAlias.targetModelGroup ? "bg-border text-muted-foreground cursor-not-allowed" : "bg-success text-white hover:bg-success"}`}
className={`flex items-center px-4 py-2 rounded-md text-sm ${!newAlias.aliasName || !newAlias.targetModelGroup ? "bg-border text-muted-foreground cursor-not-allowed" : "bg-success text-white hover:bg-success/80"}`}
>
<PlusCircleIcon className="w-4 h-4 mr-1" />
Add Alias

View file

@ -1712,7 +1712,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
}
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info"
className="text-info hover:text-info/80"
>
documentation
</a>

View file

@ -141,7 +141,7 @@ export function MCPServerPermissions({
<div
onClick={() => hasToolRestrictions && toggleServerExpansion(item.value)}
className={`flex items-center gap-3 py-2 px-3 rounded-lg border border-border transition-all ${
hasToolRestrictions ? "cursor-pointer hover:bg-accent hover:border-border" : "bg-card"
hasToolRestrictions ? "cursor-pointer hover:bg-accent" : "bg-card"
}`}
>
<div className="flex items-center gap-2 flex-1 min-w-0">

View file

@ -555,7 +555,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
<button
key={title}
onClick={() => window.open(url, "_blank")}
className="flex min-w-0 items-center space-x-3 text-info hover:text-info transition-colors p-3 rounded-lg hover:bg-info/10 border border-border"
className="flex min-w-0 items-center space-x-3 text-info transition-colors p-3 rounded-lg hover:bg-info/10 border border-border"
>
<ExternalLinkIcon className="w-4 h-4 shrink-0" />
<p className="text-sm font-medium break-words">{title}</p>
@ -1087,7 +1087,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
});
copyToClipboard(codeSnippet);
}}
className="text-sm text-info hover:text-info cursor-pointer"
className="text-sm text-info hover:text-info/80 cursor-pointer"
>
Copy to clipboard
</button>
@ -1144,7 +1144,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
href={selectedAgent.url}
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info text-sm break-all"
className="text-info hover:text-info/80 text-sm break-all"
>
{selectedAgent.url}
</a>
@ -1232,7 +1232,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
href={selectedAgent.documentationUrl}
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info flex items-center space-x-2"
className="text-info hover:text-info/80 flex items-center space-x-2"
>
<ExternalLinkIcon className="w-4 h-4" />
<span>View Documentation</span>
@ -1332,7 +1332,7 @@ if _public_card.supports_authenticated_extended_card:
)`;
copyToClipboard(codeSnippet);
}}
className="text-sm text-info hover:text-info cursor-pointer"
className="text-sm text-info hover:text-info/80 cursor-pointer"
>
Copy to clipboard
</button>
@ -1389,7 +1389,7 @@ response = await client.send_message(request)
print(response.model_dump(mode='json', exclude_none=True))`;
copyToClipboard(codeSnippet);
}}
className="text-sm text-info hover:text-info cursor-pointer"
className="text-sm text-info hover:text-info/80 cursor-pointer"
>
Copy to clipboard
</button>
@ -1549,7 +1549,7 @@ if __name__ == "__main__":
asyncio.run(main())`;
copyToClipboard(codeSnippet);
}}
className="text-sm text-info hover:text-info cursor-pointer"
className="text-sm text-info hover:text-info/80 cursor-pointer"
>
Copy to clipboard
</button>

View file

@ -26,7 +26,7 @@ const TagFilteringToggle: React.FC<TagFilteringToggleProps> = ({ enabled, router
href={routerFieldsMetadata["enable_tag_filtering"].link}
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info underline"
className="text-info hover:text-info/80 underline"
>
Learn more
</a>

View file

@ -597,7 +597,7 @@ export function DataTable<TData extends RowData, TValue>(props: DataTableProps<T
<TableRoot className={enableColumnResizing ? "table-fixed" : ""} style={tableStyle}>
<TableHeader className={cn(stickyHeader ? "sticky top-0 z-20" : "", fill.header)}>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="bg-muted/50 hover:bg-muted/50">
<TableRow key={headerGroup.id} className="bg-muted/50">
{headerGroup.headers.map((header) => (
<DataTableHeadCell
key={header.id}

View file

@ -286,7 +286,7 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
type="button"
data-slot="advanced-date-picker-trigger"
aria-expanded={isOpen}
className="w-[300px] px-3 py-2 text-sm text-left border border-border rounded-md bg-card cursor-pointer hover:border-border focus:border-info focus:ring-1 focus:ring-ring"
className="w-[300px] px-3 py-2 text-sm text-left border border-border rounded-md bg-card cursor-pointer hover:border-ring focus:border-info focus:ring-1 focus:ring-ring"
onClick={() => setIsOpen(!isOpen)}
>
<span className="flex items-center justify-between">
@ -368,7 +368,7 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className={`w-65 px-3 py-2 text-sm border rounded-md cursor-pointer hover:border-border focus:border-info focus:ring-1 focus:ring-ring ${
className={`w-65 px-3 py-2 text-sm border rounded-md cursor-pointer hover:border-ring focus:border-info focus:ring-1 focus:ring-ring ${
!validation.isValid
? "border-destructive/30 focus:border-destructive focus:ring-red-200"
: "border-border"
@ -383,7 +383,7 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className={`w-65 px-3 py-2 text-sm border rounded-md cursor-pointer hover:border-border focus:border-info focus:ring-1 focus:ring-ring ${
className={`w-65 px-3 py-2 text-sm border rounded-md cursor-pointer hover:border-ring focus:border-info focus:ring-1 focus:ring-ring ${
!validation.isValid
? "border-destructive/30 focus:border-destructive focus:ring-red-200"
: "border-border"

View file

@ -278,7 +278,7 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
variant="ghost"
onClick={() => removeLoggingConfig(index)}
size="sm"
className="text-destructive hover:bg-destructive/10 hover:text-destructive"
className="text-destructive hover:bg-destructive/10"
type="button"
>
<Trash2 />

View file

@ -136,7 +136,7 @@ export function LiveTailBanner({ onStop }: { onStop: () => void }) {
return (
<div className="mb-4 flex items-center justify-between rounded-md border border-success/20 bg-success/10 px-4 py-2">
<span className="text-sm text-success">Auto-refreshing every 15 seconds</span>
<button type="button" onClick={onStop} className="text-sm text-success hover:text-success">
<button type="button" onClick={onStop} className="text-sm text-success hover:text-success/80">
Stop
</button>
</div>

View file

@ -0,0 +1,50 @@
import { RuleTester } from "eslint";
import rule from "../../scripts/eslint-rules/no-noop-hover-variant.mjs";
const ruleTester = new RuleTester({
languageOptions: { ecmaVersion: "latest", sourceType: "module" },
});
ruleTester.run("no-noop-hover-variant", rule as never, {
valid: [
'const c = "text-info hover:text-info/80";',
'const c = "bg-success hover:bg-success/80 text-white";',
'const c = "border-border hover:border-ring";',
'const c = "text-info hover:underline";',
'const c = "bg-info/10 hover:bg-info/20";',
'const c = "hover:bg-muted";',
'const c = "text-info focus:text-info";',
"const c = `cursor-pointer hover:bg-info/10 ${isSelected ? 'bg-info/10' : ''}`;",
"const c = `border-2 ${isSelected ? 'border-info bg-info/10' : 'border-border hover:border-ring'}`;",
],
invalid: [
{
code: 'const c = "text-info hover:text-info";',
errors: [{ messageId: "noop", data: { hover: "hover:text-info", base: "text-info" } }],
},
{
code: 'const c = "text-xs bg-success hover:bg-success text-white transition-colors";',
errors: [{ messageId: "noop", data: { hover: "hover:bg-success", base: "bg-success" } }],
},
{
code: 'const c = "border border-border hover:border-border";',
errors: [{ messageId: "noop", data: { hover: "hover:border-border", base: "border-border" } }],
},
{
code: 'const c = "text-destructive border-destructive hover:text-destructive hover:border-destructive";',
errors: [{ messageId: "noop" }, { messageId: "noop" }],
},
{
code: 'const c = "text-info underline hover:underline";',
errors: [{ messageId: "noop", data: { hover: "hover:underline", base: "underline" } }],
},
{
code: "const c = `text-info hover:text-info ${extra}`;",
errors: [{ messageId: "noop" }],
},
{
code: "const c = `p-2 ${active ? 'bg-info/15 text-info' : 'text-success hover:text-success'}`;",
errors: [{ messageId: "noop", data: { hover: "hover:text-success", base: "text-success" } }],
},
],
});