aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/react/SearchButton.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/react/SearchButton.tsx')
-rw-r--r--src/components/react/SearchButton.tsx51
1 files changed, 51 insertions, 0 deletions
diff --git a/src/components/react/SearchButton.tsx b/src/components/react/SearchButton.tsx
new file mode 100644
index 0000000..17aad93
--- /dev/null
+++ b/src/components/react/SearchButton.tsx
@@ -0,0 +1,51 @@
+import React, { useState, useEffect } from 'react'
+import { Search } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import SearchDialog from './SearchDialog'
+import { ErrorBoundary } from './ErrorBoundary'
+
+const SearchButton: React.FC = () => {
+ const [isOpen, setIsOpen] = useState(false)
+
+ // Keyboard shortcut: Cmd/Ctrl + K
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ // Don't trigger if user is typing in an input, textarea, or contenteditable
+ const target = e.target as HTMLElement
+ const isInput = target.tagName === 'INPUT' ||
+ target.tagName === 'TEXTAREA' ||
+ target.isContentEditable
+
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k' && !isInput) {
+ e.preventDefault()
+ setIsOpen(true)
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown)
+ return () => window.removeEventListener('keydown', handleKeyDown)
+ }, [])
+
+ return (
+ <ErrorBoundary>
+ <Button
+ variant="outline"
+ size="icon"
+ onClick={() => setIsOpen(true)}
+ className="size-9 border md:border-0 md:bg-transparent md:hover:bg-muted md:-my-2 md:-me-2 md:size-8"
+ title="Search (⌘K)"
+ aria-label="Search blog posts"
+ aria-expanded={isOpen}
+ aria-haspopup="dialog"
+ >
+ <Search className="h-5 w-5 md:h-4 md:w-4" />
+ <span className="sr-only">Search</span>
+ </Button>
+ <ErrorBoundary>
+ <SearchDialog open={isOpen} onOpenChange={setIsOpen} />
+ </ErrorBoundary>
+ </ErrorBoundary>
+ )
+}
+
+export default SearchButton