Add light/dark mode slider for dev

This commit is contained in:
yuneng-jiang 2026-01-26 11:22:14 -08:00
parent 4a6dcf3012
commit 9791293fb0
6 changed files with 257 additions and 215 deletions

View File

@ -51,12 +51,14 @@ LiteLLM is a unified interface for 100+ LLMs that:
### MAKING CODE CHANGES FOR THE UI (IGNORE FOR BACKEND)
1. **Use Common Components as much as possible**:
1. **Tremor is DEPRECATED, do not use Tremor components in new features/changes**
- The only exception is the Tremor Table component and its required Tremor Table sub components.
2. **Use Common Components as much as possible**:
- These are usually defined in the `common_components` directory
- Use these components as much as possible and avoid building new components unless needed
- Tremor components are deprecated; prefer using Ant Design (AntD) as much as possible
2. **Testing**:
3. **Testing**:
- The codebase uses **Vitest** and **React Testing Library**
- **Query Priority Order**: Use query methods in this order: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByTestId`
- **Always use `screen`** instead of destructuring from `render()` (e.g., use `screen.getByText()` not `getByText`)

View File

@ -56,8 +56,10 @@ export default function Layout({ children }: { children: React.ReactNode }) {
userRole={userRole}
premiumUser={premiumUser}
proxySettings={undefined}
setProxySettings={() => {}}
setProxySettings={() => { }}
accessToken={accessToken}
isDarkMode={false}
toggleDarkMode={() => { }}
/>
<div className="flex flex-1 overflow-auto">
<div className="mt-2">

View File

@ -45,6 +45,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { jwtDecode } from "jwt-decode";
import { useSearchParams } from "next/navigation";
import { Suspense, useEffect, useState } from "react";
import { ConfigProvider, theme } from "antd";
function getCookie(name: string) {
// Safer cookie read + decoding; handles '=' inside values
@ -131,6 +132,12 @@ export default function CreateKeyPage() {
const [showClaudeCodePrompt, setShowClaudeCodePrompt] = useState(false);
const [showClaudeCodeModal, setShowClaudeCodeModal] = useState(false);
// Dark mode state
const [isDarkMode, setIsDarkMode] = useState(false);
const toggleDarkMode = () => {
setIsDarkMode(!isDarkMode);
};
const invitation_id = searchParams.get("invitation_id");
// Get page from URL, default to 'api-keys' if not present
@ -282,7 +289,7 @@ export default function CreateKeyPage() {
const nudgesConfig = await getInProductNudgesCall(accessToken);
const isUsingClaudeCode = nudgesConfig?.is_claude_code_enabled || false;
setIsClaudeCode(isUsingClaudeCode);
// Show Claude Code prompt on login if enabled
if (isUsingClaudeCode) {
setShowClaudeCodePrompt(true);
@ -362,225 +369,231 @@ export default function CreateKeyPage() {
return (
<Suspense fallback={<LoadingScreen />}>
<QueryClientProvider client={queryClient}>
<ThemeProvider accessToken={accessToken}>
{invitation_id ? (
<UserDashboard
userID={userID}
userRole={userRole}
premiumUser={premiumUser}
teams={teams}
keys={keys}
setUserRole={setUserRole}
userEmail={userEmail}
setUserEmail={setUserEmail}
setTeams={setTeams}
setKeys={setKeys}
organizations={organizations}
addKey={addKey}
createClicked={createClicked}
/>
) : (
<div className="flex flex-col min-h-screen">
<Navbar
<ConfigProvider theme={{
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
}}>
<ThemeProvider accessToken={accessToken}>
{invitation_id ? (
<UserDashboard
userID={userID}
userRole={userRole}
premiumUser={premiumUser}
teams={teams}
keys={keys}
setUserRole={setUserRole}
userEmail={userEmail}
setProxySettings={setProxySettings}
proxySettings={proxySettings}
accessToken={accessToken}
isPublicPage={false}
sidebarCollapsed={sidebarCollapsed}
onToggleSidebar={toggleSidebar}
setUserEmail={setUserEmail}
setTeams={setTeams}
setKeys={setKeys}
organizations={organizations}
addKey={addKey}
createClicked={createClicked}
/>
<div className="flex flex-1">
<div className="mt-2">
<SidebarProvider setPage={updatePage} defaultSelectedKey={page} sidebarCollapsed={sidebarCollapsed} />
</div>
) : (
<div className="flex flex-col min-h-screen">
<Navbar
userID={userID}
userRole={userRole}
premiumUser={premiumUser}
userEmail={userEmail}
setProxySettings={setProxySettings}
proxySettings={proxySettings}
accessToken={accessToken}
isPublicPage={false}
sidebarCollapsed={sidebarCollapsed}
onToggleSidebar={toggleSidebar}
isDarkMode={isDarkMode}
toggleDarkMode={toggleDarkMode}
/>
<div className="flex flex-1">
<div className="mt-2">
<SidebarProvider setPage={updatePage} defaultSelectedKey={page} sidebarCollapsed={sidebarCollapsed} />
</div>
{page == "api-keys" ? (
<UserDashboard
userID={userID}
userRole={userRole}
premiumUser={premiumUser}
teams={teams}
keys={keys}
setUserRole={setUserRole}
userEmail={userEmail}
setUserEmail={setUserEmail}
setTeams={setTeams}
setKeys={setKeys}
organizations={organizations}
addKey={addKey}
createClicked={createClicked}
/>
) : page == "models" ? (
<OldModelDashboard
token={token}
keys={keys}
modelData={modelData}
setModelData={setModelData}
premiumUser={premiumUser}
teams={teams}
/>
) : page == "llm-playground" ? (
<PlaygroundPage />
) : page == "users" ? (
<ViewUserDashboard
userID={userID}
userRole={userRole}
token={token}
keys={keys}
teams={teams}
accessToken={accessToken}
setKeys={setKeys}
/>
) : page == "teams" ? (
<OldTeams
teams={teams}
setTeams={setTeams}
accessToken={accessToken}
userID={userID}
userRole={userRole}
organizations={organizations}
premiumUser={premiumUser}
searchParams={searchParams}
/>
) : page == "organizations" ? (
<Organizations
organizations={organizations}
setOrganizations={setOrganizations}
userModels={userModels}
accessToken={accessToken}
userRole={userRole}
premiumUser={premiumUser}
/>
) : page == "admin-panel" ? (
<AdminPanel
setTeams={setTeams}
searchParams={searchParams}
accessToken={accessToken}
userID={userID}
showSSOBanner={showSSOBanner}
premiumUser={premiumUser}
proxySettings={proxySettings}
/>
) : page == "api_ref" ? (
<APIReferenceView proxySettings={proxySettings} />
) : page == "logging-and-alerts" ? (
<Settings userID={userID} userRole={userRole} accessToken={accessToken} premiumUser={premiumUser} />
) : page == "budgets" ? (
<BudgetPanel accessToken={accessToken} />
) : page == "guardrails" ? (
<GuardrailsPanel accessToken={accessToken} userRole={userRole} />
) : page == "policies" ? (
<PoliciesPanel accessToken={accessToken} userRole={userRole} />
) : page == "agents" ? (
<AgentsPanel accessToken={accessToken} userRole={userRole} />
) : page == "prompts" ? (
<PromptsPanel accessToken={accessToken} userRole={userRole} />
) : page == "transform-request" ? (
<TransformRequestPanel accessToken={accessToken} />
) : page == "router-settings" ? (
<GeneralSettings
userID={userID}
userRole={userRole}
accessToken={accessToken}
modelData={modelData}
/>
) : page == "ui-theme" ? (
<UIThemeSettings userID={userID} userRole={userRole} accessToken={accessToken} />
) : page == "cost-tracking" ? (
<CostTrackingSettings userID={userID} userRole={userRole} accessToken={accessToken} />
) : page == "model-hub-table" ? (
isAdminRole(userRole) ? (
<ModelHubTable
accessToken={accessToken}
publicPage={false}
premiumUser={premiumUser}
{page == "api-keys" ? (
<UserDashboard
userID={userID}
userRole={userRole}
premiumUser={premiumUser}
teams={teams}
keys={keys}
setUserRole={setUserRole}
userEmail={userEmail}
setUserEmail={setUserEmail}
setTeams={setTeams}
setKeys={setKeys}
organizations={organizations}
addKey={addKey}
createClicked={createClicked}
/>
) : page == "models" ? (
<OldModelDashboard
token={token}
keys={keys}
modelData={modelData}
setModelData={setModelData}
premiumUser={premiumUser}
teams={teams}
/>
) : page == "llm-playground" ? (
<PlaygroundPage />
) : page == "users" ? (
<ViewUserDashboard
userID={userID}
userRole={userRole}
token={token}
keys={keys}
teams={teams}
accessToken={accessToken}
setKeys={setKeys}
/>
) : page == "teams" ? (
<OldTeams
teams={teams}
setTeams={setTeams}
accessToken={accessToken}
userID={userID}
userRole={userRole}
organizations={organizations}
premiumUser={premiumUser}
searchParams={searchParams}
/>
) : page == "organizations" ? (
<Organizations
organizations={organizations}
setOrganizations={setOrganizations}
userModels={userModels}
accessToken={accessToken}
userRole={userRole}
premiumUser={premiumUser}
/>
) : page == "admin-panel" ? (
<AdminPanel
setTeams={setTeams}
searchParams={searchParams}
accessToken={accessToken}
userID={userID}
showSSOBanner={showSSOBanner}
premiumUser={premiumUser}
proxySettings={proxySettings}
/>
) : page == "api_ref" ? (
<APIReferenceView proxySettings={proxySettings} />
) : page == "logging-and-alerts" ? (
<Settings userID={userID} userRole={userRole} accessToken={accessToken} premiumUser={premiumUser} />
) : page == "budgets" ? (
<BudgetPanel accessToken={accessToken} />
) : page == "guardrails" ? (
<GuardrailsPanel accessToken={accessToken} userRole={userRole} />
) : page == "policies" ? (
<PoliciesPanel accessToken={accessToken} userRole={userRole} />
) : page == "agents" ? (
<AgentsPanel accessToken={accessToken} userRole={userRole} />
) : page == "prompts" ? (
<PromptsPanel accessToken={accessToken} userRole={userRole} />
) : page == "transform-request" ? (
<TransformRequestPanel accessToken={accessToken} />
) : page == "router-settings" ? (
<GeneralSettings
userID={userID}
userRole={userRole}
accessToken={accessToken}
modelData={modelData}
/>
) : page == "ui-theme" ? (
<UIThemeSettings userID={userID} userRole={userRole} accessToken={accessToken} />
) : page == "cost-tracking" ? (
<CostTrackingSettings userID={userID} userRole={userRole} accessToken={accessToken} />
) : page == "model-hub-table" ? (
isAdminRole(userRole) ? (
<ModelHubTable
accessToken={accessToken}
publicPage={false}
premiumUser={premiumUser}
userRole={userRole}
/>
) : (
<PublicModelHub accessToken={accessToken} isEmbedded={true} />
)
) : page == "caching" ? (
<CacheDashboard
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
premiumUser={premiumUser}
/>
) : page == "pass-through-settings" ? (
<PassThroughSettings
userID={userID}
userRole={userRole}
accessToken={accessToken}
modelData={modelData}
premiumUser={premiumUser}
/>
) : page == "logs" ? (
<SpendLogsTable
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
allTeams={(teams as Team[]) ?? []}
premiumUser={premiumUser}
/>
) : page == "mcp-servers" ? (
<MCPServers accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "search-tools" ? (
<SearchTools accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "tag-management" ? (
<TagManagement accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "claude-code-plugins" ? (
<ClaudeCodePluginsPanel accessToken={accessToken} userRole={userRole} />
) : page == "vector-stores" ? (
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "new_usage" ? (
<NewUsagePage
teams={(teams as Team[]) ?? []}
organizations={(organizations as Organization[]) ?? []}
/>
) : (
<PublicModelHub accessToken={accessToken} isEmbedded={true} />
)
) : page == "caching" ? (
<CacheDashboard
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
premiumUser={premiumUser}
/>
) : page == "pass-through-settings" ? (
<PassThroughSettings
userID={userID}
userRole={userRole}
accessToken={accessToken}
modelData={modelData}
premiumUser={premiumUser}
/>
) : page == "logs" ? (
<SpendLogsTable
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
allTeams={(teams as Team[]) ?? []}
premiumUser={premiumUser}
/>
) : page == "mcp-servers" ? (
<MCPServers accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "search-tools" ? (
<SearchTools accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "tag-management" ? (
<TagManagement accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "claude-code-plugins" ? (
<ClaudeCodePluginsPanel accessToken={accessToken} userRole={userRole} />
) : page == "vector-stores" ? (
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "new_usage" ? (
<NewUsagePage
teams={(teams as Team[]) ?? []}
organizations={(organizations as Organization[]) ?? []}
/>
) : (
<Usage
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
keys={keys}
premiumUser={premiumUser}
/>
)}
<Usage
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
keys={keys}
premiumUser={premiumUser}
/>
)}
</div>
{/* Survey Components */}
<SurveyPrompt
isVisible={showSurveyPrompt}
onOpen={handleOpenSurvey}
onDismiss={handleDismissSurveyPrompt}
/>
<SurveyModal
isOpen={showSurveyModal}
onClose={handleSurveyModalClose}
onComplete={handleSurveyComplete}
/>
{/* Claude Code Components */}
<ClaudeCodePrompt
isVisible={showClaudeCodePrompt}
onOpen={handleOpenClaudeCode}
onDismiss={handleDismissClaudeCodePrompt}
/>
<ClaudeCodeModal
isOpen={showClaudeCodeModal}
onClose={handleClaudeCodeModalClose}
onComplete={handleClaudeCodeComplete}
/>
</div>
{/* Survey Components */}
<SurveyPrompt
isVisible={showSurveyPrompt}
onOpen={handleOpenSurvey}
onDismiss={handleDismissSurveyPrompt}
/>
<SurveyModal
isOpen={showSurveyModal}
onClose={handleSurveyModalClose}
onComplete={handleSurveyComplete}
/>
{/* Claude Code Components */}
<ClaudeCodePrompt
isVisible={showClaudeCodePrompt}
onOpen={handleOpenClaudeCode}
onDismiss={handleDismissClaudeCodePrompt}
/>
<ClaudeCodeModal
isOpen={showClaudeCodeModal}
onClose={handleClaudeCodeModalClose}
onComplete={handleClaudeCodeComplete}
/>
</div>
)}
</ThemeProvider>
)}
</ThemeProvider>
</ConfigProvider>
</QueryClientProvider>
</Suspense>
);

View File

@ -52,6 +52,8 @@ describe("Navbar", () => {
setProxySettings: vi.fn(),
accessToken: "test-token",
isPublicPage: false,
isDarkMode: false,
toggleDarkMode: vi.fn(),
};
it("should render without crashing", () => {
@ -198,4 +200,11 @@ describe("Navbar", () => {
expect(cookieUtils.clearTokenCookies).toHaveBeenCalled();
expect(window.location.href).toBe("");
});
it("should not render dark mode toggle slider", () => {
renderWithProviders(<Navbar {...defaultProps} />);
// DO NOT RENDER THIS UNTIL ALL COMPONENTS ARE CONFIRMED TO SUPPORT DARK MODE STYLES. IT IS AN ISSUE IF THIS TEST FAILS.
expect(screen.queryByTestId("dark-mode-toggle")).not.toBeInTheDocument();
});
});

View File

@ -16,8 +16,10 @@ import {
MailOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
MoonOutlined,
SafetyOutlined,
SlackOutlined,
SunOutlined,
UserOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
@ -36,6 +38,8 @@ interface NavbarProps {
isPublicPage: boolean;
sidebarCollapsed?: boolean;
onToggleSidebar?: () => void;
isDarkMode: boolean;
toggleDarkMode: () => void;
}
const Navbar: React.FC<NavbarProps> = ({
@ -49,9 +53,10 @@ const Navbar: React.FC<NavbarProps> = ({
isPublicPage = false,
sidebarCollapsed = false,
onToggleSidebar,
isDarkMode,
toggleDarkMode
}) => {
const baseUrl = getProxyBaseUrl();
console.log("baseUrl", baseUrl);
const [logoutUrl, setLogoutUrl] = useState("");
const [disableShowNewBadge, setDisableShowNewBadge] = useState(false);
const { logoUrl } = useTheme();
@ -227,6 +232,15 @@ const Navbar: React.FC<NavbarProps> = ({
>
Star us on GitHub
</Button>
{/* Dark mode is currently a work in progress. To test, you can change 'false' to 'true' below.
Do not set this to true by default until all components are confirmed to support dark mode styles. */}
{false && <Switch
data-testid="dark-mode-toggle"
checked={isDarkMode}
onChange={toggleDarkMode}
checkedChildren={<MoonOutlined />}
unCheckedChildren={<SunOutlined />}
/>}
<a
href="https://docs.litellm.ai/docs/"
target="_blank"

View File

@ -962,6 +962,8 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
proxySettings={proxySettings}
accessToken={accessToken || null}
isPublicPage={true}
isDarkMode={false}
toggleDarkMode={() => { }}
/>
)}