From d1f930984384bcbeaa5377c32dc97d1728d61199 Mon Sep 17 00:00:00 2001 From: "gioi.dinh" Date: Fri, 4 Oct 2024 08:21:42 +0700 Subject: [PATCH] fear: Input component --- src/components/Input/index.tsx | 84 ++++++++++++++++++++++- src/components/Input/input.scss | 117 +++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 6 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 453c742..5ab185f 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -2,6 +2,7 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; +import { useEffect, useMemo, useState } from "react"; export interface InputProps { /** Placeholder of the input */ @@ -12,12 +13,89 @@ export interface InputProps { const Input = ({ placeholder, onSelectItem }: InputProps) => { // DO NOT remove this log - console.log('input re-render') + console.log("input re-render"); // Your code start here - return + const [searchResults, setSearchResults] = useState([]); + const [inputValue, setInputValue] = useState(""); + const [isLoading, setIsLoading] = useState(true); + const [errMessage, setErrMessage] = useState(""); + + useEffect(() => { + let ignore = false; + setSearchResults([]); + setIsLoading(false); + setErrMessage(''); + + if (!inputValue) { + return; + } + + fetchData(inputValue) + .then((res) => { + console.log("result:", res); + if (!ignore) { + setSearchResults(res as []); + if (!res.length) { + setErrMessage("No results"); + } + setIsLoading(true); + + } + }) + .catch((error) => { + console.log("error"); + setIsLoading(true); + setErrMessage(error); + }); + return () => { + ignore = true; + } + }, [inputValue]); + + const onChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const debouncedOnChange = debounce(onChange, 500); + + const generateSearchResults = useMemo(() => { + return inputValue && ( +
+ {!isLoading ? : ( +
+
{errMessage}
+ {searchResults.map((item, index) => { + return ( +
onSelectItem(item)} + > + {item} +
+ ); + })} +
+ )} + +
+ ); + }, [searchResults, isLoading]); + + return ( + <> +
+ + {generateSearchResults} +
+ + ); // Your code end here }; export default Input; - diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 1dafbe7..fd92f9b 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -1,6 +1,117 @@ * { - box-sizing: border-box; + box-sizing: border-box; } -html{ - font-size: 16px; +html { + font-size: 16px; +} +.input-search-container { + width: 300px; +} +.input { + border-radius: 4px; + line-height: 1.5em; + font-size: 16px; + padding: 0.5em 1em; + width: 100%; +} +.search-result { + width: 100%; + position: relative; + border-radius: 4px; + min-height: 100px; + border: solid 1px #ddd; + margin-top: 4px; +} +.lists { + display: flex; + flex-direction: column; +} +.item { + padding: 1em 2em; +} +.item:hover { + background-color: #eee; + cursor: pointer; +} +.no-result { + font-style: italic; + color: #333; + text-align: center; +} +.error-message { + color: red; + font-style: italic; + padding: 0.5em 1em; +} +.loader-container { + height: 50px; + width: 50px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.8); +} +.loader { + animation: rotate 1s infinite; + height: 50px; + width: 50px; + position: absolute; +} +.loader:before, +.loader:after { + border-radius: 50%; + content: ""; + display: block; + height: 20px; + width: 20px; +} +.loader:before { + animation: ball1 1s infinite; + background-color: #ccc; + box-shadow: 30px 0 #333; + margin-bottom: 10px; +} +.loader:after { + animation: ball2 1s infinite; + background-color: #333; + box-shadow: 30px 0 #ccc; +} +@keyframes rotate { + 0% { + transform: rotate(0) scale(0.8); + } + 50% { + transform: rotate(360deg) scale(1.2); + } + to { + transform: rotate(720deg) scale(0.8); + } +} +@keyframes ball1 { + 0% { + box-shadow: 30px 0 #333; + } + 50% { + box-shadow: 0 0 #333; + margin-bottom: 0; + transform: translate(15px, 15px); + } + to { + box-shadow: 30px 0 #333; + margin-bottom: 10px; + } +} +@keyframes ball2 { + 0% { + box-shadow: 30px 0 #ccc; + } + 50% { + box-shadow: 0 0 #ccc; + margin-top: -20px; + transform: translate(15px, 15px); + } + to { + box-shadow: 30px 0 #ccc; + margin-top: 0; + } }