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:
YoVinchen
2025-11-22 02:41:17 +08:00
parent 0d4be40c25
commit 127fa5bf9d
11 changed files with 458 additions and 445 deletions

View File

@@ -32,33 +32,39 @@ export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
> >
{/* Header */} {/* Header */}
<div <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))" }} style={{ backgroundColor: "hsl(var(--background))" }}
> >
<Button <div className="mx-auto max-w-[60rem] px-6 flex items-center gap-4">
type="button" <Button
variant="ghost" type="button"
size="icon" variant="ghost"
onClick={onClose} size="icon"
className="hover:bg-black/5 dark:hover:bg-white/5" onClick={onClose}
> className="hover:bg-black/5 dark:hover:bg-white/5"
<ArrowLeft className="h-5 w-5" /> >
</Button> <ArrowLeft className="h-5 w-5" />
<h2 className="text-lg font-semibold text-foreground">{title}</h2> </Button>
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
</div>
</div> </div>
{/* Content */} {/* 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">
{children} <div className="mx-auto max-w-[60rem] px-6 py-6 space-y-6 w-full">
{children}
</div>
</div> </div>
{/* Footer */} {/* Footer */}
{footer && ( {footer && (
<div <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))" }} style={{ backgroundColor: "hsl(var(--background))" }}
> >
{footer} <div className="mx-auto max-w-[60rem] px-6 flex items-center justify-end gap-3">
{footer}
</div>
</div> </div>
)} )}
</div>, </div>,

View File

