From 5ee0f15d946ea04de0433d0a9322f3c10343e96b Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 18 Nov 2024 03:18:39 +0530 Subject: [PATCH] (feat: search): close on click outside --- .../src/components/DocsGPTWidget.tsx | 9 +-- .../react-widget/src/components/SearchBar.tsx | 58 +++++++++++++++---- extensions/react-widget/src/types/index.ts | 1 + frontend/.env.development | 2 +- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index d471aa57..b9ee8acc 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -78,14 +78,14 @@ const openContainer = keyframes` height: 100px; } 100% { - width: ${(props) => props.theme.dimensions.width} !important; - height: ${(props) => props.theme.dimensions.height} !important; + width: ${(props) => props.theme.dimensions.width}; + height: ${(props) => props.theme.dimensions.height}; border-radius: 12px; }` const closeContainer = keyframes` 0% { - width: ${(props) => props.theme.dimensions.width} !important; - height: ${(props) => props.theme.dimensions.height} !important; + width: ${(props) => props.theme.dimensions.width}; + height: ${(props) => props.theme.dimensions.height}; border-radius: 12px; } 100% { @@ -671,6 +671,7 @@ export const WidgetCore = ({ const handleImageError = (event: React.SyntheticEvent) => { event.currentTarget.src = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"; }; + const dimensions = typeof size === 'object' && 'custom' in size ? sizesConfig.getCustom(size.custom) diff --git a/extensions/react-widget/src/components/SearchBar.tsx b/extensions/react-widget/src/components/SearchBar.tsx index 5486871b..a417b955 100644 --- a/extensions/react-widget/src/components/SearchBar.tsx +++ b/extensions/react-widget/src/components/SearchBar.tsx @@ -39,15 +39,16 @@ const Main = styled.div` font-family: sans-serif; ` -const TextField = styled.input` +const TextField = styled.input<{inputWidth:string}>` padding: 6px 6px; + width: ${({inputWidth}) => inputWidth}; border-radius: 8px; display: inline; color: ${props => props.theme.primary.text}; outline: none; border: none; background-color: ${props => props.theme.secondary.bg}; - width: 240px; + &:focus { outline: none; box-shadow: 0px 0px 0px 2px rgba(0, 109, 199); @@ -61,6 +62,7 @@ const Container = styled.div` ` const SearchResults = styled.div` position: absolute; + display: block; background-color: ${props => props.theme.primary.bg}; opacity: 90%; border: 1px solid rgba(0, 0, 0, .1); @@ -77,6 +79,11 @@ const SearchResults = styled.div` scrollbar-width: thin; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1); backdrop-filter: blur(16px); + @media only screen and (max-width: 768px) { + max-height: 100vh; + max-width: 80vw; + overflow: auto; + } ` const Title = styled.h3` font-size: 14px; @@ -137,17 +144,45 @@ font-size: 12px; color: #007ee6; } ` +const Toolkit = styled.kbd` + position: absolute; + right: 12px; + top: 4px; + background-color: ${(props) => props.theme.primary.bg}; + color: ${(props) => props.theme.secondary.text}; + font-size: 9px; + padding: 2px; + border: 1px solid ${(props) => props.theme.secondary.text}; + border-radius: 4px; +` export const SearchBar = ({ apiKey = "79bcbf0e-3dd1-4ac3-b893-e41b3d40ec8d", apiHost = "http://127.0.0.1:7091", - theme = "light", - placeholder = "Search or Ask AI" + theme = "dark", + placeholder = "Search or Ask AI...", + width="240px" }: SearchBarProps) => { - const [input, setInput] = React.useState("") + const [input, setInput] = React.useState(""); const [isWidgetOpen, setIsWidgetOpen] = React.useState(false); - const inputRef = React.useRef(null) - const widgetRef = React.useRef(null) - const [results, setResults] = React.useState([]) + const inputRef = React.useRef(null); + const resultsRef = React.useRef(null); + const [isResultVisible, setIsResultVisible] = React.useState(true); + const [results, setResults] = React.useState([]); + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + resultsRef.current && + !resultsRef.current.contains(event.target as Node) + ) { + setIsResultVisible(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => { + resultsRef.current && (resultsRef.current.style.display = 'block') + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []) React.useEffect(() => { input.length > 0 ? getSearchResults(input, apiKey, apiHost) @@ -171,6 +206,8 @@ export const SearchBar = ({
setIsResultVisible(true)} ref={inputRef} onKeyDown={(e) => handleKeyDown(e)} placeholder={placeholder} @@ -178,8 +215,8 @@ export const SearchBar = ({ onChange={(e) => setInput(e.target.value)} /> { - input.length > 0 && results.length > 0 && ( - + input.length > 0 && results.length > 0 && isResultVisible && ( + {results.map((res) => (
{res.title} @@ -194,6 +231,7 @@ export const SearchBar = ({ ) } + Enter