diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx
index 453c742..3899ae0 100644
--- a/src/components/Input/index.tsx
+++ b/src/components/Input/index.tsx
@@ -2,22 +2,103 @@ import "./input.scss";
import { fetchData } from "../../utils/fetch-data";
import { debounce } from "../../utils/deboucne";
import Loader from "../Loader";
+import { useState } from "react";
export interface InputProps {
/** Placeholder of the input */
placeholder?: string;
/** On click item handler */
onSelectItem: (item: string) => void;
+ /** debounce time (in ms) */
+ debounceTime?: number;
}
-const Input = ({ placeholder, onSelectItem }: InputProps) => {
+export interface SearchResult {
+ loading: boolean;
+ error?: string;
+ items?: string[];
+}
+
+const Input = ({
+ placeholder,
+ onSelectItem,
+ debounceTime = 300,
+}: InputProps) => {
// DO NOT remove this log
- console.log('input re-render')
+ console.log("input re-render");
// Your code start here
- return
+ const [result, setResult] = useState(undefined);
+
+ const hangleChange = debounce((e: React.ChangeEvent) => {
+ const searchQuery = e.target.value;
+ if (!searchQuery) {
+ setResult(undefined);
+ return;
+ }
+
+ let ignored = false;
+ setResult({ loading: true });
+ fetchData(searchQuery)
+ .then((result) => {
+ if (ignored) return;
+ setResult({
+ loading: false,
+ items: result,
+ });
+ })
+ .catch((err) => {
+ if (ignored) return;
+ setResult({
+ loading: false,
+ error: err,
+ });
+ });
+
+ return () => {
+ ignored = true;
+ };
+ }, debounceTime);
+
+ const renderResult = () => {
+ if (result.loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (result.error) {
+ return {result.error}
;
+ }
+
+ if (!result.items?.length) {
+ return No result!
;
+ }
+
+ return (
+
+ {result.items.map((item) => (
+ -
+
+
+ ))}
+
+ );
+ };
+
+ return (
+
+
+ {!!result &&
{renderResult()}
}
+
+ );
// Your code end here
};
export default Input;
-
diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss
index 1dafbe7..4463e6d 100644
--- a/src/components/Input/input.scss
+++ b/src/components/Input/input.scss
@@ -1,6 +1,66 @@
* {
- box-sizing: border-box;
+ box-sizing: border-box;
}
-html{
- font-size: 16px;
+html {
+ font-size: 16px;
+}
+
+.search {
+ position: relative;
+ width: 300px;
+ line-height: 1.5em;
+ font-size: 16px;
+
+ &__input {
+ border-radius: 4px;
+ padding: 0.5em 1em;
+ width: 100%;
+ }
+
+ &__result {
+ position: absolute;
+ width: 100%;
+ background-color: #ffffff;
+ box-shadow: 0 0 10px 0 rgba(0, 0, 0, 10%);
+ top: calc(100% + 8px);
+ }
+
+ &__loader {
+ padding: 2em;
+ }
+
+ &__message {
+ padding: 0.5em 1em;
+ color: #03183f;
+ }
+
+ &__list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ max-height: 200px;
+ overflow-y: auto;
+ }
+ &__item {
+ button {
+ color: #03183f;
+ line-height: 1.5em;
+ font-size: 16px;
+ padding: 0.5em 1em;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: flex-start;
+ margin: 0;
+ border: none;
+ background: none;
+ width: 100%;
+
+ &:hover {
+ background-color: #f6f6f6;
+ }
+ }
+ }
}
diff --git a/src/stories/Input.stories.ts b/src/stories/Input.stories.ts
index 4961b83..fe0bda7 100644
--- a/src/stories/Input.stories.ts
+++ b/src/stories/Input.stories.ts
@@ -1,18 +1,18 @@
-import type { Meta, StoryObj } from '@storybook/react';
-import { fn } from '@storybook/test';
+import type { Meta, StoryObj } from "@storybook/react";
+import { fn } from "@storybook/test";
-import Input from '../components/Input';
+import Input from "../components/Input";
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
- title: 'Example/Input',
+ title: "Example/Input",
component: Input,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
- layout: 'centered',
+ layout: "centered",
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
- tags: ['autodocs'],
+ tags: ["autodocs"],
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
// args: { onClick: fn() },
} satisfies Meta;
@@ -24,6 +24,7 @@ type Story = StoryObj;
export const Primary: Story = {
args: {
placeholder: "Type something to search...",
- onSelectItem: fn()
+ debounceTime: 300,
+ onSelectItem: fn(),
},
};