Files
components/search.tsx
1'use client'
2
3import * as React from 'react'
4import { ArrowRight, Search } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import {
8    Command,
9    CommandEmpty,
10    CommandGroup,
11    CommandInput,
12    CommandItem,
13    CommandList,
14} from '@/components/ui/command'
15import {
16    Dialog,
17    DialogContent,
18    DialogFooter,
19    DialogTrigger,
20} from '@/components/ui/dialog'
21import { Kbd } from '@/components/ui/kbd'
22
23export function Search01() {
24    const [open, setOpen] = React.useState(false)
25
26    const navigate = (path: string) => {
27        if (typeof window !== 'undefined') {
28            window.top?.location.assign(path)
29        }
30    }
31
32    return (
33        <Dialog open={open} onOpenChange={setOpen}>
34            <DialogTrigger asChild>
35                <Button variant="outline">
36                    <Search className="h-4 w-4" />
37                    Search documentation...
38                </Button>
39            </DialogTrigger>
40
41            <DialogContent className="gap-0 p-0">
42                <Command>
43                    <CommandInput placeholder="Search documentation..." />
44                    <CommandList className="scrollbar-thin scrollbar-track-gray-100 scrollbar-thumb-gray-400 overflow-y-auto">
45                        <CommandEmpty>No results found.</CommandEmpty>
46                        <CommandGroup heading="Get Started">
47                            <CommandItem
48                                onSelect={() => {
49                                    navigate('/blocks')
50                                }}
51                            >
52                                <ArrowRight className="h-3 w-3" />
53                                Introduction
54                            </CommandItem>
55                        </CommandGroup>
56                        <CommandGroup heading="Pages">
57                            <CommandItem
58                                onSelect={() => {
59                                    navigate('/blocks/login')
60                                }}
61                            >
62                                <ArrowRight className="h-3 w-3" />
63                                Login
64                            </CommandItem>
65                            <CommandItem
66                                onSelect={() => {
67                                    navigate('/blocks/signup')
68                                }}
69                            >
70                                <ArrowRight className="h-3 w-3" />
71                                Signup
72                            </CommandItem>
73                        </CommandGroup>
74                        <CommandGroup heading="Blocks">
75                            <CommandItem
76                                onSelect={() => {
77                                    navigate('/blocks/calendar')
78                                }}
79                            >
80                                Calendar
81                            </CommandItem>
82                            <CommandItem
83                                onSelect={() => {
84                                    navigate('/blocks/color-picker')
85                                }}
86                            >
87                                Color Picker
88                            </CommandItem>
89                            <CommandItem
90                                onSelect={() => {
91                                    navigate('/blocks/cookie-banner')
92                                }}
93                            >
94                                Cookie Banner
95                            </CommandItem>
96                            <CommandItem
97                                onSelect={() => {
98                                    navigate('/blocks/full-calendar')
99                                }}
100                            >
101                                Full Calendar
102                            </CommandItem>
103                            <CommandItem
104                                onSelect={() => {
105                                    navigate('/blocks/kanban-board')
106                                }}
107                            >
108                                Kanban Board
109                            </CommandItem>
110                            <CommandItem
111                                onSelect={() => {
112                                    navigate('/blocks/otp')
113                                }}
114                            >
115                                OTP
116                            </CommandItem>
117                            <CommandItem
118                                onSelect={() => {
119                                    navigate('/blocks/rating')
120                                }}
121                            >
122                                Rating
123                            </CommandItem>
124                            <CommandItem
125                                onSelect={() => {
126                                    navigate('/blocks/sidebar')
127                                }}
128                            >
129                                Sidebar
130                            </CommandItem>
131                            <CommandItem
132                                onSelect={() => {
133                                    navigate('/blocks/slider')
134                                }}
135                            >
136                                Slider
137                            </CommandItem>
138                            <CommandItem
139                                onSelect={() => {
140                                    navigate('/blocks/upload')
141                                }}
142                            >
143                                Upload
144                            </CommandItem>
145                            <CommandItem
146                                onSelect={() => {
147                                    navigate('/blocks/clipboard-copy')
148                                }}
149                            >
150                                Clipboard Copy
151                            </CommandItem>
152                        </CommandGroup>
153                    </CommandList>
154                </Command>
155                <DialogFooter className="flex items-center justify-between px-4 py-3 text-sm">
156                    <div className="flex items-center gap-2">
157                        <Kbd>↵ Go to page</Kbd>
158                    </div>
159
160                    <div className="flex items-center gap-2">
161                        <Kbd>Esc</Kbd>
162                    </div>
163                </DialogFooter>
164            </DialogContent>
165        </Dialog>
166    )
167}
168
Search with dialog
search-01
Files
components/search.tsx
1'use client'
2
3import * as React from 'react'
4import { Search } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import {
8    Command,
9    CommandEmpty,
10    CommandGroup,
11    CommandInput,
12    CommandItem,
13    CommandList,
14    CommandShortcut,
15} from '@/components/ui/command'
16import {
17    Dialog,
18    DialogContent,
19    DialogFooter,
20    DialogTrigger,
21} from '@/components/ui/dialog'
22import { Kbd } from '@/components/ui/kbd'
23
24export function Search02() {
25    const [open, setOpen] = React.useState(false)
26
27    React.useEffect(() => {
28        const down = (e: KeyboardEvent) => {
29            if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
30                e.preventDefault()
31                setOpen((open) => !open)
32            }
33            if (e.key === 'l' && (e.metaKey || e.ctrlKey)) {
34                e.preventDefault()
35
36                if (typeof window !== 'undefined') {
37                    window.top?.location.assign('/blocks/login')
38                }
39            }
40            if (e.key === 'p' && (e.metaKey || e.ctrlKey)) {
41                e.preventDefault()
42
43                if (typeof window !== 'undefined') {
44                    window.top?.location.assign('/blocks')
45                }
46            }
47        }
48        document.addEventListener('keydown', down)
49        return () => document.removeEventListener('keydown', down)
50    }, [])
51
52    const navigate = (path: string) => {
53        if (typeof window !== 'undefined') {
54            window.top?.location.assign(path)
55        }
56    }
57
58    return (
59        <Dialog open={open} onOpenChange={setOpen}>
60            <DialogTrigger asChild>
61                <Button variant="outline">
62                    <Search className="h-4 w-4" />
63                    Search documentation...
64                    <Kbd>⌘K</Kbd>
65                </Button>
66            </DialogTrigger>
67
68            <DialogContent className="gap-0 p-0">
69                <Command>
70                    <CommandInput placeholder="Search documentation..." />
71                    <CommandList className="scrollbar-thin scrollbar-track-gray-100 scrollbar-thumb-gray-400 overflow-y-auto">
72                        <CommandEmpty>No results found.</CommandEmpty>
73                        <CommandGroup heading="Get Started">
74                            <CommandItem
75                                onSelect={() => {
76                                    navigate('/blocks')
77                                }}
78                            >
79                                Introduction
80                                <CommandShortcut>⌘P</CommandShortcut>
81                            </CommandItem>
82                        </CommandGroup>
83                        <CommandGroup heading="Pages">
84                            <CommandItem
85                                onSelect={() => {
86                                    navigate('/blocks/login')
87                                }}
88                            >
89                                Login
90                                <CommandShortcut>⌘L</CommandShortcut>
91                            </CommandItem>
92                            <CommandItem
93                                onSelect={() => {
94                                    navigate('/blocks/signup')
95                                }}
96                            >
97                                Signup
98                            </CommandItem>
99                        </CommandGroup>
100                        <CommandGroup heading="Blocks">
101                            <CommandItem
102                                onSelect={() => {
103                                    navigate('/blocks/calendar')
104                                }}
105                            >
106                                Calendar
107                            </CommandItem>
108                            <CommandItem
109                                onSelect={() => {
110                                    navigate('/blocks/color-picker')
111                                }}
112                            >
113                                Color Picker
114                            </CommandItem>
115                            <CommandItem
116                                onSelect={() => {
117                                    navigate('/blocks/cookie-banner')
118                                }}
119                            >
120                                Cookie Banner
121                            </CommandItem>
122                            <CommandItem
123                                onSelect={() => {
124                                    navigate('/blocks/full-calendar')
125                                }}
126                            >
127                                Full Calendar
128                            </CommandItem>
129                            <CommandItem
130                                onSelect={() => {
131                                    navigate('/blocks/kanban-board')
132                                }}
133                            >
134                                Kanban Board
135                            </CommandItem>
136                            <CommandItem
137                                onSelect={() => {
138                                    navigate('/blocks/otp')
139                                }}
140                            >
141                                OTP
142                            </CommandItem>
143                            <CommandItem
144                                onSelect={() => {
145                                    navigate('/blocks/rating')
146                                }}
147                            >
148                                Rating
149                            </CommandItem>
150                            <CommandItem
151                                onSelect={() => {
152                                    navigate('/blocks/sidebar')
153                                }}
154                            >
155                                Sidebar
156                            </CommandItem>
157                            <CommandItem
158                                onSelect={() => {
159                                    navigate('/blocks/slider')
160                                }}
161                            >
162                                Slider
163                            </CommandItem>
164                            <CommandItem
165                                onSelect={() => {
166                                    navigate('/blocks/upload')
167                                }}
168                            >
169                                Upload
170                            </CommandItem>
171                            <CommandItem
172                                onSelect={() => {
173                                    navigate('/blocks/clipboard-copy')
174                                }}
175                            >
176                                Clipboard Copy
177                            </CommandItem>
178                        </CommandGroup>
179                    </CommandList>
180                </Command>
181                <DialogFooter className="flex items-center justify-between px-4 py-3 text-sm">
182                    <div className="flex items-center gap-2">
183                        <Kbd>↵ Go to page</Kbd>
184                    </div>
185
186                    <div className="flex items-center gap-2">
187                        <Kbd>Esc</Kbd>
188                    </div>
189                </DialogFooter>
190            </DialogContent>
191        </Dialog>
192    )
193}
194
Search dialog with shortcuts
search-02
Files
components/search.tsx
1'use client'
2
3import * as React from 'react'
4import { ArrowRight } from 'lucide-react'
5
6import {
7    Command,
8    CommandEmpty,
9    CommandGroup,
10    CommandInput,
11    CommandItem,
12    CommandList,
13    CommandSeparator,
14} from '@/components/ui/command'
15
16export function Search03() {
17    const navigate = (path: string) => {
18        if (typeof window !== 'undefined') {
19            window.top?.location.assign(path)
20        }
21    }
22    return (
23        <Command className="border-border rounded-lg border shadow-md md:max-w-[550px]">
24            <CommandInput placeholder="Search documentation..." />
25            <CommandList className="scrollbar-thin scrollbar-track-gray-100 scrollbar-thumb-gray-400 overflow-y-auto">
26                <CommandEmpty>No results found.</CommandEmpty>
27                <CommandGroup heading="Get Started">
28                    <CommandItem
29                        onSelect={() => {
30                            navigate('/blocks')
31                        }}
32                    >
33                        <ArrowRight className="h-3 w-3" />
34                        Introduction
35                    </CommandItem>
36                </CommandGroup>
37                <CommandSeparator />
38                <CommandGroup heading="Pages">
39                    <CommandItem
40                        onSelect={() => {
41                            navigate('/blocks/login')
42                        }}
43                    >
44                        <ArrowRight className="h-3 w-3" />
45                        Login
46                    </CommandItem>
47                    <CommandItem
48                        onSelect={() => {
49                            navigate('/blocks/signup')
50                        }}
51                    >
52                        <ArrowRight className="h-3 w-3" />
53                        Signup
54                    </CommandItem>
55                </CommandGroup>
56                <CommandSeparator />
57                <CommandGroup heading="Blocks">
58                    <CommandItem
59                        onSelect={() => {
60                            navigate('/blocks/calendar')
61                        }}
62                    >
63                        Calendar
64                    </CommandItem>
65                    <CommandItem
66                        onSelect={() => {
67                            navigate('/blocks/color-picker')
68                        }}
69                    >
70                        Color Picker
71                    </CommandItem>
72                    <CommandItem
73                        onSelect={() => {
74                            navigate('/blocks/cookie-banner')
75                        }}
76                    >
77                        Cookie Banner
78                    </CommandItem>
79                    <CommandItem
80                        onSelect={() => {
81                            navigate('/blocks/full-calendar')
82                        }}
83                    >
84                        Full Calendar
85                    </CommandItem>
86                    <CommandItem
87                        onSelect={() => {
88                            navigate('/blocks/kanban-board')
89                        }}
90                    >
91                        Kanban Board
92                    </CommandItem>
93                    <CommandItem
94                        onSelect={() => {
95                            navigate('/blocks/otp')
96                        }}
97                    >
98                        OTP
99                    </CommandItem>
100                    <CommandItem
101                        onSelect={() => {
102                            navigate('/blocks/rating')
103                        }}
104                    >
105                        Rating
106                    </CommandItem>
107                    <CommandItem
108                        onSelect={() => {
109                            navigate('/blocks/sidebar')
110                        }}
111                    >
112                        Sidebar
113                    </CommandItem>
114                    <CommandItem
115                        onSelect={() => {
116                            navigate('/blocks/slider')
117                        }}
118                    >
119                        Slider
120                    </CommandItem>
121                    <CommandItem
122                        onSelect={() => {
123                            navigate('/blocks/upload')
124                        }}
125                    >
126                        Upload
127                    </CommandItem>
128                    <CommandItem
129                        onSelect={() => {
130                            navigate('/blocks/clipboard-copy')
131                        }}
132                    >
133                        Clipboard Copy
134                    </CommandItem>
135                </CommandGroup>
136            </CommandList>
137        </Command>
138    )
139}
140
Search without dialog
search-03
Files
components/search.tsx
1'use client'
2
3import * as React from 'react'
4import debounce from 'lodash.debounce'
5import { CheckCircle2, CircleAlert, Search, X } from 'lucide-react'
6
7import { Input } from '@/components/ui/input'
8import {
9    Table,
10    TableBody,
11    TableCell,
12    TableHead,
13    TableHeader,
14    TableRow,
15} from '@/components/ui/table'
16import {
17    Tooltip,
18    TooltipContent,
19    TooltipProvider,
20    TooltipTrigger,
21} from '@/components/ui/tooltip'
22
23type Todo = {
24    id: number
25    title: string
26    completed: boolean
27}
28
29const TODOS: Todo[] = [
30    { id: 1, title: 'Review pending user registrations', completed: false },
31    { id: 2, title: 'Fix mobile layout issues on dashboard', completed: true },
32    { id: 3, title: 'Implement global search functionality', completed: false },
33    { id: 4, title: 'Optimize API response time', completed: true },
34    { id: 5, title: 'Deploy latest build to staging server', completed: true },
35    { id: 6, title: 'Update role-based access permissions', completed: false },
36    {
37        id: 7,
38        title: 'Resolve payment gateway webhook errors',
39        completed: false,
40    },
41]
42
43export function Search04() {
44    const [query, setQuery] = React.useState('')
45    const [searchValue, setSearchValue] = React.useState('')
46
47    const debouncedSearch = debounce((search) => {
48        setQuery(search)
49    }, 500)
50
51    const filteredTodos = React.useMemo(() => {
52        const lowerQuery = query.toLowerCase()
53
54        return TODOS.filter(
55            (todo) =>
56                todo.title.toLowerCase().includes(lowerQuery) ||
57                todo.id.toString().includes(lowerQuery),
58        )
59    }, [query])
60
61    return (
62        <div className="flex w-full max-w-md flex-col gap-3">
63            <div className="relative">
64                <Input
65                    type="text"
66                    value={searchValue}
67                    placeholder="Search to-do..."
68                    className="w-full bg-transparent pr-10 text-sm outline-none"
69                    iconLeft={<Search className="h-4 w-4" />}
70                    onChange={(e) => {
71                        setSearchValue(e.target.value)
72                        debouncedSearch(e.target.value)
73                    }}
74                />
75                {searchValue && (
76                    <button
77                        type="button"
78                        className="absolute top-1/2 right-2 flex -translate-y-1/2"
79                    >
80                        <X
81                            className="text-gray size-4.5 transition hover:text-[red]"
82                            onClick={() => {
83                                setSearchValue('')
84                                setQuery('')
85                            }}
86                        />
87                    </button>
88                )}
89            </div>
90
91            <Table className="rounded-md">
92                <TableHeader>
93                    <TableRow>
94                        <TableHead className="w-[120px]">Task ID</TableHead>
95                        <TableHead>Task Title</TableHead>
96                        <TableHead>Status</TableHead>
97                    </TableRow>
98                </TableHeader>
99                <TableBody>
100                    {filteredTodos.length === 0 ? (
101                        <TableRow className="text-center">
102                            <TableCell colSpan={3}>No results found</TableCell>
103                        </TableRow>
104                    ) : (
105                        filteredTodos.map((todo) => (
106                            <TableRow key={todo.id}>
107                                <TableCell className="font-medium">
108                                    {todo.id}
109                                </TableCell>
110                                <TableCell>{todo.title}</TableCell>
111                                <TableCell className="text-center">
112                                    <TooltipProvider>
113                                        <Tooltip>
114                                            <TooltipTrigger asChild>
115                                                <span className="inline-flex justify-center">
116                                                    {todo.completed ? (
117                                                        <CheckCircle2 className="text-success h-4 w-4" />
118                                                    ) : (
119                                                        <CircleAlert className="text-danger h-4 w-4" />
120                                                    )}
121                                                </span>
122                                            </TooltipTrigger>
123
124                                            <TooltipContent side="top">
125                                                <p>
126                                                    {todo.completed
127                                                        ? 'Completed'
128                                                        : 'Pending'}
129                                                </p>
130                                            </TooltipContent>
131                                        </Tooltip>
132                                    </TooltipProvider>
133                                </TableCell>
134                            </TableRow>
135                        ))
136                    )}
137                </TableBody>
138            </Table>
139        </div>
140    )
141}
142
Search with debounce
search-04
Files
components/search.tsx
1'use client'
2
3import * as React from 'react'
4import debounce from 'lodash.debounce'
5import { CheckCircle2, CircleAlert, Search, X } from 'lucide-react'
6
7import { cn } from '@/lib/utils'
8import { Input } from '@/components/ui/input'
9import {
10    Table,
11    TableBody,
12    TableCell,
13    TableHead,
14    TableHeader,
15    TableRow,
16} from '@/components/ui/table'
17import {
18    Tooltip,
19    TooltipContent,
20    TooltipProvider,
21    TooltipTrigger,
22} from '@/components/ui/tooltip'
23
24type Todo = {
25    id: number
26    title: string
27    completed: boolean
28}
29
30const TODOS: Todo[] = [
31    { id: 1, title: 'Review pending user registrations', completed: false },
32    { id: 2, title: 'Fix mobile layout issues on dashboard', completed: true },
33    { id: 3, title: 'Implement global search functionality', completed: false },
34    { id: 4, title: 'Optimize API response time', completed: true },
35    { id: 5, title: 'Deploy latest build to staging server', completed: true },
36    { id: 6, title: 'Update role-based access permissions', completed: false },
37    {
38        id: 7,
39        title: 'Resolve payment gateway webhook errors',
40        completed: false,
41    },
42]
43
44export function Search05() {
45    const [query, setQuery] = React.useState('')
46    const [isOpen, setIsOpen] = React.useState(false)
47    const [searchValue, setSearchValue] = React.useState('')
48
49    const debouncedSearch = debounce((search) => {
50        setQuery(search)
51    }, 500)
52
53    const filteredTodos = React.useMemo(() => {
54        const lowerQuery = query.toLowerCase()
55
56        return TODOS.filter(
57            (todo) =>
58                todo.title.toLowerCase().includes(lowerQuery) ||
59                todo.id.toString().includes(lowerQuery),
60        )
61    }, [query])
62
63    const toggleSearch = () => {
64        setIsOpen((prev) => {
65            if (prev) setQuery('')
66            setSearchValue('')
67            return !prev
68        })
69    }
70
71    return (
72        <div className="flex w-full max-w-md flex-col gap-3">
73            <div className="relative flex items-center">
74                <button
75                    onClick={toggleSearch}
76                    className="z-10 p-2 text-gray-600 transition-colors hover:text-gray-900"
77                >
78                    {isOpen ? (
79                        <X className="h-5 w-5" />
80                    ) : (
81                        <Search className="h-5 w-5" />
82                    )}
83                </button>
84                <div className="relative w-full">
85                    <Input
86                        type="text"
87                        placeholder="Search to-do..."
88                        value={searchValue}
89                        className={cn(
90                            'right-0 w-0 bg-transparent text-sm transition-all duration-300 ease-in-out outline-none',
91                            isOpen ? 'w-full opacity-100' : 'opacity-0',
92                        )}
93                        onChange={(e) => {
94                            setSearchValue(e.target.value)
95                            debouncedSearch(e.target.value)
96                        }}
97                    />
98                    {searchValue && (
99                        <button
100                            type="button"
101                            className="absolute top-1/2 right-2 flex -translate-y-1/2"
102                        >
103                            <X
104                                className="text-gray size-4.5 transition hover:text-[red]"
105                                onClick={() => {
106                                    setSearchValue('')
107                                    setQuery('')
108                                }}
109                            />
110                        </button>
111                    )}
112                </div>
113            </div>
114
115            <Table className="rounded-md">
116                <TableHeader>
117                    <TableRow>
118                        <TableHead className="w-[120px]">Task ID</TableHead>
119                        <TableHead>Task Title</TableHead>
120                        <TableHead>Status</TableHead>
121                    </TableRow>
122                </TableHeader>
123                <TableBody>
124                    {filteredTodos.length === 0 ? (
125                        <TableRow className="text-center">
126                            <TableCell colSpan={3}>No results found</TableCell>
127                        </TableRow>
128                    ) : (
129                        filteredTodos.map((todo) => (
130                            <TableRow key={todo.id}>
131                                <TableCell className="font-medium">
132                                    {todo.id}
133                                </TableCell>
134                                <TableCell>{todo.title}</TableCell>
135                                <TableCell className="text-center">
136                                    <TooltipProvider>
137                                        <Tooltip>
138                                            <TooltipTrigger asChild>
139                                                <span className="inline-flex justify-center">
140                                                    {todo.completed ? (
141                                                        <CheckCircle2 className="text-success h-4 w-4" />
142                                                    ) : (
143                                                        <CircleAlert className="text-danger h-4 w-4" />
144                                                    )}
145                                                </span>
146                                            </TooltipTrigger>
147
148                                            <TooltipContent side="top">
149                                                <p>
150                                                    {todo.completed
151                                                        ? 'Completed'
152                                                        : 'Pending'}
153                                                </p>
154                                            </TooltipContent>
155                                        </Tooltip>
156                                    </TooltipProvider>
157                                </TableCell>
158                            </TableRow>
159                        ))
160                    )}
161                </TableBody>
162            </Table>
163        </div>
164    )
165}
166
Input search open after icon click
search-05