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 */}
@@ -282,7 +282,7 @@ export default function ConnectionPanel() {
{/* 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; +}