@@ -409,241 +409,11 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
return ( return (
<> <>
<FullScreenPanel isOpen={true} title={getFormTitle()} onClose={onClose}> <FullScreenPanel
{/* 预设选择(仅新增时展示) */} isOpen={true}
{!isEditing && ( title={getFormTitle()}
<div> onClose={onClose}
<label className="block text-sm font-medium text-foreground mb-3"> footer={
{t("mcp.presets.title")}
</label>
<div className="flex flex-wrap gap-2">
<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
? "bg-emerald-500 text-white dark:bg-emerald-600"
: "bg-accent text-muted-foreground hover:bg-accent/80"
}`}
>
{t("presetSelector.custom")}
</button>
{mcpPresets.map((preset, idx) => {
const descriptionKey = `mcp.presets.${preset.id}.description`;
return (
<button
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
? "bg-emerald-500 text-white dark:bg-emerald-600"
: "bg-accent text-muted-foreground hover:bg-accent/80"
}`}
title={t(descriptionKey)}
>
{preset.id}
</button>
);
})}
</div>
</div>
)}
{/* ID (标题) */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-foreground">
{t("mcp.form.title")} <span className="text-red-500">*</span>
</label>
{!isEditing && idError && (
<span className="text-xs text-red-500 dark:text-red-400">
{idError}
</span>
)}
</div>
<Input
type="text"
placeholder={t("mcp.form.titlePlaceholder")}
value={formId}
onChange={(e) => handleIdChange(e.target.value)}
disabled={isEditing}
/>
</div>
{/* Name */}
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.name")}
</label>
<Input
type="text"
placeholder={t("mcp.form.namePlaceholder")}
value={formName}
onChange={(e) => setFormName(e.target.value)}
/>
</div>
{/* 启用到哪些应用 */}
<div>
<label className="block text-sm font-medium text-foreground mb-3">
{t("mcp.form.enabledApps")}
</label>
<div className="flex flex-wrap gap-4">
<div className="flex items-center gap-2">
<Checkbox
id="enable-claude"
checked={enabledApps.claude}
onCheckedChange={(checked: boolean) =>
setEnabledApps({ ...enabledApps, claude: checked })
}
/>
<label
htmlFor="enable-claude"
className="text-sm text-foreground cursor-pointer select-none"
>
{t("mcp.unifiedPanel.apps.claude")}
</label>
</div>
<div className="flex items-center gap-2">
<Checkbox
id="enable-codex"
checked={enabledApps.codex}
onCheckedChange={(checked: boolean) =>
setEnabledApps({ ...enabledApps, codex: checked })
}
/>
<label
htmlFor="enable-codex"
className="text-sm text-foreground cursor-pointer select-none"
>
{t("mcp.unifiedPanel.apps.codex")}
</label>
</div>
<div className="flex items-center gap-2">
<Checkbox
id="enable-gemini"
checked={enabledApps.gemini}
onCheckedChange={(checked: boolean) =>
setEnabledApps({ ...enabledApps, gemini: checked })
}
/>
<label
htmlFor="enable-gemini"
className="text-sm text-foreground cursor-pointer select-none"
>
{t("mcp.unifiedPanel.apps.gemini")}
</label>
</div>
</div>
</div>
{/* 可折叠的附加信息按钮 */}
<div>
<button
type="button"
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} />}
{t("mcp.form.additionalInfo")}
</button>
</div>
{/* 附加信息区域(可折叠) */}
{showMetadata && (
<>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.description")}
</label>
<Input
type="text"
placeholder={t("mcp.form.descriptionPlaceholder")}
value={formDescription}
onChange={(e) => setFormDescription(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.tags")}
</label>
<Input
type="text"
placeholder={t("mcp.form.tagsPlaceholder")}
value={formTags}
onChange={(e) => setFormTags(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.homepage")}
</label>
<Input
type="text"
placeholder={t("mcp.form.homepagePlaceholder")}
value={formHomepage}
onChange={(e) => setFormHomepage(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.docs")}
</label>
<Input
type="text"
placeholder={t("mcp.form.docsPlaceholder")}
value={formDocs}
onChange={(e) => setFormDocs(e.target.value)}
/>
</div>
</>
)}
{/* 配置输入框 */}
<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")}
</label>
{(isEditing || selectedPreset === -1) && (
<button
type="button"
onClick={() => setIsWizardOpen(true)}
className="text-sm text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300 transition-colors"
>
{t("mcp.form.useWizard")}
</button>
)}
</div>
<JsonEditor
value={formConfig}
onChange={handleConfigChange}
placeholder={
useToml
? t("mcp.form.tomlPlaceholder")
: t("mcp.form.jsonPlaceholder")
}
darkMode={isDarkMode}
rows={12}
showValidation={!useToml}
language={useToml ? "javascript" : "json"}
height="300px"
/>
{configError && (
<div className="flex items-center gap-2 mt-2 text-red-500 dark:text-red-400 text-sm">
<AlertCircle size={16} />
<span>{configError}</span>
</div>
)}
</div>
<div className="flex justify-end pt-6">
<Button <Button
type="button" type="button"
onClick={handleSubmit} onClick={handleSubmit}
@@ -657,6 +427,245 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
? t("common.save") ? t("common.save")
: t("common.add")} : t("common.add")}
</Button> </Button>
}
>
<div className="glass rounded-xl p-6 border border-white/10 space-y-6">
{/* 预设选择(仅新增时展示) */}
{!isEditing && (
<div>
<label className="block text-sm font-medium text-foreground mb-3">
{t("mcp.presets.title")}
</label>
<div className="flex flex-wrap gap-2">
<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
? "bg-emerald-500 text-white dark:bg-emerald-600"
: "bg-accent text-muted-foreground hover:bg-accent/80"
}`}
>
{t("presetSelector.custom")}
</button>
{mcpPresets.map((preset, idx) => {
const descriptionKey = `mcp.presets.${preset.id}.description`;
return (
<button
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
? "bg-emerald-500 text-white dark:bg-emerald-600"
: "bg-accent text-muted-foreground hover:bg-accent/80"
}`}
title={t(descriptionKey)}
>
{preset.id}
</button>
);
})}
</div>
</div>
)}
{/* ID (标题) */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-foreground">
{t("mcp.form.title")} <span className="text-red-500">*</span>
</label>
{!isEditing && idError && (
<span className="text-xs text-red-500 dark:text-red-400">
{idError}
</span>
)}
</div>
<Input
type="text"
placeholder={t("mcp.form.titlePlaceholder")}
value={formId}
onChange={(e) => handleIdChange(e.target.value)}
disabled={isEditing}
/>
</div>
{/* Name */}
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.name")}
</label>
<Input
type="text"
placeholder={t("mcp.form.namePlaceholder")}
value={formName}
onChange={(e) => setFormName(e.target.value)}
/>
</div>
{/* 启用到哪些应用 */}
<div>
<label className="block text-sm font-medium text-foreground mb-3">
{t("mcp.form.enabledApps")}
</label>
<div className="flex flex-wrap gap-4">
<div className="flex items-center gap-2">
<Checkbox
id="enable-claude"
checked={enabledApps.claude}
onCheckedChange={(checked: boolean) =>
setEnabledApps({ ...enabledApps, claude: checked })
}
/>
<label
htmlFor="enable-claude"
className="text-sm text-foreground cursor-pointer select-none"
>
{t("mcp.unifiedPanel.apps.claude")}
</label>
</div>
<div className="flex items-center gap-2">
<Checkbox
id="enable-codex"
checked={enabledApps.codex}
onCheckedChange={(checked: boolean) =>
setEnabledApps({ ...enabledApps, codex: checked })
}
/>
<label
htmlFor="enable-codex"
className="text-sm text-foreground cursor-pointer select-none"
>
{t("mcp.unifiedPanel.apps.codex")}
</label>
</div>
<div className="flex items-center gap-2">
<Checkbox
id="enable-gemini"
checked={enabledApps.gemini}
onCheckedChange={(checked: boolean) =>
setEnabledApps({ ...enabledApps, gemini: checked })
}
/>
<label
htmlFor="enable-gemini"
className="text-sm text-foreground cursor-pointer select-none"
>
{t("mcp.unifiedPanel.apps.gemini")}
</label>
</div>
</div>
</div>
{/* 可折叠的附加信息按钮 */}
<div>
<button
type="button"
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} />
)}
{t("mcp.form.additionalInfo")}
</button>
</div>
{/* 附加信息区域(可折叠) */}
{showMetadata && (
<>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.description")}
</label>
<Input
type="text"
placeholder={t("mcp.form.descriptionPlaceholder")}
value={formDescription}
onChange={(e) => setFormDescription(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.tags")}
</label>
<Input
type="text"
placeholder={t("mcp.form.tagsPlaceholder")}
value={formTags}
onChange={(e) => setFormTags(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.homepage")}
</label>
<Input
type="text"
placeholder={t("mcp.form.homepagePlaceholder")}
value={formHomepage}
onChange={(e) => setFormHomepage(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
{t("mcp.form.docs")}
</label>
<Input
type="text"
placeholder={t("mcp.form.docsPlaceholder")}
value={formDocs}
onChange={(e) => setFormDocs(e.target.value)}
/>
</div>
</>
)}
{/* 配置输入框 */}
<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")}
</label>
{(isEditing || selectedPreset === -1) && (
<button
type="button"
onClick={() => setIsWizardOpen(true)}
className="text-sm text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300 transition-colors"
>
{t("mcp.form.useWizard")}
</button>
)}
</div>
<JsonEditor
value={formConfig}
onChange={handleConfigChange}
placeholder={
useToml
? t("mcp.form.tomlPlaceholder")
: t("mcp.form.jsonPlaceholder")
}
darkMode={isDarkMode}
rows={12}
showValidation={!useToml}
language={useToml ? "javascript" : "json"}
height="300px"
/>
{configError && (
<div className="flex items-center gap-2 mt-2 text-red-500 dark:text-red-400 text-sm">
<AlertCircle size={16} />
<span>{configError}</span>
</div>
)}
</div>
</div> </div>
</FullScreenPanel> </FullScreenPanel>

View File

@@ -115,9 +115,9 @@ const UnifiedMcpPanel = React.forwardRef<
}; };
return ( 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 */} {/* 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"> <div className="text-sm text-muted-foreground">
{t("mcp.serverCount", { count: serverEntries.length })} ·{" "} {t("mcp.serverCount", { count: serverEntries.length })} ·{" "}
{t("mcp.unifiedPanel.apps.claude")}: {enabledCounts.claude} ·{" "} {t("mcp.unifiedPanel.apps.claude")}: {enabledCounts.claude} ·{" "}
@@ -127,7 +127,7 @@ const UnifiedMcpPanel = React.forwardRef<
</div> </div>
{/* Content - Scrollable */} {/* 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 ? ( {isLoading ? (
<div className="text-center py-12 text-gray-500 dark:text-gray-400"> <div className="text-center py-12 text-gray-500 dark:text-gray-400">
{t("mcp.loading")} {t("mcp.loading")}
@@ -233,112 +233,110 @@ const UnifiedMcpListItem: React.FC<UnifiedMcpListItemProps> = ({
}; };
return ( 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="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 items-center gap-4"> {/* 左侧:服务器信息 */}
{/* 左侧:服务器信息 */} <div className="flex-1 min-w-0">
<div className="flex-1 min-w-0"> <div className="flex items-center gap-2 mb-1">
<div className="flex items-center gap-2 mb-1"> <h3 className="font-medium text-gray-900 dark:text-gray-100">
<h3 className="font-medium text-gray-900 dark:text-gray-100"> {name}
{name} </h3>
</h3> {docsUrl && (
{docsUrl && ( <Button
<Button type="button"
type="button" variant="ghost"
variant="ghost" size="sm"
size="sm" onClick={openDocs}
onClick={openDocs} title={t("mcp.presets.docs")}
title={t("mcp.presets.docs")} >
> {t("mcp.presets.docs")}
{t("mcp.presets.docs")} </Button>
</Button>
)}
</div>
{description && (
<p className="text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
{description}
</p>
)}
{!description && tags && tags.length > 0 && (
<p className="text-xs text-gray-400 dark:text-gray-500 truncate">
{tags.join(", ")}
</p>
)} )}
</div> </div>
{description && (
<p className="text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
{description}
</p>
)}
{!description && tags && tags.length > 0 && (
<p className="text-xs text-gray-400 dark:text-gray-500 truncate">
{tags.join(", ")}
</p>
)}
</div>
{/* 中间:应用开关 */} {/* 中间:应用开关 */}
<div className="flex flex-col gap-2 flex-shrink-0 min-w-[120px]"> <div className="flex flex-col gap-2 flex-shrink-0 min-w-[120px]">
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<label <label
htmlFor={`${id}-claude`} htmlFor={`${id}-claude`}
className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer" className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer"
> >
{t("mcp.unifiedPanel.apps.claude")} {t("mcp.unifiedPanel.apps.claude")}
</label> </label>
<Switch <Switch
id={`${id}-claude`} id={`${id}-claude`}
checked={server.apps.claude} checked={server.apps.claude}
onCheckedChange={(checked: boolean) => onCheckedChange={(checked: boolean) =>
onToggleApp(id, "claude", checked) onToggleApp(id, "claude", checked)
} }
/> />
</div>
<div className="flex items-center justify-between gap-3">
<label
htmlFor={`${id}-codex`}
className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer"
>
{t("mcp.unifiedPanel.apps.codex")}
</label>
<Switch
id={`${id}-codex`}
checked={server.apps.codex}
onCheckedChange={(checked: boolean) =>
onToggleApp(id, "codex", checked)
}
/>
</div>
<div className="flex items-center justify-between gap-3">
<label
htmlFor={`${id}-gemini`}
className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer"
>
{t("mcp.unifiedPanel.apps.gemini")}
</label>
<Switch
id={`${id}-gemini`}
checked={server.apps.gemini}
onCheckedChange={(checked: boolean) =>
onToggleApp(id, "gemini", checked)
}
/>
</div>
</div> </div>
{/* 右侧:操作按钮 */} <div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2 flex-shrink-0"> <label
<Button htmlFor={`${id}-codex`}
type="button" className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer"
variant="ghost"
size="icon"
onClick={() => onEdit(id)}
title={t("common.edit")}
> >
<Edit3 size={16} /> {t("mcp.unifiedPanel.apps.codex")}
</Button> </label>
<Switch
<Button id={`${id}-codex`}
type="button" checked={server.apps.codex}
variant="ghost" onCheckedChange={(checked: boolean) =>
size="icon" onToggleApp(id, "codex", checked)
onClick={() => onDelete(id)} }
className="hover:text-red-500 hover:bg-red-100 dark:hover:text-red-400 dark:hover:bg-red-500/10" />
title={t("common.delete")}
>
<Trash2 size={16} />
</Button>
</div> </div>
<div className="flex items-center justify-between gap-3">
<label
htmlFor={`${id}-gemini`}
className="text-sm text-gray-700 dark:text-gray-300 cursor-pointer"
>
{t("mcp.unifiedPanel.apps.gemini")}
</label>
<Switch
id={`${id}-gemini`}
checked={server.apps.gemini}
onCheckedChange={(checked: boolean) =>
onToggleApp(id, "gemini", checked)
}
/>
</div>
</div>
{/* 右侧:操作按钮 */}
<div className="flex items-center gap-2 flex-shrink-0">
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => onEdit(id)}
title={t("common.edit")}
>
<Edit3 size={16} />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => onDelete(id)}
className="hover:text-red-500 hover:bg-red-100 dark:hover:text-red-400 dark:hover:bg-red-500/10"
title={t("common.delete")}
>
<Trash2 size={16} />
</Button>
</div> </div>
</div> </div>
); );

