Skip to content

Commit c6a21e1

Browse files
committed
fix(search): 슬래시 단축키 토글 중복 수정
모바일과 데스크톱 검색 버튼이 각각 전역 keydown listener를 등록해 / 입력 시 검색창이 즉시 닫히던 문제를 수정합니다. 단축키 처리를 AppShell 단일 listener로 이동하고 검색 입력 aria-label과 회귀 테스트를 추가합니다.
1 parent 0e5bb5f commit c6a21e1

3 files changed

Lines changed: 44 additions & 26 deletions

File tree

src/features/search/ui/components/CommandPalette/CommandPalette.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const CommandPalette = () => {
2323
<div className={styles.searchWrapper}>
2424
<KBarSearch
2525
className={styles.search}
26+
aria-label="검색어, 태그, 글 제목 입력"
2627
defaultPlaceholder="검색어, 태그, 글 제목을 입력하세요"
2728
/>
2829
<button

src/shared/layout/AppShell/AppShell.tsx

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -156,32 +156,6 @@ function resolveSection(pathname: string, posts: FeedData[]): AppSection {
156156
function SearchButton() {
157157
const { query } = useKBar();
158158

159-
useEffect(() => {
160-
const handleKeyDown = (event: KeyboardEvent) => {
161-
if (event.key !== '/' || event.metaKey || event.ctrlKey || event.altKey) {
162-
return;
163-
}
164-
165-
const activeElement = document.activeElement as HTMLElement | null;
166-
const tagName = activeElement?.tagName;
167-
const isEditable =
168-
activeElement?.isContentEditable ||
169-
tagName === 'INPUT' ||
170-
tagName === 'TEXTAREA' ||
171-
tagName === 'SELECT';
172-
173-
if (isEditable) {
174-
return;
175-
}
176-
177-
event.preventDefault();
178-
query.toggle();
179-
};
180-
181-
window.addEventListener('keydown', handleKeyDown);
182-
return () => window.removeEventListener('keydown', handleKeyDown);
183-
}, [query]);
184-
185159
return (
186160
<button
187161
type="button"
@@ -212,6 +186,7 @@ function SearchButton() {
212186

213187
export default function AppShell({ children, posts }: AppShellProps) {
214188
const pathname = usePathname();
189+
const { query } = useKBar();
215190
const [mobileNavOpen, setMobileNavOpen] = useState(false);
216191

217192
const activeSection = useMemo(
@@ -223,6 +198,32 @@ export default function AppShell({ children, posts }: AppShellProps) {
223198
setMobileNavOpen(false);
224199
}, [pathname]);
225200

201+
useEffect(() => {
202+
const handleKeyDown = (event: KeyboardEvent) => {
203+
if (event.key !== '/' || event.metaKey || event.ctrlKey || event.altKey) {
204+
return;
205+
}
206+
207+
const activeElement = document.activeElement as HTMLElement | null;
208+
const tagName = activeElement?.tagName;
209+
const isEditable =
210+
activeElement?.isContentEditable ||
211+
tagName === 'INPUT' ||
212+
tagName === 'TEXTAREA' ||
213+
tagName === 'SELECT';
214+
215+
if (isEditable) {
216+
return;
217+
}
218+
219+
event.preventDefault();
220+
query.toggle();
221+
};
222+
223+
window.addEventListener('keydown', handleKeyDown);
224+
return () => window.removeEventListener('keydown', handleKeyDown);
225+
}, [query]);
226+
226227
return (
227228
<div className="min-h-screen bg-[var(--color-grey-50)] text-[var(--color-text-primary)] md:flex md:h-screen md:overflow-hidden">
228229
<ThemeTransitionWash />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
test.describe('Search shortcut', () => {
4+
test('/ 단축키로 검색창이 열린 상태를 유지해요', async ({ page }) => {
5+
await page.goto('/');
6+
7+
await page.keyboard.press('/');
8+
9+
const searchInput = page.getByRole('combobox', {
10+
name: /|| /,
11+
});
12+
13+
await expect(searchInput).toBeVisible();
14+
await expect(searchInput).toBeFocused();
15+
});
16+
});

0 commit comments

Comments
 (0)