diff --git a/src/components/ConnectionPanel.tsx b/src/components/ConnectionPanel.tsx
index 4c07b2e..1c469dc 100644
--- a/src/components/ConnectionPanel.tsx
+++ b/src/components/ConnectionPanel.tsx
@@ -274,7 +274,7 @@ export default function ConnectionPanel() {
<>
{/* Backdrop */}
{/* Header */}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index f343006..701471a 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -279,8 +279,9 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession }:
className={`
absolute top-0 right-0 bottom-0 w-1 cursor-col-resize
hover:bg-accent-blue/50 active:bg-accent-blue/70
- transition-colors duration-150
- ${isResizing ? 'bg-accent-blue/70' : ''}
+ hover:w-1.5 hover:shadow-[0_0_6px_rgba(59,130,246,0.4)]
+ transition-all duration-150
+ ${isResizing ? 'bg-accent-blue/70 w-1.5 shadow-[0_0_6px_rgba(59,130,246,0.5)]' : ''}
`}
/>
diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx
index a445147..19be614 100644
--- a/src/components/StatusBar.tsx
+++ b/src/components/StatusBar.tsx
@@ -169,15 +169,17 @@ export default function StatusBar() {
type="button"
onClick={togglePanel}
className={`flex items-center gap-1.5 rounded px-1 py-0.5 -ml-1
- hover:bg-dark-700/60 transition-colors cursor-pointer
+ hover:bg-dark-700/60 transition-colors cursor-pointer group
${statusCfg.colorClass} ${statusCfg.animateClass}`}
title="点击打开连接管理面板"
>
- {statusCfg.icon}
-
{statusCfg.label}
+ {statusCfg.icon && (
+
{statusCfg.icon}
+ )}
+
{statusCfg.label}
{hostLabel && (
<>
-
·
+
·
{hostLabel}
>
)}
@@ -186,7 +188,7 @@ export default function StatusBar() {
{totalConnections}
)}
-
+
{/* Center: Transfer stats */}
@@ -202,11 +204,11 @@ export default function StatusBar() {
{/* Right: Clock + version */}
-
+
{formatTime(currentTime)}
-
OpsXShell v{version}
+
OpsXShell v{version}
)
diff --git a/src/components/common/CommandPalette.tsx b/src/components/common/CommandPalette.tsx
index cdb175b..874ee44 100644
--- a/src/components/common/CommandPalette.tsx
+++ b/src/components/common/CommandPalette.tsx
@@ -305,11 +305,11 @@ export function CommandPalette({
onKeyDown={handleKeyDown}
>
{/* Backdrop */}
-
+
{/* Palette container */}
{/* 标题 */}
-
{title}
+
{title}
{/* 描述 */}
-
{description}
+
{description}
{/* 操作按钮 */}
{action && (
diff --git a/src/components/sidebar/SearchBox.tsx b/src/components/sidebar/SearchBox.tsx
index 0c8dcb7..2fae7ab 100644
--- a/src/components/sidebar/SearchBox.tsx
+++ b/src/components/sidebar/SearchBox.tsx
@@ -19,7 +19,7 @@ export function SearchBox({ value, onChange, placeholder = '搜索...' }: Search
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
- className="w-full pl-9 pr-3 py-2 bg-dark-900 border border-dark-700 rounded text-sm focus:outline-none focus:border-accent-blue"
+ className="w-full pl-9 pr-3 py-2 bg-dark-900 border border-dark-700 rounded text-sm focus:outline-none focus:border-accent-blue transition-colors duration-150"
/>
)
diff --git a/src/components/sidebar/SessionTree.tsx b/src/components/sidebar/SessionTree.tsx
index 54d6fc2..41d8cd6 100644
--- a/src/components/sidebar/SessionTree.tsx
+++ b/src/components/sidebar/SessionTree.tsx
@@ -91,7 +91,7 @@ export function SessionTree({
onContextMenu={(e) => onContextMenu(e, session)}
className={`
group flex items-center gap-2 px-4 py-2 text-sm cursor-pointer
- transition-colors pl-10
+ transition-colors duration-150 pl-10
${selectedSessionId === session.id
? 'bg-dark-700 border-l-2 border-accent-blue'
: 'hover:bg-dark-700'
diff --git a/src/components/snippets/SnippetPanel.tsx b/src/components/snippets/SnippetPanel.tsx
index 79bdd9c..d8a17a1 100644
--- a/src/components/snippets/SnippetPanel.tsx
+++ b/src/components/snippets/SnippetPanel.tsx
@@ -304,13 +304,13 @@ function SnippetItem({ snippet, copied, canInsert, onInsert, onCopy, onEdit, onD
const preview = snippet.content.split('\n')[0].slice(0, 60) + (snippet.content.length > 60 ? '…' : '')
return (
-
+
{/* 名称行 */}
{snippet.name}
-
+
{/* 插入 */}
- {/* Active tab bottom indicator */}
- {isActive && (
-
- )}
+ {/* Active tab bottom indicator — always rendered for width transition */}
+
)
}
diff --git a/src/index.css b/src/index.css
index b8a3e7b..c8d31af 100644
--- a/src/index.css
+++ b/src/index.css
@@ -136,3 +136,84 @@ body {
.animate-fade-in {
animation: fadeIn 0.2s ease-in-out;
}
+
+@keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.6);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.animate-scale-in {
+ animation: scaleIn 0.15s ease-out;
+}
+
+/* Slide animations */
+@keyframes slideInRight {
+ from { transform: translateX(100%); opacity: 0; }
+ to { transform: translateX(0); opacity: 1; }
+}
+
+@keyframes slideOutRight {
+ from { transform: translateX(0); opacity: 1; }
+ to { transform: translateX(100%); opacity: 0; }
+}
+
+@keyframes slideInUp {
+ from { transform: translateY(20px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+}
+
+@keyframes scaleIn {
+ from { transform: scale(0.95); opacity: 0; }
+ to { transform: scale(1); opacity: 1; }
+}
+
+@keyframes pulse-subtle {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.7; }
+}
+
+.animate-slide-in-right { animation: slideInRight 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
+.animate-slide-out-right { animation: slideOutRight 0.2s ease-in forwards; }
+.animate-slide-in-up { animation: slideInUp 0.25s cubic-bezier(0.16, 1, 0.3, 1); }
+.animate-scale-in { animation: scaleIn 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
+.animate-pulse-subtle { animation: pulse-subtle 2s ease-in-out infinite; }
+
+/* Focus ring */
+button:focus-visible,
+a:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible {
+ outline: 2px solid var(--color-accent-blue);
+ outline-offset: 2px;
+}
+
+button:focus:not(:focus-visible),
+a:focus:not(:focus-visible) {
+ outline: none;
+}
+
+/* Button press feedback */
+button:active:not(:disabled) {
+ transform: scale(0.97);
+}
+button {
+ transition: all 0.15s ease;
+}
+
+/* Skeleton shimmer */
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+}
+.animate-shimmer {
+ background: linear-gradient(90deg, var(--color-dark-800) 25%, var(--color-dark-700) 50%, var(--color-dark-800) 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s ease-in-out infinite;
+}