fix(web): OmniTerminal Escape 關閉 + 響應式底部抽屜
Phase 19.R - 修復 UX 問題: - 新增 Escape 鍵關閉 Terminal (之前僅有 CMD+J) - Mobile: 全螢幕改為 70vh 底部抽屜 - 新增半透明 backdrop,點擊可關閉 - 響應式: Mobile/Tablet/Desktop 三級適配 修復問題: Terminal 開啟後無法關閉 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import {
|
||||
useTerminalStore,
|
||||
useIsConnected,
|
||||
@@ -34,6 +35,7 @@ import { Z_INDEX } from '@/lib/constants/z-index'
|
||||
import { SHORTCUTS, matchShortcut } from '@/lib/constants/shortcuts'
|
||||
|
||||
export const OmniTerminal = () => {
|
||||
const t = useTranslations('omniTerminal')
|
||||
// Use optimized selectors for better re-render performance
|
||||
const isOpen = useIsTerminalOpen()
|
||||
const messages = useTerminalMessages()
|
||||
@@ -44,16 +46,24 @@ export const OmniTerminal = () => {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// CMD+J 快捷鍵 (Phase 19 - 避免 CMD+K 瀏覽器衝突)
|
||||
// 快捷鍵處理 (Phase 19 - 完整鍵盤支援)
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// CMD+J: Toggle Terminal
|
||||
// CMD+J: Toggle Terminal (全局)
|
||||
if (matchShortcut(e, SHORTCUTS.TOGGLE_TERMINAL)) {
|
||||
e.preventDefault()
|
||||
toggleTerminal()
|
||||
return
|
||||
}
|
||||
// '/': Quick open (非輸入框時)
|
||||
|
||||
// Escape: 關閉 Terminal (當 Terminal 開啟時)
|
||||
if (e.key === 'Escape' && isOpen) {
|
||||
e.preventDefault()
|
||||
toggleTerminal()
|
||||
return
|
||||
}
|
||||
|
||||
// '/': Quick open (非輸入框時,Terminal 關閉時)
|
||||
if (e.key === '/' && !isOpen && document.activeElement?.tagName !== 'INPUT') {
|
||||
e.preventDefault()
|
||||
openTerminal()
|
||||
@@ -162,26 +172,30 @@ export const OmniTerminal = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 sm:inset-auto sm:bottom-0 sm:left-0 sm:w-full flex justify-center sm:pb-4 sm:px-4 pointer-events-none"
|
||||
style={{ zIndex: Z_INDEX.OMNI_TERMINAL }}
|
||||
>
|
||||
{/* Responsive container:
|
||||
- Mobile: Full screen
|
||||
- Tablet: 90% width
|
||||
- Desktop: max-w-4xl centered */}
|
||||
<>
|
||||
{/* Backdrop overlay for mobile - 點擊可關閉 */}
|
||||
<div
|
||||
className="w-full h-full sm:h-auto sm:w-[90%] lg:w-full lg:max-w-4xl bg-white/90 sm:bg-white/70 backdrop-blur-[20px] sm:border border-gray-200 sm:rounded-lg shadow-xl overflow-hidden pointer-events-auto flex flex-col transition-all"
|
||||
style={{ maxHeight: '100vh', minHeight: '100vh' }}
|
||||
role="dialog"
|
||||
aria-label="Omni-Terminal"
|
||||
className="fixed inset-0 bg-black/20 backdrop-blur-sm sm:hidden"
|
||||
style={{ zIndex: Z_INDEX.OMNI_TERMINAL - 1 }}
|
||||
onClick={toggleTerminal}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="fixed inset-x-0 bottom-0 flex justify-center px-2 pb-2 sm:px-4 sm:pb-4"
|
||||
style={{ zIndex: Z_INDEX.OMNI_TERMINAL }}
|
||||
>
|
||||
{/* Responsive height on tablet/desktop */}
|
||||
<style jsx>{`
|
||||
@media (min-width: 640px) {
|
||||
div { max-height: 60vh !important; min-height: 30vh !important; }
|
||||
}
|
||||
`}</style>
|
||||
{/* Responsive container:
|
||||
- Mobile: 底部彈出 (不再全螢幕!)
|
||||
- Tablet: 90% width
|
||||
- Desktop: max-w-4xl centered */}
|
||||
<div
|
||||
className="w-full sm:w-[90%] lg:max-w-4xl bg-white/95 backdrop-blur-[20px] border border-gray-200 rounded-xl shadow-2xl overflow-hidden flex flex-col transition-all"
|
||||
style={{ maxHeight: '70vh', minHeight: '200px' }}
|
||||
role="dialog"
|
||||
aria-label="Omni-Terminal"
|
||||
aria-modal="true"
|
||||
>
|
||||
|
||||
{/* Terminal Header */}
|
||||
<div className="flex justify-between items-center px-3 sm:px-4 py-2 border-b border-gray-200 bg-white/40">
|
||||
@@ -233,15 +247,16 @@ export const OmniTerminal = () => {
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
placeholder="輸入指令..."
|
||||
placeholder={t('inputPlaceholder')}
|
||||
className="flex-1 bg-transparent border-none outline-none font-['VT323'] text-xl sm:text-2xl text-nothing-black placeholder-gray-400"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
aria-label="Terminal input"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user