View File

@@ -91,47 +91,11 @@ const PromptFormPanel: React.FC<PromptFormPanelProps> = ({
: t("prompts.addTitle", { appName }); : t("prompts.addTitle", { appName });
return ( return (
<FullScreenPanel isOpen={true} title={title} onClose={onClose}> <FullScreenPanel
<div> isOpen={true}
<Label htmlFor="name" className="text-foreground"> title={title}
{t("prompts.name")} onClose={onClose}
</Label> footer={
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t("prompts.namePlaceholder")}
className="mt-2"
/>
</div>
<div>
<Label htmlFor="description" className="text-foreground">
{t("prompts.description")}
</Label>
<Input
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder={t("prompts.descriptionPlaceholder")}
className="mt-2"
/>
</div>
<div>
<Label htmlFor="content" className="block mb-2 text-foreground">
{t("prompts.content")}
</Label>
<MarkdownEditor
value={content}
onChange={setContent}
placeholder={t("prompts.contentPlaceholder", { filename })}
darkMode={isDarkMode}
minHeight="500px"
/>
</div>
<div className="flex justify-end pt-6">
<Button <Button
type="button" type="button"
onClick={handleSave} onClick={handleSave}
@@ -140,6 +104,47 @@ const PromptFormPanel: React.FC<PromptFormPanelProps> = ({
> >
{saving ? t("common.saving") : t("common.save")} {saving ? t("common.saving") : t("common.save")}
</Button> </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")}
</Label>
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t("prompts.namePlaceholder")}
className="mt-2"
/>
</div>
<div>
<Label htmlFor="description" className="text-foreground">
{t("prompts.description")}
</Label>
<Input
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder={t("prompts.descriptionPlaceholder")}
className="mt-2"
/>
</div>
<div>
<Label htmlFor="content" className="block mb-2 text-foreground">
{t("prompts.content")}
</Label>
<MarkdownEditor
value={content}
onChange={setContent}
placeholder={t("prompts.contentPlaceholder", { filename })}
darkMode={isDarkMode}
minHeight="500px"
/>
</div>
</div> </div>
</FullScreenPanel> </FullScreenPanel>
); );

View File

@@ -25,7 +25,7 @@ const PromptListItem: React.FC<PromptListItemProps> = ({
const enabled = prompt.enabled === true; const enabled = prompt.enabled === true;
return ( 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"> <div className="flex items-center gap-4 h-full">
{/* Toggle 开关 */} {/* Toggle 开关 */}
<div className="flex-shrink-0"> <div className="flex-shrink-0">

View File

@@ -80,18 +80,11 @@ export function BasicFormFields({ form }: BasicFormFieldsProps) {
<ArrowLeft className="h-5 w-5" /> <ArrowLeft className="h-5 w-5" />
</Button> </Button>
</DialogClose> </DialogClose>
<div className="space-y-1"> <p className="text-lg font-semibold leading-tight">
<p className="text-lg font-semibold leading-tight"> {t("providerIcon.selectIcon", {
{t("providerIcon.selectIcon", { defaultValue: "选择图标",
defaultValue: "选择图标", })}
})} </p>
</p>
<p className="text-sm text-muted-foreground">
{t("providerIcon.selectDescription", {
defaultValue: "为供应商选择一个图标",
})}
</p>
</div>
</div> </div>
<div className="flex-1 overflow-y-auto px-6 py-6"> <div className="flex-1 overflow-y-auto px-6 py-6">
<div className="space-y-6 max-w-5xl mx-auto w-full"> <div className="space-y-6 max-w-5xl mx-auto w-full">

View File

@@ -655,7 +655,7 @@ export function ProviderForm({
<form <form
id="provider-form" id="provider-form"
onSubmit={form.handleSubmit(handleSubmit)} onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-6" className="space-y-6 glass rounded-xl p-6 border border-white/10"
> >
{/* 预设供应商选择(仅新增模式显示) */} {/* 预设供应商选择(仅新增模式显示) */}
{!initialData && ( {!initialData && (

View File

@@ -168,7 +168,7 @@ export function SettingsPage({
const isBusy = useMemo(() => isLoading && !settings, [isLoading, settings]); const isBusy = useMemo(() => isLoading && !settings, [isLoading, settings]);
return ( 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 ? ( {isBusy ? (
<div className="flex flex-1 items-center justify-center"> <div className="flex flex-1 items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />

View File

@@ -93,7 +93,7 @@ export function RepoManagerPanel({
onClose={onClose} 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 className="text-base font-semibold text-foreground">
</h3> </h3>
@@ -156,7 +156,7 @@ export function RepoManagerPanel({
{t("skills.repo.list")} {t("skills.repo.list")}
</h3> </h3>
{repos.length === 0 ? ( {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"> <p className="text-sm text-muted-foreground">
{t("skills.repo.empty")} {t("skills.repo.empty")}
</p> </p>
@@ -166,7 +166,7 @@ export function RepoManagerPanel({
{repos.map((repo) => ( {repos.map((repo) => (
<div <div
key={`${repo.owner}/${repo.name}`} 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>
<div className="text-sm font-medium text-foreground"> <div className="text-sm font-medium text-foreground">

View File

@@ -57,7 +57,7 @@ export function SkillCard({ skill, onInstall, onUninstall }: SkillCardProps) {
skill.directory.trim().toLowerCase() !== skill.name.trim().toLowerCase(); skill.directory.trim().toLowerCase() !== skill.name.trim().toLowerCase();
return ( 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" /> <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"> <CardHeader className="pb-3">
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">

View File

@@ -167,39 +167,41 @@ export const SkillsPage = forwardRef<SkillsPageHandle, SkillsPageProps>(
{/* 顶部操作栏(固定区域)已移除,由 App.tsx 接管 */} {/* 顶部操作栏(固定区域)已移除,由 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">
{loading ? ( <div className="mx-auto max-w-[60rem] px-6 py-4">
<div className="flex items-center justify-center h-64"> {loading ? (
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" /> <div className="flex items-center justify-center h-64">
</div> <RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" />
) : skills.length === 0 ? ( </div>
<div className="flex flex-col items-center justify-center h-64 text-center"> ) : skills.length === 0 ? (
<p className="text-lg font-medium text-gray-900 dark:text-gray-100"> <div className="flex flex-col items-center justify-center h-64 text-center">
{t("skills.empty")} <p className="text-lg font-medium text-gray-900 dark:text-gray-100">
</p> {t("skills.empty")}
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400"> </p>
{t("skills.emptyDescription")} <p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
</p> {t("skills.emptyDescription")}
<Button </p>
variant="link" <Button
onClick={() => setRepoManagerOpen(true)} variant="link"
className="mt-3 text-sm font-normal" onClick={() => setRepoManagerOpen(true)}
> className="mt-3 text-sm font-normal"
{t("skills.addRepo")} >
</Button> {t("skills.addRepo")}
</div> </Button>
) : ( </div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> ) : (
{skills.map((skill) => ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<SkillCard {skills.map((skill) => (
key={skill.key} <SkillCard
skill={skill} key={skill.key}
onInstall={handleInstall} skill={skill}
onUninstall={handleUninstall} onInstall={handleInstall}
/> onUninstall={handleUninstall}
))} />
</div> ))}
)} </div>
)}
</div>
</div> </div>
{/* 仓库管理面板 */} {/* 仓库管理面板 */}