refactor(ui): improve icon rendering consistency and spacing

Standardize icon sizes and improve rendering consistency across the
application interface.

Changes:
- ProviderIcon: Add fontSize sync with size prop for embedded SVG
  scaling, improve fallback text sizing calculation
- AppSwitcher: Replace brand icon components with unified ProviderIcon,
  standardize icon size to 20px across all app tabs
- ProviderCard: Reduce icon size from 26px to 20px for better visual
  balance in card layout
- DeepLinkImportDialog: Increase confirmation icon size from 64px to
  80px for better visibility
- BasicFormFields: Reduce icon picker button spacing from space-y-6 to
  space-y-2 for improved layout density

Icon rendering improvements:
- Embedded SVGs now properly scale with fontSize set to match size prop
- Fallback initials use responsive font sizing (50% of icon size,
  minimum 12px)
- Consistent 20px standard for navigation and card icons
- Better visual hierarchy with appropriate sizing for different contexts

This refactor improves visual consistency and makes icon rendering
more predictable across different components.
This commit is contained in:
YoVinchen
2025-11-24 23:55:46 +08:00
parent dca1d822da
commit 1c739b7841
5 changed files with 37 additions and 15 deletions
+27 -10
View File
@@ -1,5 +1,5 @@
import type { AppId } from "@/lib/api";
import { ClaudeIcon, CodexIcon, GeminiIcon } from "./BrandIcons";
import { ProviderIcon } from "@/components/ProviderIcon";
interface AppSwitcherProps {
activeApp: AppId;
@@ -11,6 +11,17 @@ export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) {
if (app === activeApp) return;
onSwitch(app);
};
const iconSize = 20;
const appIconName: Record<AppId, string> = {
claude: "claude",
codex: "openai",
gemini: "gemini",
};
const appDisplayName: Record<AppId, string> = {
claude: "Claude",
codex: "Codex",
gemini: "Gemini",
};
return (
<div className="inline-flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 gap-1">
@@ -23,15 +34,17 @@ export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) {
: "text-gray-500 hover:text-gray-900 hover:bg-white/50 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800/60"
}`}
>
<ClaudeIcon
size={16}
<ProviderIcon
icon={appIconName.claude}
name={appDisplayName.claude}
size={iconSize}
className={
activeApp === "claude"
? "text-foreground"
: "text-gray-500 dark:text-gray-400 group-hover:text-foreground transition-colors"
}
/>
<span>Claude</span>
<span>{appDisplayName.claude}</span>
</button>
<button
@@ -43,15 +56,17 @@ export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) {
: "text-gray-500 hover:text-gray-900 hover:bg-white/50 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800/60"
}`}
>
<CodexIcon
size={16}
<ProviderIcon
icon={appIconName.codex}
name={appDisplayName.codex}
size={iconSize}
className={
activeApp === "codex"
? "text-foreground"
: "text-gray-500 dark:text-gray-400 group-hover:text-foreground transition-colors"
}
/>
<span>Codex</span>
<span>{appDisplayName.codex}</span>
</button>
<button
@@ -63,15 +78,17 @@ export function AppSwitcher({ activeApp, onSwitch }: AppSwitcherProps) {
: "text-gray-500 hover:text-gray-900 hover:bg-white/50 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800/60"
}`}
>
<GeminiIcon
size={16}
<ProviderIcon
icon={appIconName.gemini}
name={appDisplayName.gemini}
size={iconSize}
className={
activeApp === "gemini"
? "text-foreground"
: "text-gray-500 dark:text-gray-400 group-hover:text-foreground transition-colors"
}
/>
<span>Gemini</span>
<span>{appDisplayName.gemini}</span>
</button>
</div>
);
+1 -1
View File
@@ -338,7 +338,7 @@ export function DeepLinkImportDialog() {
<ProviderIcon
icon={request.icon}
name={request.name || request.icon}
size={64}
size={80}
className="drop-shadow-sm"
/>
</div>
+6 -1
View File
@@ -32,6 +32,9 @@ export const ProviderIcon: React.FC<ProviderIconProps> = ({
return {
width: sizeValue,
height: sizeValue,
// 内嵌 SVG 使用 1em 作为尺寸基准,这里同步 fontSize 让图标实际跟随 size 放大
fontSize: sizeValue,
lineHeight: 1,
};
}, [size]);
@@ -57,6 +60,8 @@ export const ProviderIcon: React.FC<ProviderIconProps> = ({
.join("")
.toUpperCase()
.slice(0, 2);
const fallbackFontSize =
typeof size === "number" ? `${Math.max(size * 0.5, 12)}px` : "0.5em";
return (
<span
className={cn(
@@ -68,7 +73,7 @@ export const ProviderIcon: React.FC<ProviderIconProps> = ({
>
<span
style={{
fontSize: `${typeof size === "number" ? size * 0.4 : 14}px`,
fontSize: fallbackFontSize,
}}
>
{initials}
+2 -2
View File
@@ -141,12 +141,12 @@ export function ProviderCard({
</button>
{/* 供应商图标 */}
<div className="h-9 w-9 rounded-lg bg-white/5 flex items-center justify-center border border-gray-200 dark:border-white/10 group-hover:scale-105 transition-transform duration-300">
<div className="h-8 w-8 rounded-lg bg-white/5 flex items-center justify-center border border-gray-200 dark:border-white/10 group-hover:scale-105 transition-transform duration-300">
<ProviderIcon
icon={provider.icon}
name={provider.name}
color={provider.iconColor}
size={26}
size={20}
/>
</div>
@@ -84,7 +84,7 @@ export function BasicFormFields({ form }: BasicFormFieldsProps) {
</div>
</div>
<div className="flex-1 overflow-y-auto">
<div className="space-y-6 mx-auto max-w-[56rem] px-6 py-6 w-full">
<div className="space-y-2 mx-auto max-w-[56rem] px-6 py-6 w-full">
<IconPicker
value={currentIcon}
onValueChange={handleIconSelect}