fix(ui): add dark mode support for provider modals

- Add dark mode styles to ProviderForm modal (background, borders, text)
- Update ApiKeyInput component with dark mode colors
- Add dark mode detection to ClaudeConfigEditor for JSON editor
- Apply dark mode styles to CodexConfigEditor textareas
- Update PresetSelector buttons for dark mode
- Ensure consistent amber color scheme for all hint boxes in dark mode

This ensures proper visibility and readability of add/edit provider dialogs
when dark mode is enabled.
This commit is contained in:
Jason
2025-09-13 21:45:34 +08:00
parent 0d2dedbb6d
commit 85ba24f1c3
5 changed files with 89 additions and 49 deletions

View File

@@ -646,17 +646,19 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
}} }}
> >
{/* Backdrop */} {/* Backdrop */}
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" /> <div className="absolute inset-0 bg-black/50 dark:bg-black/70 backdrop-blur-sm" />
{/* Modal */} {/* Modal */}
<div className="relative bg-white rounded-xl shadow-lg max-w-3xl w-full mx-4 max-h-[90vh] overflow-hidden flex flex-col"> <div className="relative bg-white dark:bg-gray-900 rounded-xl shadow-lg max-w-3xl w-full mx-4 max-h-[90vh] overflow-hidden flex flex-col">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200"> <div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-800">
<h2 className="text-xl font-semibold text-gray-900">{title}</h2> <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
{title}
</h2>
<button <button
type="button" type="button"
onClick={onClose} onClick={onClose}
className="p-1 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-md transition-colors" className="p-1 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
aria-label="关闭" aria-label="关闭"
> >
<X size={18} /> <X size={18} />
@@ -666,9 +668,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
<form onSubmit={handleSubmit} className="flex flex-col flex-1 min-h-0"> <form onSubmit={handleSubmit} className="flex flex-col flex-1 min-h-0">
<div className="flex-1 overflow-auto p-6 space-y-6"> <div className="flex-1 overflow-auto p-6 space-y-6">
{error && ( {error && (
<div className="flex items-center gap-3 p-4 bg-red-100 border border-red-500/20 rounded-lg"> <div className="flex items-center gap-3 p-4 bg-red-100 dark:bg-red-900/20 border border-red-500/20 dark:border-red-500/30 rounded-lg">
<AlertCircle size={20} className="text-red-500 flex-shrink-0" /> <AlertCircle
<p className="text-red-500 text-sm font-medium">{error}</p> size={20}
className="text-red-500 dark:text-red-400 flex-shrink-0"
/>
<p className="text-red-500 dark:text-red-400 text-sm font-medium">
{error}
</p>
</div> </div>
)} )}
@@ -697,7 +704,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
<div className="space-y-2"> <div className="space-y-2">
<label <label
htmlFor="name" htmlFor="name"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
* *
</label> </label>
@@ -710,14 +717,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
placeholder="例如Anthropic 官方" placeholder="例如Anthropic 官方"
required required
autoComplete="off" autoComplete="off"
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label <label
htmlFor="websiteUrl" htmlFor="websiteUrl"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
</label> </label>
@@ -729,7 +736,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
onChange={handleChange} onChange={handleChange}
placeholder="https://example.com可选" placeholder="https://example.com可选"
autoComplete="off" autoComplete="off"
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors"
/> />
</div> </div>
@@ -754,7 +761,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
href={getCurrentWebsiteUrl()} href={getCurrentWebsiteUrl()}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-xs text-blue-400 hover:text-blue-500 transition-colors" className="text-xs text-blue-400 dark:text-blue-500 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
> >
API Key API Key
</a> </a>
@@ -768,7 +775,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
<div className="space-y-2"> <div className="space-y-2">
<label <label
htmlFor="baseUrl" htmlFor="baseUrl"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
</label> </label>
@@ -779,10 +786,10 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
onChange={(e) => handleBaseUrlChange(e.target.value)} onChange={(e) => handleBaseUrlChange(e.target.value)}
placeholder="https://your-api-endpoint.com" placeholder="https://your-api-endpoint.com"
autoComplete="off" autoComplete="off"
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors"
/> />
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg"> <div className="p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-700 rounded-lg">
<p className="text-xs text-amber-600"> <p className="text-xs text-amber-600 dark:text-amber-400">
💡 Claude API 💡 Claude API
</p> </p>
</div> </div>
@@ -824,7 +831,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
href={getCurrentCodexWebsiteUrl()} href={getCurrentCodexWebsiteUrl()}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-xs text-blue-400 hover:text-blue-500 transition-colors" className="text-xs text-blue-400 dark:text-blue-500 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
> >
API Key API Key
</a> </a>
@@ -862,7 +869,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
<div className="space-y-2"> <div className="space-y-2">
<label <label
htmlFor="anthropicModel" htmlFor="anthropicModel"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
() ()
</label> </label>
@@ -875,14 +882,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
} }
placeholder="例如: GLM-4.5" placeholder="例如: GLM-4.5"
autoComplete="off" autoComplete="off"
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label <label
htmlFor="anthropicSmallFastModel" htmlFor="anthropicSmallFastModel"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
() ()
</label> </label>
@@ -898,13 +905,13 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
} }
placeholder="例如: GLM-4.5-Air" placeholder="例如: GLM-4.5-Air"
autoComplete="off" autoComplete="off"
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors"
/> />
</div> </div>
</div> </div>
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg"> <div className="p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-700 rounded-lg">
<p className="text-xs text-amber-600"> <p className="text-xs text-amber-600 dark:text-amber-400">
💡 使 💡 使
</p> </p>
</div> </div>
@@ -926,17 +933,17 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-end gap-3 p-6 border-t border-gray-200 bg-gray-100"> <div className="flex items-center justify-end gap-3 p-6 border-t border-gray-200 dark:border-gray-800 bg-gray-100 dark:bg-gray-800">
<button <button
type="button" type="button"
onClick={onClose} onClick={onClose}
className="px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-900 hover:bg-white rounded-lg transition-colors" className="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-white dark:hover:bg-gray-700 rounded-lg transition-colors"
> >
</button> </button>
<button <button
type="submit" type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium flex items-center gap-2" className="px-4 py-2 bg-blue-500 dark:bg-blue-600 text-white rounded-lg hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors text-sm font-medium flex items-center gap-2"
> >
<Save className="w-4 h-4" /> <Save className="w-4 h-4" />
{submitText} {submitText}

