refactor(ui): unify layout system with 60rem max-width and consistent spacing
- Standardize container max-width across all panels: * Replace max-w-4xl and max-w-5xl with unified max-w-[60rem] * Apply to SettingsPage, UnifiedMcpPanel, SkillsPage, and FullScreenPanel * Ensures consistent reading width and visual balance on wide screens - Optimize padding hierarchy and structure: * Move px-6 from parent elements to content containers * FullScreenPanel: Add max-w-[60rem] wrapper to header, content, and footer * Add border separators (border-t/border-b) to header and footer sections * Consolidate nested padding in MCP, Skills, and Prompts panels * Remove redundant padding layers for cleaner CSS - Simplify component styling: * MCP list items: Replace card-based layout with modern group-based design * Remove unnecessary wrapper divs and flatten DOM structure * Update card hover effects with smooth transitions * Simplify icon selection dialog (remove description text in BasicFormFields) - Benefits: * Consistent 60rem max-width provides optimal readability * Unified spacing rules reduce maintenance complexity * Cleaner component hierarchy improves performance * Better responsive behavior across different screen sizes * More cohesive visual design language throughout the app This creates a maintainable, scalable design system foundation.
This commit is contained in:
@@ -32,9 +32,10 @@ export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
className="flex-shrink-0 px-6 py-4 flex items-center gap-4"
|
||||
className="flex-shrink-0 py-4 border-b border-border-default"
|
||||
style={{ backgroundColor: "hsl(var(--background))" }}
|
||||
>
|
||||
<div className="mx-auto max-w-[60rem] px-6 flex items-center gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -46,20 +47,25 @@ export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
|
||||
</Button>
|
||||
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-6 space-y-6 max-w-5xl mx-auto w-full">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="mx-auto max-w-[60rem] px-6 py-6 space-y-6 w-full">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
{footer && (
|
||||
<div
|
||||
className="flex-shrink-0 px-6 py-4 flex items-center justify-end gap-3"
|
||||
className="flex-shrink-0 py-4 border-t border-border-default"
|
||||
style={{ backgroundColor: "hsl(var(--background))" }}
|
||||
>
|
||||
<div className="mx-auto max-w-[60rem] px-6 flex items-center justify-end gap-3">
|
||||
{footer}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
document.body,
|
||||
|
||||
@@ -409,7 +409,27 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<FullScreenPanel isOpen={true} title={getFormTitle()} onClose={onClose}>
|
||||
<FullScreenPanel
|
||||
isOpen={true}
|
||||
title={getFormTitle()}
|
||||
onClose={onClose}
|
||||
footer={
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={saving || (!isEditing && !!idError)}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isEditing ? <Save size={16} /> : <Plus size={16} />}
|
||||
{saving
|
||||
? t("common.saving")
|
||||
: isEditing
|
||||
? t("common.save")
|
||||
: t("common.add")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="glass rounded-xl p-6 border border-white/10 space-y-6">
|
||||
{/* 预设选择(仅新增时展示) */}
|
||||
{!isEditing && (
|
||||
<div>
|
||||
@@ -420,8 +440,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={applyCustom}
|
||||
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
selectedPreset === -1
|
||||
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${selectedPreset === -1
|
||||
? "bg-emerald-500 text-white dark:bg-emerald-600"
|
||||
: "bg-accent text-muted-foreground hover:bg-accent/80"
|
||||
}`}
|
||||
@@ -435,8 +454,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
key={preset.id}
|
||||
type="button"
|
||||
onClick={() => applyPreset(idx)}
|
||||
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
selectedPreset === idx
|
||||
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${selectedPreset === idx
|
||||
? "bg-emerald-500 text-white dark:bg-emerald-600"
|
||||
: "bg-accent text-muted-foreground hover:bg-accent/80"
|
||||
}`}
|
||||
@@ -547,7 +565,11 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
onClick={() => setShowMetadata(!showMetadata)}
|
||||
className="flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{showMetadata ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
{showMetadata ? (
|
||||
<ChevronUp size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} />
|
||||
)}
|
||||
{t("mcp.form.additionalInfo")}
|
||||
</button>
|
||||
</div>
|
||||
@@ -609,7 +631,9 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{useToml ? t("mcp.form.tomlConfig") : t("mcp.form.jsonConfig")}
|
||||
{useToml
|
||||
? t("mcp.form.tomlConfig")
|
||||
: t("mcp.form.jsonConfig")}
|
||||
</label>
|
||||
{(isEditing || selectedPreset === -1) && (
|
||||
<button
|
||||
@@ -642,21 +666,6 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-6">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={saving || (!isEditing && !!idError)}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isEditing ? <Save size={16} /> : <Plus size={16} />}
|
||||
{saving
|
||||
? t("common.saving")
|
||||
: isEditing
|
||||
? t("common.save")
|
||||
: t("common.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</FullScreenPanel>
|
||||
|
||||
|
||||
@@ -115,9 +115,9 @@ const UnifiedMcpPanel = React.forwardRef<
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl flex flex-col h-[calc(100vh-8rem)] overflow-hidden">
|
||||
<div className="mx-auto max-w-[60rem] px-6 flex flex-col h-[calc(100vh-8rem)] overflow-hidden">
|
||||
{/* Info Section */}
|
||||
<div className="flex-shrink-0 px-6 py-4 glass rounded-xl border border-white/10 mb-4">
|
||||
<div className="flex-shrink-0 py-4 glass rounded-xl border border-white/10 mb-4 px-6">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("mcp.serverCount", { count: serverEntries.length })} ·{" "}
|
||||
{t("mcp.unifiedPanel.apps.claude")}: {enabledCounts.claude} ·{" "}
|
||||
@@ -127,7 +127,7 @@ const UnifiedMcpPanel = React.forwardRef<
|
||||
</div>
|
||||
|
||||
{/* Content - Scrollable */}
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden px-6 pb-24">
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden pb-24">
|
||||
{isLoading ? (
|
||||
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
|
||||
{t("mcp.loading")}
|
||||
@@ -233,8 +233,7 @@ const UnifiedMcpListItem: React.FC<UnifiedMcpListItemProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-16 rounded-lg border border-border-default bg-card p-4 transition-[border-color,box-shadow] duration-200 hover:border-border-hover hover:shadow-sm">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="group relative flex items-center gap-4 p-4 rounded-xl border border-white/10 bg-gray-900/40 hover:bg-gray-900/60 transition-all duration-300">
|
||||
{/* 左侧:服务器信息 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
@@ -340,7 +339,6 @@ const UnifiedMcpListItem: React.FC<UnifiedMcpListItemProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -91,7 +91,22 @@ const PromptFormPanel: React.FC<PromptFormPanelProps> = ({
|
||||
: t("prompts.addTitle", { appName });
|
||||
|
||||
return (
|
||||
<FullScreenPanel isOpen={true} title={title} onClose={onClose}>
|
||||
<FullScreenPanel
|
||||
isOpen={true}
|
||||
title={title}
|
||||
onClose={onClose}
|
||||
footer={
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={!name.trim() || !content.trim() || saving}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="glass rounded-xl p-6 border border-white/10 space-y-6">
|
||||
<div>
|
||||
<Label htmlFor="name" className="text-foreground">
|
||||
{t("prompts.name")}
|
||||
@@ -130,16 +145,6 @@ const PromptFormPanel: React.FC<PromptFormPanelProps> = ({
|
||||
minHeight="500px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-6">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={!name.trim() || !content.trim() || saving}
|
||||
className="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FullScreenPanel>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ const PromptListItem: React.FC<PromptListItemProps> = ({
|
||||
const enabled = prompt.enabled === true;
|
||||
|
||||
return (
|
||||
<div className="h-16 rounded-lg border border-border-default bg-card p-4 transition-[border-color,box-shadow] duration-200 hover:border-border-hover hover:shadow-sm">
|
||||
<div className="group relative h-16 rounded-xl border border-white/10 bg-gray-900/40 p-4 transition-all duration-300 hover:bg-gray-900/60 hover:border-white/20 hover:shadow-sm">
|
||||
<div className="flex items-center gap-4 h-full">
|
||||
{/* Toggle 开关 */}
|
||||
<div className="flex-shrink-0">
|
||||
|
||||
@@ -80,18 +80,11 @@ export function BasicFormFields({ form }: BasicFormFieldsProps) {
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<div className="space-y-1">
|
||||
<p className="text-lg font-semibold leading-tight">
|
||||
{t("providerIcon.selectIcon", {
|
||||
defaultValue: "选择图标",
|
||||
})}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("providerIcon.selectDescription", {
|
||||
defaultValue: "为供应商选择一个图标",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto px-6 py-6">
|
||||
<div className="space-y-6 max-w-5xl mx-auto w-full">
|
||||
|
||||
@@ -655,7 +655,7 @@ export function ProviderForm({
|
||||
<form
|
||||
id="provider-form"
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-6"
|
||||
className="space-y-6 glass rounded-xl p-6 border border-white/10"
|
||||
>
|
||||
{/* 预设供应商选择(仅新增模式显示) */}
|
||||
{!initialData && (
|
||||
|
||||
@@ -168,7 +168,7 @@ export function SettingsPage({
|
||||
const isBusy = useMemo(() => isLoading && !settings, [isLoading, settings]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl flex flex-col h-[calc(100vh-8rem)]">
|
||||
<div className="mx-auto max-w-[60rem] flex flex-col h-[calc(100vh-8rem)] px-6">
|
||||
{isBusy ? (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
|
||||
@@ -93,7 +93,7 @@ export function RepoManagerPanel({
|
||||
onClose={onClose}
|
||||
>
|
||||
{/* 添加仓库表单 */}
|
||||
<div className="space-y-4 glass-card rounded-xl p-6 border border-border/10">
|
||||
<div className="space-y-4 glass rounded-xl p-6 border border-white/10">
|
||||
<h3 className="text-base font-semibold text-foreground">
|
||||
添加技能仓库
|
||||
</h3>
|
||||
@@ -156,7 +156,7 @@ export function RepoManagerPanel({
|
||||
{t("skills.repo.list")}
|
||||
</h3>
|
||||
{repos.length === 0 ? (
|
||||
<div className="text-center py-12 glass-card rounded-xl border border-border/10">
|
||||
<div className="text-center py-12 glass rounded-xl border border-white/10">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("skills.repo.empty")}
|
||||
</p>
|
||||
@@ -166,7 +166,7 @@ export function RepoManagerPanel({
|
||||
{repos.map((repo) => (
|
||||
<div
|
||||
key={`${repo.owner}/${repo.name}`}
|
||||
className="flex items-center justify-between rounded-xl border border-border/10 glass-card px-4 py-3"
|
||||
className="flex items-center justify-between rounded-xl border border-white/10 bg-gray-900/40 px-4 py-3"
|
||||
>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
|
||||
@@ -57,7 +57,7 @@ export function SkillCard({ skill, onInstall, onUninstall }: SkillCardProps) {
|
||||
skill.directory.trim().toLowerCase() !== skill.name.trim().toLowerCase();
|
||||
|
||||
return (
|
||||
<Card className="glass-card flex flex-col h-full border-white/5 transition-all duration-300 hover:bg-white/[0.02] hover:border-primary/50 hover:shadow-lg hover:-translate-y-1 group relative overflow-hidden">
|
||||
<Card className="glass flex flex-col h-full border border-white/10 bg-gray-900/40 transition-all duration-300 hover:bg-gray-900/60 hover:border-white/20 hover:shadow-lg group relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none" />
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
|
||||
@@ -167,7 +167,8 @@ export const SkillsPage = forwardRef<SkillsPageHandle, SkillsPageProps>(
|
||||
{/* 顶部操作栏(固定区域)已移除,由 App.tsx 接管 */}
|
||||
|
||||
{/* 技能网格(可滚动详情区域) */}
|
||||
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-4 animate-fade-in">
|
||||
<div className="flex-1 min-h-0 overflow-y-auto animate-fade-in">
|
||||
<div className="mx-auto max-w-[60rem] px-6 py-4">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
@@ -201,6 +202,7 @@ export const SkillsPage = forwardRef<SkillsPageHandle, SkillsPageProps>(
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 仓库管理面板 */}
|
||||
{repoManagerOpen && (
|
||||
|
||||
Reference in New Issue
Block a user