Skip to content

Commit 851f730

Browse files
authored
feat: implement chat page (#21)
- responsive - chat search - interactive chat UI
1 parent a91e0aa commit 851f730

File tree

6 files changed

+987
-210
lines changed

6 files changed

+987
-210
lines changed

pnpm-lock.yaml

+258-209
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as React from 'react'
2+
import { useNavigate } from 'react-router-dom'
3+
import { IconLoader2, IconSearch, IconX } from '@tabler/icons-react'
4+
import { cn } from '@/lib/utils'
5+
import usePrevious from '@/hooks/use-previous'
6+
import { Separator } from '../ui/separator'
7+
import { Button } from './button'
8+
9+
interface SearchInputProps {
10+
name?: string
11+
className?: string
12+
defaultSearchValue?: string
13+
isPending?: boolean
14+
placeholder?: string
15+
searchText?: string
16+
disableSearchParam?: boolean
17+
onSubmit: (text: string) => void
18+
searchParam?: 'search' | 'q'
19+
}
20+
21+
const SearchInput = ({
22+
name = 'search',
23+
className,
24+
defaultSearchValue = '',
25+
isPending = false,
26+
placeholder = 'Search',
27+
searchText = 'Search',
28+
onSubmit,
29+
// disableSearchParam = false,
30+
// searchParam = 'search',
31+
}: SearchInputProps) => {
32+
const navigate = useNavigate()
33+
const [value, setValue] = React.useState(defaultSearchValue)
34+
const previous = usePrevious(value)
35+
36+
const reset = React.useCallback(() => {
37+
setValue('')
38+
onSubmit('')
39+
40+
// if (!disableSearchParam)
41+
// navigate({
42+
// search: (prev) => ({ ...prev, [searchParam]: '' }),
43+
// })
44+
}, [onSubmit])
45+
46+
React.useEffect(() => {
47+
if (previous && previous?.trim().length > 0 && value.trim().length < 1)
48+
reset()
49+
}, [previous, reset, value])
50+
51+
return (
52+
<form
53+
className={cn(
54+
`flex h-9 w-56 items-center space-x-0 rounded-md border border-input bg-white pl-2 focus-within:outline-none focus-within:ring-1 focus-within:ring-ring`,
55+
className
56+
)}
57+
onSubmit={(e) => {
58+
e.preventDefault()
59+
onSubmit(value)
60+
// if (!disableSearchParam)
61+
// navigate({ search: (prev) => ({ ...prev, [searchParam]: value }) })
62+
}}
63+
>
64+
{isPending && value.trim().length > 0 ? (
65+
<IconLoader2 size={14} className='mr-2 animate-spin stroke-blue-500' />
66+
) : (
67+
<IconSearch size={14} className='mr-2' />
68+
)}
69+
<div className='relative flex-1'>
70+
<input
71+
name={name}
72+
autoComplete='off'
73+
placeholder={placeholder}
74+
value={value}
75+
onChange={(e) => setValue(e.target.value)}
76+
type='text'
77+
className={'w-full text-sm focus-visible:outline-none'}
78+
/>
79+
{value.trim().length > 0 && (
80+
<Button
81+
type='button'
82+
variant='ghost'
83+
size='icon'
84+
className='absolute right-1 top-1/2 size-6 -translate-y-1/2'
85+
onClick={reset}
86+
>
87+
<IconX size={14} />
88+
</Button>
89+
)}
90+
</div>
91+
<Separator orientation='vertical' />
92+
<Button
93+
variant='ghost'
94+
size='sm'
95+
className='h-full rounded-l-none rounded-r-md focus-visible:ring-2 focus-visible:ring-blue-500'
96+
>
97+
{searchText}
98+
</Button>
99+
</form>
100+
)
101+
}
102+
SearchInput.displayName = 'SearchInput'
103+
104+
export { SearchInput }

0 commit comments

Comments
 (0)