Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough글로벌 Command Palette(⌘K/Ctrl+K) 기반 전역 검색 기능을 추가하고, 이를 레이아웃 루트에 Provider로 통합했습니다. 기존 홈 검색 UI는 명령 팔레트 트리거 버튼으로 교체되었고 검색 표시/모바일 메뉴 로직은 별도 훅으로 분리되었습니다. Changes
Sequence DiagramsequenceDiagram
participant User
participant Header as SiteHeader
participant Provider as CommandPaletteProvider
participant Palette as CommandPalette (Cmdk)
participant Dialog as Radix Dialog
participant Router as Next.js Router
User->>Header: ⌘K 누르거나 검색 버튼 클릭
Header->>Provider: openPalette() 호출
Provider->>Dialog: open 상태 활성화
Dialog->>Palette: 명령 팔레트 렌더링 (입력 포커스)
User->>Palette: 쿼리 입력
Palette->>Palette: 필터링 (label/hint/searchableText)
Palette->>User: 필터된 결과 표시
User->>Palette: 항목 선택(엔터/클릭)
Palette->>Router: 선택된 item.href로 네비게이트
Palette->>Dialog: Dialog 닫기 (query 리셋)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (2)
💤 Files with no reviewable changes (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In @src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts:
- Around line 40-42: handleSearchClick is just a thin wrapper that only calls
openPalette; remove the unnecessary wrapper by either returning/exporting
openPalette directly from the hook or replacing the function body with a direct
assignment (e.g., const handleSearchClick = openPalette) and update all callers
to use openPalette (or the reassigned handleSearchClick) accordingly; adjust any
tests or usages that referenced handleSearchClick to the chosen exported symbol
to keep behavior unchanged.
In @src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx:
- Around line 89-98: The aria-label on the mobile menu button is static; update
the motion.button inside the SiteHeader component to set aria-label dynamically
based on isMobileMenuOpen (e.g., use "모바일 메뉴 닫기" when true and "모바일 메뉴 열기" when
false) and also add aria-expanded={isMobileMenuOpen} to reflect state; the
change should be applied to the existing motion.button that calls
toggleMobileMenu and renders {isMobileMenuOpen ? <X .../> : <Menu .../>}.
- Around line 10-17: Tests render SiteHeader without the CommandPaletteProvider
causing useSearchTransition (which calls useCommandPalette) to fail; wrap
SiteHeader in CommandPaletteProvider in SiteHeader.test.tsx around each render
(e.g., the render in the "로고가 렌더링된다" test and other tests) so the context is
available. Also update the mobile menu toggle button in SiteHeader (the element
using isMobileMenuOpen) to set its aria-label dynamically using isMobileMenuOpen
so it reads "모바일 메뉴 닫기" when open and "모바일 메뉴 열기" when closed instead of a fixed
string.
In @src/components/common/CommandPalette.tsx:
- Around line 62-65: Add a Dialog.Description to the CommandPalette Dialog to
improve screen-reader context: inside the Dialog.Content near the existing
Dialog.Title (which is sr-only), insert a Dialog.Description with a brief
descriptive string (e.g., a short Korean sentence like "검색어를 입력하여 사이트 내 항목을
찾습니다") and mark it sr-only if you want it hidden visually, so assistive tech
reads both title and description; ensure you import/use the same Dialog
namespace (Dialog.Description) so the Radix Dialog properly associates the
description with the dialog.
- Around line 81-103: The Command component is performing its own filtering in
addition to the custom filtering from useCommandPaletteInternal, causing
double-filtering; update the Command (root) element to disable built-in
filtering by adding the shouldFilter={false} prop so only filteredItems from
useCommandPaletteInternal are rendered (ensure you reference the Command.List
that maps filteredItems and keep Command.Item rendering unchanged).
- Around line 9-15: Command.Item currently passes only item.label as the value
prop, which prevents the generated searchableText from being used by cmdk's
filtering; update the value usage so it becomes value={item.searchableText ||
item.label} on Command.Item (or alternatively disable cmdk's built-in filtering
by adding shouldFilter={false} to the Command container) to ensure
searchableText is honored — look for Command.Item in CommandPalette.tsx and
adjust the value prop or add shouldFilter on the Command component.
In @src/hooks/useCommandPaletteInternal.ts:
- Around line 142-155: The handler inside useEffect uses the deprecated
navigator.platform to detect macOS; update the platform detection in the handler
(used by useEffect and togglePalette) to use feature detection: prefer
navigator.userAgentData?.platform for modern browsers and fall back to
navigator.platform or a userAgent sniff only if userAgentData is unavailable;
ensure the logic still derives the boolean isMac and uses it to compute meta
(e.metaKey vs e.ctrlKey) and keep existing prevention and togglePalette
behavior.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
package.jsonsrc/app/(layout)/(shell)/_components/home/HeroLeft.tsxsrc/app/(layout)/(shell)/_components/home/HeroTagList.tsxsrc/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.tssrc/app/(layout)/(shell)/_components/layout/SiteHeader.tsxsrc/app/(layout)/(shell)/_constants/home.tssrc/app/(layout)/layout.tsxsrc/components/common/CommandPalette.tsxsrc/hooks/useCommandPaletteInternal.ts
💤 Files with no reviewable changes (1)
- src/app/(layout)/(shell)/_components/home/HeroTagList.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts (1)
src/components/common/CommandPalette.tsx (1)
useCommandPalette(31-39)
src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx (2)
src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts (1)
useSearchTransition(7-52)src/components/ui/DarkModeWheel.tsx (1)
DarkModeWheel(9-75)
src/app/(layout)/(shell)/_components/home/HeroLeft.tsx (1)
src/components/common/CommandPalette.tsx (1)
useCommandPalette(31-39)
src/hooks/useCommandPaletteInternal.ts (2)
src/components/common/CommandPalette.tsx (1)
CommandItem(9-15)src/lib/posts/queries.ts (1)
getAllPosts(9-17)
src/components/common/CommandPalette.tsx (1)
src/hooks/useCommandPaletteInternal.ts (1)
useCommandPaletteInternal(75-169)
🪛 GitHub Actions: CI
src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx
[error] 17-17: Test failed: useCommandPalette must be used within CommandPaletteProvider.
src/components/common/CommandPalette.tsx
[error] 34-34: useCommandPalette must be used within CommandPaletteProvider.
🔇 Additional comments (14)
src/app/(layout)/(shell)/_constants/home.ts (1)
1-2: LGTM!상수 텍스트 업데이트입니다. 문제없습니다.
src/app/(layout)/layout.tsx (1)
5-12: LGTM!
CommandPaletteProvider가 레이아웃 최상위에 적절히 배치되었습니다. 하위 컴포넌트들이useCommandPalette훅을 사용할 수 있습니다.src/hooks/useCommandPaletteInternal.ts (2)
41-73: LGTM!
buildPostItems가 포스트 데이터를 검색 가능한 형태로 잘 변환합니다.searchableText에 title, summary, tags, category, series, slug를 모두 포함하여 다양한 검색이 가능합니다.
80-83: 정적 포스트 데이터에 대한 빈 의존성 배열
items의useMemo의존성 배열이 비어 있어 포스트가 런타임에 변경되어도 업데이트되지 않습니다.getAllPosts가 정적 데이터를 반환하므로 현재는 문제없지만, 이 의도가 맞는지 확인해 주세요.src/app/(layout)/(shell)/_components/home/HeroLeft.tsx (2)
11-12: LGTM!
useCommandPalette훅에서openPalette만 필요한 만큼만 destructure하여 사용하고 있습니다.
28-65: 검색 버튼 UI 구현이 잘 되었습니다버튼이
type="button"으로 명시되어 있고, 라이트/다크 모드에 대한 스타일링이 잘 적용되었습니다.⌘K힌트도 적절하게 표시됩니다.src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts (1)
14-29: 스크롤 이벤트 리스너 구현이 적절합니다홈 페이지에서만 스크롤 리스너를 등록하고, cleanup 함수로 잘 정리하고 있습니다. 다만, 홈에서 다른 페이지로 이동 시
pastThreshold상태가true로 남아있을 수 있습니다. 다시 홈으로 돌아왔을 때handleScroll()이 초기 호출되므로 실질적 문제는 없지만, 명시적으로 리셋하면 더 명확할 수 있습니다.src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx (2)
27-54: LGTM!
AnimatePresence를 사용한 검색 버튼의 진입/퇴장 애니메이션이 잘 구현되어 있습니다.showSearch상태에 따라 조건부 렌더링이 적절하게 동작합니다.
102-133: 모바일 메뉴 애니메이션 구현이 적절합니다
AnimatePresence와motion.div를 사용하여 모바일 메뉴의 열림/닫힘 애니메이션이 부드럽게 처리됩니다. 네비게이션 링크 클릭 시closeMobileMenu를 호출하여 메뉴가 자동으로 닫히는 것도 좋은 UX입니다.package.json (1)
20-23: 새 의존성 추가 적절함
@radix-ui/react-dialog와cmdk는 Command Palette 구현에 잘 맞는 조합입니다.cmdk가 내부적으로@radix-ui/react-dialog에 의존하고 있으므로, 버전 관리가 명확합니다. 두 라이브러리 모두 최신 버전을 사용하고 있어 문제없습니다.src/components/common/CommandPalette.tsx (4)
41-59: LGTM!Provider 구현이 깔끔합니다. 내부 상태(
open,query,filteredItems)는 Provider 내부에만 유지하고, 외부에는 필요한 액션(openPalette,closePalette,togglePalette)만 노출하는 좋은 캡슐화입니다.
106-110: LGTM!Dialog와 Provider 구조가 올바르게 구성되어 있습니다.
children이 Context Provider 내부에 렌더링되어 하위 컴포넌트들이useCommandPalette훅에 접근할 수 있습니다.
31-39: 원본 코드의 에러 처리는 올바르게 구현되어 있습니다.
useCommandPalette훅의 컨텍스트 확인 로직(31-39번 라인)은 정상입니다. 코드 검토 결과:
CommandPaletteProvider는src/app/(layout)/layout.tsx에서HeroLeft및useSearchTransition을 포함한 모든 자식 컴포넌트를 감싸고 있습니다.useCommandPalette를 사용하는 모든 컴포넌트가 provider 범위 내에 있으므로, 제시된 에러가 발생할 조건이 없습니다.CI 파이프라인 실패 메시지에 대한 구체적인 증거가 필요합니다. 현재 코드 구조상 정상 작동해야 하는 상황이므로, 실제 에러 로그나 재현 가능한 시나리오가 있는지 확인해 주세요.
Likely an incorrect or invalid review comment.
62-63: z-index 커스텀 값 확인 필요
z-60과z-70은 Tailwind CSS의 기본 유틸리티가 아닙니다. Tailwind의 기본 z-index 유틸리티는z-0,z-10,z-20,z-30,z-40,z-50까지만 제공됩니다. 이 값들이 프로젝트의tailwind.config에 커스텀으로 정의되어 있는지 확인해 주세요. 정의되지 않았다면 스타일이 제대로 적용되지 않을 수 있습니다.
| const handleSearchClick = () => { | ||
| openPalette(); | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
handleSearchClick은 openPalette의 단순 래퍼입니다
현재 handleSearchClick은 openPalette()만 호출합니다. 향후 analytics 등 추가 로직이 필요하다면 이 구조가 유용하지만, 그렇지 않다면 openPalette를 직접 반환하는 것도 고려해 볼 수 있습니다.
🤖 Prompt for AI Agents
In @src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts
around lines 40 - 42, handleSearchClick is just a thin wrapper that only calls
openPalette; remove the unnecessary wrapper by either returning/exporting
openPalette directly from the hook or replacing the function body with a direct
assignment (e.g., const handleSearchClick = openPalette) and update all callers
to use openPalette (or the reassigned handleSearchClick) accordingly; adjust any
tests or usages that referenced handleSearchClick to the chosen exported symbol
to keep behavior unchanged.
| export default function SiteHeader() { | ||
| const [open, setOpen] = useState(false); | ||
| const { | ||
| showSearch, | ||
| isMobileMenuOpen, | ||
| toggleMobileMenu, | ||
| closeMobileMenu, | ||
| handleSearchClick, | ||
| } = useSearchTransition(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if the file exists and read relevant sections
echo "=== Checking SiteHeader.tsx file ==="
if [ -f "src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx" ]; then
wc -l "src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx"
echo ""
echo "=== Lines 10-17 ==="
sed -n '10,17p' "src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx"
echo ""
echo "=== Lines 90-95 (around line 93) ==="
sed -n '90,95p' "src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx"
else
echo "File not found at src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx"
# Try to find it
fd "SiteHeader.tsx"
fiRepository: swallowedB/b-log
Length of output: 630
🏁 Script executed:
#!/bin/bash
# Find test files related to SiteHeader
echo "=== Finding test files ==="
fd -type f \( -name "*SiteHeader*" -o -name "*site-header*" \) | head -20
echo ""
echo "=== Finding all test files in the directory ==="
fd -path "*shell*" -type f \( -name "*.test.tsx" -o -name "*.spec.tsx" -o -name "*.test.ts" -o -name "*.spec.ts" \) | head -20Repository: swallowedB/b-log
Length of output: 492
🏁 Script executed:
#!/bin/bash
# Search for CommandPaletteProvider in the codebase
echo "=== Searching for CommandPaletteProvider usage ==="
rg "CommandPaletteProvider" -n | head -30Repository: swallowedB/b-log
Length of output: 697
🏁 Script executed:
#!/bin/bash
# Find and examine the useSearchTransition hook
echo "=== Finding useSearchTransition hook ==="
fd -name "*.ts" -o -name "*.tsx" | xargs rg -l "useSearchTransition" | head -5Repository: swallowedB/b-log
Length of output: 479
🏁 Script executed:
#!/bin/bash
# Check the layout.tsx file to see if SiteHeader is rendered within CommandPaletteProvider
echo "=== Checking layout.tsx ==="
cat -n src/app/\(layout\)/layout.tsx | head -40Repository: swallowedB/b-log
Length of output: 572
🏁 Script executed:
#!/bin/bash
# Read the useSearchTransition hook implementation
echo "=== Reading useSearchTransition hook ==="
cat -n src/app/\(layout\)/\(shell\)/_components/home/_hooks/useSearchTransition.tsRepository: swallowedB/b-log
Length of output: 1652
🏁 Script executed:
#!/bin/bash
# Search for test files more broadly
echo "=== Searching for test files ==="
find . -type f \( -name "*.test.ts*" -o -name "*.spec.ts*" \) | grep -i "header\|shell" | head -20Repository: swallowedB/b-log
Length of output: 240
🏁 Script executed:
#!/bin/bash
# Read the SiteHeader component fully to understand context
echo "=== Reading full SiteHeader.tsx ==="
cat -n src/app/\(layout\)/\(shell\)/_components/layout/SiteHeader.tsxRepository: swallowedB/b-log
Length of output: 6045
🏁 Script executed:
#!/bin/bash
# Read the SiteHeader test file to check if it wraps with CommandPaletteProvider
echo "=== Reading SiteHeader.test.tsx ==="
cat -n src/app/\(layout\)/\(shell\)/_components/layout/_tests_/SiteHeader.test.tsxRepository: swallowedB/b-log
Length of output: 1407
🏁 Script executed:
#!/bin/bash
# Check CommandPalette implementation to understand the error
echo "=== Reading CommandPalette.tsx context setup ==="
head -50 src/components/common/CommandPalette.tsx | cat -nRepository: swallowedB/b-log
Length of output: 1552
테스트에서 CommandPaletteProvider로 감싸기 필요
SiteHeader.test.tsx를 확인하니 현재 CommandPaletteProvider 없이 컴포넌트를 렌더링하고 있습니다(14줄). 하지만 useSearchTransition 훅이 내부적으로 useCommandPalette()를 호출하므로(useSearchTransition.ts 10줄), 프로바이더 컨텍스트 없이 실행되면 오류가 발생합니다.
테스트를 다음과 같이 수정해 주세요:
describe("SiteHeader", () => {
test("로고가 렌더링된다", () => {
render(
<CommandPaletteProvider>
<SiteHeader />
</CommandPaletteProvider>
);
// ...
});
// 다른 테스트도 동일하게 적용
});모바일 메뉴 토글 버튼의 aria-label 수정
93줄의 aria-label이 "모바일 메뉴 열기"라고 고정되어 있는데, 실제로는 토글 버튼입니다(메뉴를 열거나 닫음). 접근성을 위해 현재 상태를 반영하도록 수정하세요:
aria-label={isMobileMenuOpen ? "모바일 메뉴 닫기" : "모바일 메뉴 열기"}🧰 Tools
🪛 GitHub Actions: CI
[error] 17-17: Test failed: useCommandPalette must be used within CommandPaletteProvider.
🤖 Prompt for AI Agents
In @src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx around lines 10 -
17, Tests render SiteHeader without the CommandPaletteProvider causing
useSearchTransition (which calls useCommandPalette) to fail; wrap SiteHeader in
CommandPaletteProvider in SiteHeader.test.tsx around each render (e.g., the
render in the "로고가 렌더링된다" test and other tests) so the context is available.
Also update the mobile menu toggle button in SiteHeader (the element using
isMobileMenuOpen) to set its aria-label dynamically using isMobileMenuOpen so it
reads "모바일 메뉴 닫기" when open and "모바일 메뉴 열기" when closed instead of a fixed
string.
| <motion.button | ||
| type="button" | ||
| className="md:hidden" | ||
| onClick={() => setOpen(!open)} | ||
| onClick={toggleMobileMenu} | ||
| aria-label="모바일 메뉴 열기" | ||
| whileTap={{ scale: 0.9, opacity: 0.8 }} | ||
| transition={{ duration: 0.08 }} | ||
| > | ||
| {open ? <X size={24} /> : <Menu size={24} />} | ||
| </button> | ||
| {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />} | ||
| </motion.button> |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
모바일 메뉴 토글 버튼의 aria-label 개선 제안
현재 aria-label="모바일 메뉴 열기"로 고정되어 있지만, 메뉴가 열려있을 때는 "닫기"가 더 적절합니다. 스크린 리더 사용자를 위해 동적으로 변경하는 것을 권장합니다.
♻️ 접근성 개선 제안
<motion.button
type="button"
className="md:hidden"
onClick={toggleMobileMenu}
- aria-label="모바일 메뉴 열기"
+ aria-label={isMobileMenuOpen ? "모바일 메뉴 닫기" : "모바일 메뉴 열기"}
+ aria-expanded={isMobileMenuOpen}
whileTap={{ scale: 0.9, opacity: 0.8 }}
transition={{ duration: 0.08 }}
>
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</motion.button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <motion.button | |
| type="button" | |
| className="md:hidden" | |
| onClick={() => setOpen(!open)} | |
| onClick={toggleMobileMenu} | |
| aria-label="모바일 메뉴 열기" | |
| whileTap={{ scale: 0.9, opacity: 0.8 }} | |
| transition={{ duration: 0.08 }} | |
| > | |
| {open ? <X size={24} /> : <Menu size={24} />} | |
| </button> | |
| {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />} | |
| </motion.button> | |
| <motion.button | |
| type="button" | |
| className="md:hidden" | |
| onClick={toggleMobileMenu} | |
| aria-label={isMobileMenuOpen ? "모바일 메뉴 닫기" : "모바일 메뉴 열기"} | |
| aria-expanded={isMobileMenuOpen} | |
| whileTap={{ scale: 0.9, opacity: 0.8 }} | |
| transition={{ duration: 0.08 }} | |
| > | |
| {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />} | |
| </motion.button> |
🤖 Prompt for AI Agents
In @src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx around lines 89 -
98, The aria-label on the mobile menu button is static; update the motion.button
inside the SiteHeader component to set aria-label dynamically based on
isMobileMenuOpen (e.g., use "모바일 메뉴 닫기" when true and "모바일 메뉴 열기" when false)
and also add aria-expanded={isMobileMenuOpen} to reflect state; the change
should be applied to the existing motion.button that calls toggleMobileMenu and
renders {isMobileMenuOpen ? <X .../> : <Menu .../>}.
| export type CommandItem = { | ||
| id: string; | ||
| label: string; | ||
| hint?: string; | ||
| href?: string; | ||
| searchableText?: string; | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/components/common/CommandPalette.tsx | head -100Repository: swallowedB/b-log
Length of output: 4181
🏁 Script executed:
rg "searchableText" src/components/common/CommandPalette.tsx -B 2 -A 2Repository: swallowedB/b-log
Length of output: 124
🌐 Web query:
cmdk library Command.Item value prop filtering behavior search
💡 Result:
Short answer — how Command.Item.value affects filtering in cmdk:
- If you pass a value prop on Command.Item, that value (not the rendered text) is used by the built‑in filter as the item’s primary string. If you don’t pass value it’s inferred from the item’s textContent. [1][2]
- The filter function is called with (value, search[, keywords]) and by default both strings are normalized (trim + lowercase) before comparing — unless you provide a custom filter. You can also provide keywords on Command.Item to act as aliases for filtering. [1][2]
- You can disable built‑in filtering with shouldFilter={false} or supply a custom filter to control ranking/removal. [1][2]
- Note: cmdk introduced a breaking change in v1.0 — the value prop became case‑sensitive (still trimmed). If you rely on automatic lowercasing, update code or use a custom filter. [3]
Sources: [1] cmdk README (GitHub). [2] cmdk docs on npm (older docs). [3] cmdk v1.0.0 release notes about value case sensitivity.
🏁 Script executed:
find . -name "*useCommandPalette*" -type fRepository: swallowedB/b-log
Length of output: 100
🏁 Script executed:
rg "useCommandPaletteInternal" --type ts --type tsx -lRepository: swallowedB/b-log
Length of output: 87
🏁 Script executed:
cat -n ./src/hooks/useCommandPaletteInternal.tsRepository: swallowedB/b-log
Length of output: 5098
searchableText 필드를 Command.Item의 value prop에 전달하세요
searchableText는 실제로 useCommandPaletteInternal에서 생성되고 있으며, 커스텀 필터링 로직에 포함되어 있습니다(label, hint와 함께). 다만 CommandPalette.tsx Line 90에서 Command.Item의 value prop으로는 item.label만 전달되고 있습니다.
cmdk 라이브러리에서 value prop은 필터링의 기준이 되므로, 현재 구조에서는:
- useCommandPaletteInternal의 커스텀 필터링: label + hint + searchableText 모두 사용 ✓
- Command.Item의 value: label만 사용 ✗
이미 필터링된 filteredItems를 렌더링하므로 실제 버그는 아니지만, searchableText가 생성되는 의도를 완전히 활용하려면 value={item.searchableText || item.label}로 수정하거나, cmdk의 자동 필터링을 의도적으로 비활성화하려면 shouldFilter={false} 속성을 Command에 추가하는 것이 더 명확합니다.
🤖 Prompt for AI Agents
In @src/components/common/CommandPalette.tsx around lines 9 - 15, Command.Item
currently passes only item.label as the value prop, which prevents the generated
searchableText from being used by cmdk's filtering; update the value usage so it
becomes value={item.searchableText || item.label} on Command.Item (or
alternatively disable cmdk's built-in filtering by adding shouldFilter={false}
to the Command container) to ensure searchableText is honored — look for
Command.Item in CommandPalette.tsx and adjust the value prop or add shouldFilter
on the Command component.
| <Dialog.Overlay className="fixed inset-0 z-60 bg-black/40 backdrop-blur-sm" /> | ||
| <Dialog.Content className="fixed left-1/2 top-50 z-70 w-full max-w-xl -translate-x-1/2 rounded-2xl border border-white/10 bg-foreground/70 dark:bg-foreground shadow-2xl"> | ||
| <Dialog.Title className="sr-only">사이트 검색</Dialog.Title> | ||
|
|
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
접근성 개선 제안: Dialog.Description 추가
Radix UI Dialog는 스크린 리더 사용자를 위해 Dialog.Description을 제공할 것을 권장합니다. 현재 Dialog.Title은 sr-only로 잘 처리되어 있는데, Description도 추가하면 더 좋을 것 같습니다.
♻️ 제안 코드
<Dialog.Title className="sr-only">사이트 검색</Dialog.Title>
+<Dialog.Description className="sr-only">
+ 페이지, 포스트, 명령어를 검색하세요
+</Dialog.Description>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Dialog.Overlay className="fixed inset-0 z-60 bg-black/40 backdrop-blur-sm" /> | |
| <Dialog.Content className="fixed left-1/2 top-50 z-70 w-full max-w-xl -translate-x-1/2 rounded-2xl border border-white/10 bg-foreground/70 dark:bg-foreground shadow-2xl"> | |
| <Dialog.Title className="sr-only">사이트 검색</Dialog.Title> | |
| <Dialog.Overlay className="fixed inset-0 z-60 bg-black/40 backdrop-blur-sm" /> | |
| <Dialog.Content className="fixed left-1/2 top-50 z-70 w-full max-w-xl -translate-x-1/2 rounded-2xl border border-white/10 bg-foreground/70 dark:bg-foreground shadow-2xl"> | |
| <Dialog.Title className="sr-only">사이트 검색</Dialog.Title> | |
| <Dialog.Description className="sr-only"> | |
| 페이지, 포스트, 명령어를 검색하세요 | |
| </Dialog.Description> |
🤖 Prompt for AI Agents
In @src/components/common/CommandPalette.tsx around lines 62 - 65, Add a
Dialog.Description to the CommandPalette Dialog to improve screen-reader
context: inside the Dialog.Content near the existing Dialog.Title (which is
sr-only), insert a Dialog.Description with a brief descriptive string (e.g., a
short Korean sentence like "검색어를 입력하여 사이트 내 항목을 찾습니다") and mark it sr-only if
you want it hidden visually, so assistive tech reads both title and description;
ensure you import/use the same Dialog namespace (Dialog.Description) so the
Radix Dialog properly associates the description with the dialog.
| <Command.List className="max-h-80 overflow-y-auto py-2"> | ||
| <Command.Empty className="px-4 py-3 text-xs text-background/70"> | ||
| 검색 결과가 없습니다. | ||
| </Command.Empty> | ||
|
|
||
| <Command.Group className="px-2 pt-1"> | ||
| {filteredItems.map((item) => ( | ||
| <Command.Item | ||
| key={item.id} | ||
| value={item.label} | ||
| onSelect={() => handleSelect(item)} | ||
| className="flex cursor-pointer flex-col gap-0.5 rounded-xl px-3 py-2 text-sm aria-selected:bg-background/10" | ||
| > | ||
| <span className="font-medium">{item.label}</span> | ||
| {item.hint && ( | ||
| <span className="text-xs text-background/70"> | ||
| {item.hint} | ||
| </span> | ||
| )} | ||
| </Command.Item> | ||
| ))} | ||
| </Command.Group> | ||
| </Command.List> |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
cmdk 필터링 동작 확인 필요
현재 filteredItems는 useCommandPaletteInternal에서 이미 필터링된 결과입니다. 그런데 cmdk의 Command 컴포넌트는 기본적으로 자체 필터링 로직을 가지고 있어서, 이중 필터링이 발생할 수 있습니다.
만약 커스텀 필터링만 사용하려면 Command 컴포넌트에 shouldFilter={false}를 추가하는 것이 좋습니다:
♻️ 제안 코드
-<Command className="overflow-hidden rounded-2xl bg-transparent text-sm text-background">
+<Command shouldFilter={false} className="overflow-hidden rounded-2xl bg-transparent text-sm text-background">이렇게 하면 useCommandPaletteInternal의 필터링 로직만 사용되어 searchableText 필드를 포함한 커스텀 검색이 일관되게 동작합니다.
🤖 Prompt for AI Agents
In @src/components/common/CommandPalette.tsx around lines 81 - 103, The Command
component is performing its own filtering in addition to the custom filtering
from useCommandPaletteInternal, causing double-filtering; update the Command
(root) element to disable built-in filtering by adding the shouldFilter={false}
prop so only filteredItems from useCommandPaletteInternal are rendered (ensure
you reference the Command.List that maps filteredItems and keep Command.Item
rendering unchanged).
| useEffect(() => { | ||
| const handler = (e: KeyboardEvent) => { | ||
| const isMac = navigator.platform.toLowerCase().includes("mac"); | ||
| const meta = isMac ? e.metaKey : e.ctrlKey; | ||
|
|
||
| if (meta && e.key.toLowerCase() === "k") { | ||
| e.preventDefault(); | ||
| togglePalette(); | ||
| } | ||
| }; | ||
|
|
||
| window.addEventListener("keydown", handler); | ||
| return () => window.removeEventListener("keydown", handler); | ||
| }, [togglePalette]); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
navigator.platform은 deprecated 상태입니다
navigator.platform은 deprecated되었으며, 향후 브라우저에서 제거될 수 있습니다. 현재로서는 동작에 문제가 없지만, 장기적으로 navigator.userAgentData를 사용하는 방안을 고려해 볼 수 있습니다.
♻️ 대안 제안 (optional)
useEffect(() => {
const handler = (e: KeyboardEvent) => {
- const isMac = navigator.platform.toLowerCase().includes("mac");
+ const isMac = navigator.userAgentData?.platform?.toLowerCase().includes("mac")
+ ?? navigator.platform?.toLowerCase().includes("mac")
+ ?? false;
const meta = isMac ? e.metaKey : e.ctrlKey;
if (meta && e.key.toLowerCase() === "k") {
e.preventDefault();
togglePalette();
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [togglePalette]);🤖 Prompt for AI Agents
In @src/hooks/useCommandPaletteInternal.ts around lines 142 - 155, The handler
inside useEffect uses the deprecated navigator.platform to detect macOS; update
the platform detection in the handler (used by useEffect and togglePalette) to
use feature detection: prefer navigator.userAgentData?.platform for modern
browsers and fall back to navigator.platform or a userAgent sniff only if
userAgentData is unavailable; ensure the logic still derives the boolean isMac
and uses it to compute meta (e.metaKey vs e.ctrlKey) and keep existing
prevention and togglePalette behavior.
요약
변경 내용
CommandPaletteProvider구축 및 Dialog(UI) 분리openPalette()호출 연동getAllPosts()기반 search index 구성useCommandPaletteInternal네이밍 변경 및 로직 정돈open/close/toggle/ESC/⌘K단축키 처리스크린샷/동영상 (선택)
테스트
pnpm test통과 (해당 없음)pnpm typecheck,pnpm lint)관련 이슈
close #95
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.