index.tsx raw
1 import { cn } from '@/lib/utils'
2 import { QrCodeIcon, SearchIcon, X } from 'lucide-react'
3 import { ComponentProps, forwardRef, useEffect, useState } from 'react'
4 import QrScannerModal from '../QrScannerModal'
5
6 type SearchInputProps = ComponentProps<'input'> & {
7 onQrScan?: (value: string) => void
8 }
9
10 const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
11 ({ value, onChange, className, onQrScan, ...props }, ref) => {
12 const [displayClear, setDisplayClear] = useState(false)
13 const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null)
14 const [showQrScanner, setShowQrScanner] = useState(false)
15
16 useEffect(() => {
17 setDisplayClear(!!value)
18 }, [value])
19
20 function setRefs(el: HTMLInputElement) {
21 setInputRef(el)
22 if (typeof ref === 'function') {
23 ref(el)
24 } else if (ref) {
25 ;(ref as React.MutableRefObject<HTMLInputElement | null>).current = el
26 }
27 }
28
29 const handleQrScan = (result: string) => {
30 // Strip nostr: prefix if present
31 const value = result.startsWith('nostr:') ? result.slice(6) : result
32 onQrScan?.(value)
33 }
34
35 return (
36 <>
37 <div
38 tabIndex={0}
39 className={cn(
40 'flex h-9 w-full items-center rounded-xl border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-all duration-200 md:text-sm [&:has(:focus-visible)]:ring-ring [&:has(:focus-visible)]:ring-2 [&:has(:focus-visible)]:outline-none hover:border-ring/50',
41 className
42 )}
43 >
44 <SearchIcon className="size-4 shrink-0 opacity-50" onClick={() => inputRef?.focus()} />
45 <input
46 {...props}
47 name="search-input"
48 ref={setRefs}
49 value={value}
50 onChange={onChange}
51 className="size-full mx-2 border-none bg-transparent focus:outline-none placeholder:text-muted-foreground"
52 />
53 {onQrScan && (
54 <button
55 type="button"
56 className="text-muted-foreground hover:text-foreground transition-colors size-5 shrink-0 flex items-center justify-center mr-1"
57 onMouseDown={(e) => e.preventDefault()}
58 onClick={() => setShowQrScanner(true)}
59 >
60 <QrCodeIcon className="size-4" />
61 </button>
62 )}
63 {displayClear && (
64 <button
65 type="button"
66 className="rounded-full bg-foreground/40 hover:bg-foreground transition-opacity size-5 shrink-0 flex flex-col items-center justify-center"
67 onMouseDown={(e) => e.preventDefault()}
68 onClick={() => onChange?.({ target: { value: '' } } as any)}
69 >
70 <X className="!size-3 shrink-0 text-background" strokeWidth={4} />
71 </button>
72 )}
73 </div>
74 {showQrScanner && (
75 <QrScannerModal onScan={handleQrScan} onClose={() => setShowQrScanner(false)} />
76 )}
77 </>
78 )
79 }
80 )
81 SearchInput.displayName = 'SearchInput'
82 export default SearchInput
83