View File

@@ -28,13 +28,16 @@ const ApiKeyInput: React.FC<ApiKeyInputProps> = ({
const inputClass = `w-full px-3 py-2 pr-10 border rounded-lg text-sm transition-colors ${ const inputClass = `w-full px-3 py-2 pr-10 border rounded-lg text-sm transition-colors ${
disabled disabled
? "bg-gray-100 border-gray-200 text-gray-400 cursor-not-allowed" ? "bg-gray-100 dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed"
: "border-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500" : "border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400"
}`; }`;
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor={id} className="block text-sm font-medium text-gray-900"> <label
htmlFor={id}
className="block text-sm font-medium text-gray-900 dark:text-gray-100"
>
{label} {required && "*"} {label} {required && "*"}
</label> </label>
<div className="relative"> <div className="relative">
@@ -53,7 +56,7 @@ const ApiKeyInput: React.FC<ApiKeyInputProps> = ({
<button <button
type="button" type="button"
onClick={toggleShowKey} onClick={toggleShowKey}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-900 transition-colors" className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
aria-label={showKey ? "隐藏API Key" : "显示API Key"} aria-label={showKey ? "隐藏API Key" : "显示API Key"}
> >
{showKey ? <EyeOff size={16} /> : <Eye size={16} />} {showKey ? <EyeOff size={16} /> : <Eye size={16} />}

View File

@@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect, useState } from "react";
import JsonEditor from "../JsonEditor"; import JsonEditor from "../JsonEditor";
interface ClaudeConfigEditorProps { interface ClaudeConfigEditorProps {
@@ -14,21 +14,47 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
disableCoAuthored, disableCoAuthored,
onCoAuthoredToggle, onCoAuthoredToggle,
}) => { }) => {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
// 检测暗色模式
const checkDarkMode = () => {
setIsDarkMode(document.documentElement.classList.contains("dark"));
};
checkDarkMode();
// 监听暗色模式变化
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === "class") {
checkDarkMode();
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
return () => observer.disconnect();
}, []);
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label <label
htmlFor="settingsConfig" htmlFor="settingsConfig"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
Claude Code (JSON) * Claude Code (JSON) *
</label> </label>
<label className="inline-flex items-center gap-2 text-sm text-gray-500 cursor-pointer"> <label className="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 cursor-pointer">
<input <input
type="checkbox" type="checkbox"
checked={disableCoAuthored} checked={disableCoAuthored}
onChange={(e) => onCoAuthoredToggle(e.target.checked)} onChange={(e) => onCoAuthoredToggle(e.target.checked)}
className="w-4 h-4 text-blue-500 bg-white border-gray-200 rounded focus:ring-blue-500 focus:ring-2" className="w-4 h-4 text-blue-500 bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 rounded focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2"
/> />
Claude Code Claude Code
</label> </label>
@@ -36,6 +62,7 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
<JsonEditor <JsonEditor
value={value} value={value}
onChange={onChange} onChange={onChange}
darkMode={isDarkMode}
placeholder={`{ placeholder={`{
"env": { "env": {
"ANTHROPIC_BASE_URL": "https://your-api-endpoint.com", "ANTHROPIC_BASE_URL": "https://your-api-endpoint.com",
@@ -44,7 +71,7 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
}`} }`}
rows={12} rows={12}
/> />
<p className="text-xs text-gray-500"> <p className="text-xs text-gray-500 dark:text-gray-400">
Claude Code settings.json Claude Code settings.json
</p> </p>
</div> </div>

View File

@@ -20,7 +20,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
<div className="space-y-2"> <div className="space-y-2">
<label <label
htmlFor="codexAuth" htmlFor="codexAuth"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
auth.json (JSON) * auth.json (JSON) *
</label> </label>
@@ -34,15 +34,17 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
}`} }`}
rows={6} rows={6}
required required
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors resize-y min-h-[8rem]" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors resize-y min-h-[8rem]"
/> />
<p className="text-xs text-gray-500">Codex auth.json </p> <p className="text-xs text-gray-500 dark:text-gray-400">
Codex auth.json
</p>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label <label
htmlFor="codexConfig" htmlFor="codexConfig"
className="block text-sm font-medium text-gray-900" className="block text-sm font-medium text-gray-900 dark:text-gray-100"
> >
config.toml (TOML) config.toml (TOML)
</label> </label>
@@ -52,9 +54,11 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
onChange={(e) => onConfigChange(e.target.value)} onChange={(e) => onConfigChange(e.target.value)}
placeholder="" placeholder=""
rows={8} rows={8}
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors resize-y min-h-[10rem]" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 focus:border-blue-500 dark:focus:border-blue-400 transition-colors resize-y min-h-[10rem]"
/> />
<p className="text-xs text-gray-500">Codex config.toml </p> <p className="text-xs text-gray-500 dark:text-gray-400">
Codex config.toml
</p>
</div> </div>
</div> </div>
); );

View File

@@ -26,10 +26,7 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
onCustomClick, onCustomClick,
customLabel = "自定义", customLabel = "自定义",
}) => { }) => {
const getButtonClass = ( const getButtonClass = (index: number, preset?: Preset) => {
index: number,
preset?: Preset,
) => {
const isSelected = selectedIndex === index; const isSelected = selectedIndex === index;
const baseClass = const baseClass =
"inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors"; "inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors";
@@ -46,7 +43,7 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
return `${baseClass} bg-blue-500 text-white`; return `${baseClass} bg-blue-500 text-white`;
} }
return `${baseClass} bg-gray-100 text-gray-500 hover:bg-gray-200`; return `${baseClass} bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700`;
}; };
const getDescription = () => { const getDescription = () => {
@@ -67,7 +64,7 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-medium text-gray-900 mb-3"> <label className="block text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
{title} {title}
</label> </label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
@@ -102,7 +99,9 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
</div> </div>
</div> </div>
{getDescription() && ( {getDescription() && (
<p className="text-sm text-gray-500">{getDescription()}</p> <p className="text-sm text-gray-500 dark:text-gray-400">
{getDescription()}
</p>
)} )}
</div> </div>
); );