@@ -4,7 +4,15 @@ import { type KeybindingCommand } from "@t3tools/contracts";
44import { useQuery } from "@tanstack/react-query" ;
55import { useNavigate } from "@tanstack/react-router" ;
66import { MessageSquareIcon , SettingsIcon , SquarePenIcon } from "lucide-react" ;
7- import { createContext , useCallback , useContext , useMemo , useState , type ReactNode } from "react" ;
7+ import {
8+ createContext ,
9+ useCallback ,
10+ useContext ,
11+ useDeferredValue ,
12+ useMemo ,
13+ useState ,
14+ type ReactNode ,
15+ } from "react" ;
816import { useAppSettings } from "../appSettings" ;
917import { useHandleNewThread } from "../hooks/useHandleNewThread" ;
1018import {
@@ -63,6 +71,10 @@ function iconClassName() {
6371 return "size-4 text-muted-foreground/80" ;
6472}
6573
74+ function normalizeSearchText ( value : string ) : string {
75+ return value . trim ( ) . toLowerCase ( ) . replace ( / \s + / g, " " ) ;
76+ }
77+
6678export function useCommandPalette ( ) {
6779 const context = useContext ( CommandPaletteContext ) ;
6880 if ( ! context ) {
@@ -108,6 +120,8 @@ function CommandPaletteDialog() {
108120function OpenCommandPaletteDialog ( ) {
109121 const navigate = useNavigate ( ) ;
110122 const { setOpen } = useCommandPalette ( ) ;
123+ const [ query , setQuery ] = useState ( "" ) ;
124+ const deferredQuery = useDeferredValue ( query ) ;
111125 const { settings } = useAppSettings ( ) ;
112126 const { activeDraftThread, activeThread, handleNewThread, projects } = useHandleNewThread ( ) ;
113127 const threads = useStore ( ( store ) => store . threads ) ;
@@ -118,7 +132,7 @@ function OpenCommandPaletteDialog() {
118132 [ projects ] ,
119133 ) ;
120134
121- const groups = useMemo < CommandPaletteGroup [ ] > ( ( ) => {
135+ const allGroups = useMemo < CommandPaletteGroup [ ] > ( ( ) => {
122136 const actionItems : CommandPaletteItem [ ] = [ ] ;
123137 if ( projects . length > 0 ) {
124138 const activeProjectTitle =
@@ -248,6 +262,25 @@ function OpenCommandPaletteDialog() {
248262 threads ,
249263 ] ) ;
250264
265+ const filteredGroups = useMemo ( ( ) => {
266+ const normalizedQuery = normalizeSearchText ( deferredQuery ) ;
267+ if ( normalizedQuery . length === 0 ) {
268+ return allGroups ;
269+ }
270+
271+ return allGroups
272+ . map ( ( group ) => ( {
273+ ...group ,
274+ items : group . items . filter ( ( item ) => {
275+ const haystack = normalizeSearchText (
276+ [ item . label , item . title , item . description ?? "" ] . join ( " " ) ,
277+ ) ;
278+ return haystack . includes ( normalizedQuery ) ;
279+ } ) ,
280+ } ) )
281+ . filter ( ( group ) => group . items . length > 0 ) ;
282+ } , [ allGroups , deferredQuery ] ) ;
283+
251284 const executeItem = useCallback (
252285 ( item : CommandPaletteItem ) => {
253286 setOpen ( false ) ;
@@ -268,11 +301,11 @@ function OpenCommandPaletteDialog() {
268301 className = "overflow-hidden p-0"
269302 data-testid = "command-palette"
270303 >
271- < Command aria-label = "Command palette" items = { groups } >
304+ < Command aria-label = "Command palette" mode = "none" onValueChange = { setQuery } value = { query } >
272305 < CommandInput placeholder = "Search commands and threads..." />
273306 < CommandPanel className = "max-h-[min(28rem,70vh)]" >
274307 < CommandList >
275- { groups . map ( ( group ) => (
308+ { filteredGroups . map ( ( group ) => (
276309 < CommandGroup items = { group . items } key = { group . value } >
277310 < CommandGroupLabel > { group . label } </ CommandGroupLabel >
278311 < CommandCollection >
0 commit comments