feat: add mobile shell and unified search
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s

This commit is contained in:
Yun Chan 2026-09-28 23:23:36 +09:00
parent f505a03d34
commit 5ca3709b32
98 changed files with 5168 additions and 2093 deletions

View file

@ -12,8 +12,9 @@ export interface SearchInputProps {
value: string
onChange: (value: string) => void
placeholder: string
/** 포커스 단축키. '/' 만 지원. 빈 문자열이면 끈다 */
/** 포커스 단축키. '/' 또는 'Ctrl K'. 빈 문자열이면 끈다 */
shortcut?: string
autoFocus?: boolean
}
function isTypingTarget(target: EventTarget | null): boolean {
@ -21,17 +22,34 @@ function isTypingTarget(target: EventTarget | null): boolean {
return target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)
}
export function SearchInput({ value, onChange, placeholder, shortcut = '/' }: SearchInputProps): React.ReactElement {
export function SearchInput({
value,
onChange,
placeholder,
shortcut = '/',
autoFocus = false,
}: SearchInputProps): React.ReactElement {
const { t } = useI18n()
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (shortcut !== '/') return undefined
const onKeyDown = (e: KeyboardEvent): void => {
if (e.key !== '/' || e.ctrlKey || e.metaKey || e.altKey || isTypingTarget(e.target)) return
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])
@ -39,6 +57,7 @@ export function SearchInput({ value, onChange, placeholder, shortcut = '/' }: Se
return (
<TextField
inputRef={inputRef}
autoFocus={autoFocus}
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}