// src/renderer/components/shared/SearchInput.tsx // 공유: 화면 안 검색창. "/" 를 누르면 여기로 포커스가 온다(입력 중이 아닐 때) — 표시만 하고 동작하지 않던 // 키캡을 실제 단축키로 만들었다(2026-09-28). Esc 는 검색어를 지우고, 비어 있으면 포커스를 놓는다. import React, { useEffect, useRef } from 'react' import { TextField, InputAdornment, IconButton, Box } from '@mui/material' import { Search, X } from 'lucide-react' import { d3roPalette, d3roFontMono, d3roTypo, d3roRadius } from '@d3ro/ui/theme' import { useI18n } from '@d3ro/i18n' export interface SearchInputProps { value: string onChange: (value: string) => void placeholder: string /** 포커스 단축키. '/' 또는 'Ctrl K'. 빈 문자열이면 끈다 */ shortcut?: string autoFocus?: boolean } function isTypingTarget(target: EventTarget | null): boolean { if (!(target instanceof HTMLElement)) return false return target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName) } export function SearchInput({ value, onChange, placeholder, shortcut = '/', autoFocus = false, }: SearchInputProps): React.ReactElement { const { t } = useI18n() const inputRef = useRef(null) useEffect(() => { const onKeyDown = (e: KeyboardEvent): void => { const slash = shortcut === '/' && e.key === '/' && !e.ctrlKey && !e.metaKey && !e.altKey && !isTypingTarget(e.target) const commandK = shortcut === 'Ctrl K' && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.key.toLocaleLowerCase() === 'k' if (!slash && !commandK) return e.preventDefault() inputRef.current?.focus() } if (shortcut !== '/' && shortcut !== 'Ctrl K') return undefined window.addEventListener('keydown', onKeyDown) return () => window.removeEventListener('keydown', onKeyDown) }, [shortcut]) return ( onChange(e.target.value)} onKeyDown={(e) => { if (e.key !== 'Escape') return if (value) onChange('') else inputRef.current?.blur() }} fullWidth sx={{ mb: 2, '& .MuiOutlinedInput-root': { bgcolor: d3roPalette.bg.input, borderRadius: d3roRadius.button, '& fieldset': { borderColor: d3roPalette.border.default }, '&:hover fieldset': { borderColor: d3roPalette.border.strong }, '&.Mui-focused fieldset': { borderColor: d3roPalette.accent.main, borderWidth: 1 }, }, '& .MuiInputBase-input': { fontSize: d3roTypo.compact.size, color: d3roPalette.text.primary, py: 1, '&::placeholder': { color: d3roPalette.text.tertiary, opacity: 1 }, }, }} slotProps={{ htmlInput: { 'aria-label': placeholder, type: 'search' }, input: { startAdornment: ( ), endAdornment: value ? ( onChange('')} sx={{ p: 0.5 }}> ) : shortcut ? ( {shortcut} ) : undefined, }, }} /> ) }