From 503e0076e5f89cfb665cefb57d7600c45847afc6 Mon Sep 17 00:00:00 2001 From: Afengi110 Date: Wed, 22 Apr 2026 09:55:43 +0300 Subject: [PATCH 1/2] solution --- src/App.tsx | 114 ++++++++++++++++++++-------------- src/components/peopleList.tsx | 26 ++++++++ src/hooks/Filter.ts | 14 +++++ 3 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 src/components/peopleList.tsx create mode 100644 src/hooks/Filter.ts diff --git a/src/App.tsx b/src/App.tsx index a88cd7a6d..017e11d8c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,73 +1,93 @@ -import React from 'react'; -import './App.scss'; +import React, { useEffect, useState } from 'react'; +import { PeopleList } from './components/peopleList'; +import { useFilter } from './hooks/Filter'; +import { Person } from './types/Person'; import { peopleFromServer } from './data/people'; export const App: React.FC = () => { - const { name, born, died } = peopleFromServer[0]; + const [query, setQuery] = useState(''); + const [data] = useState(peopleFromServer); + const [error, setError] = useState(false); + const [selectedPerson, setSelectedPerson] = useState(null); + const [show, setShow] = useState(false); + + const filteredPeople = useFilter(data, query); + + useEffect(() => { + // скрываем список после выбора + if (selectedPerson && show) { + setShow(false); + } + + // показываем ошибку только когда dropdown открыт + if (show && query.trim() !== '' && filteredPeople.length === 0) { + setError(true); + } else { + setError(false); + } + }, [filteredPeople, query, selectedPerson, show]); + + const handleInputChange = (event: React.ChangeEvent) => { + setQuery(event.target.value); + setSelectedPerson(null); + setShow(true); + }; + + const handleSelect = (person: Person) => { + setSelectedPerson(person); + setQuery(person.name); // подставляем имя в input + }; return (

- {`${name} (${born} - ${died})`} + {selectedPerson + ? `${selectedPerson.name} (${selectedPerson.born} - ${selectedPerson.died})` + : 'No selected person'}

-
+
setShow(true)} />
-
-
-
-

Pieter Haverbeke

-
- -
-

Pieter Bernard Haverbeke

-
- -
-

Pieter Antone Haverbeke

-
- -
-

Elisabeth Haverbeke

-
- -
-

Pieter de Decker

-
- -
-

Petronella de Decker

-
- -
-

Elisabeth Hercke

+ {show && ( +
+
+
-
+ )}
-
-

No matching suggestions

-
+ {error && ( +
+

No matching suggestions

+
+ )}
); diff --git a/src/components/peopleList.tsx b/src/components/peopleList.tsx new file mode 100644 index 000000000..271391c30 --- /dev/null +++ b/src/components/peopleList.tsx @@ -0,0 +1,26 @@ +import type { Person } from '../types/Person'; + +interface PeopleListProps { + people: Person[]; + onSelect?: (person: Person) => void; +} + +export const PeopleList = ({ + people, + onSelect = () => {}, +}: PeopleListProps) => { + return ( + <> + {people.map(person => ( +
onSelect(person)} + > + {person.name} +
+ ))} + + ); +}; diff --git a/src/hooks/Filter.ts b/src/hooks/Filter.ts new file mode 100644 index 000000000..f59ce8142 --- /dev/null +++ b/src/hooks/Filter.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react'; +import { Person } from '../types/Person'; + +export const useFilter = (people: Person[], query: string) => { + return useMemo(() => { + if (!query.trim()) { + return people; // 👈 show all when empty + } + + return people.filter(person => + person.name.toLowerCase().includes(query.toLowerCase()), + ); + }, [people, query]); +}; From a5c5397caa711aa8f8e32b66e3dae936723a8e0b Mon Sep 17 00:00:00 2001 From: Afengi110 Date: Wed, 22 Apr 2026 10:12:47 +0300 Subject: [PATCH 2/2] sol --- src/App.tsx | 81 +++---------------------------- src/components/Autocomplete.tsx | 85 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 75 deletions(-) create mode 100644 src/components/Autocomplete.tsx diff --git a/src/App.tsx b/src/App.tsx index 017e11d8c..3ec506ada 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,42 +1,10 @@ -import React, { useEffect, useState } from 'react'; -import { PeopleList } from './components/peopleList'; -import { useFilter } from './hooks/Filter'; +import React, { useState } from 'react'; +import { Autocomplete } from './components/Autocomplete'; import { Person } from './types/Person'; import { peopleFromServer } from './data/people'; export const App: React.FC = () => { - const [query, setQuery] = useState(''); - const [data] = useState(peopleFromServer); - const [error, setError] = useState(false); const [selectedPerson, setSelectedPerson] = useState(null); - const [show, setShow] = useState(false); - - const filteredPeople = useFilter(data, query); - - useEffect(() => { - // скрываем список после выбора - if (selectedPerson && show) { - setShow(false); - } - - // показываем ошибку только когда dropdown открыт - if (show && query.trim() !== '' && filteredPeople.length === 0) { - setError(true); - } else { - setError(false); - } - }, [filteredPeople, query, selectedPerson, show]); - - const handleInputChange = (event: React.ChangeEvent) => { - setQuery(event.target.value); - setSelectedPerson(null); - setShow(true); - }; - - const handleSelect = (person: Person) => { - setSelectedPerson(person); - setQuery(person.name); // подставляем имя в input - }; return (
@@ -47,47 +15,10 @@ export const App: React.FC = () => { : 'No selected person'} -
-
- setShow(true)} - /> -
- - {show && ( -
-
- -
-
- )} -
- - {error && ( -
-

No matching suggestions

-
- )} +
); diff --git a/src/components/Autocomplete.tsx b/src/components/Autocomplete.tsx new file mode 100644 index 000000000..010437467 --- /dev/null +++ b/src/components/Autocomplete.tsx @@ -0,0 +1,85 @@ +import React, { useEffect, useState } from 'react'; +import { Person } from '../types/Person'; +import { useFilter } from '../hooks/Filter'; +import { PeopleList } from './peopleList'; + +interface Props { + people: Person[]; + delay?: number; + onSelected: (person: Person | null) => void; +} + +export const Autocomplete: React.FC = ({ + people, + delay = 300, + onSelected, +}) => { + const [rawInput, setRawInput] = useState(''); + const [query, setQuery] = useState(''); + const [show, setShow] = useState(false); + + const filteredPeople = useFilter(people, query); + + // 🔹 debounce + useEffect(() => { + const trimmed = rawInput.trim(); + + const timer = setTimeout(() => { + setQuery(trimmed); // даже пустая строка → покажет всех + }, delay); + + return () => clearTimeout(timer); + }, [rawInput, delay]); + + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value; + + setRawInput(value); + setShow(true); + + // 🔴 ВАЖНО: сбрасываем выбранного человека + onSelected(null); + }; + + const handleSelect = (person: Person) => { + setRawInput(person.name); + setQuery(person.name); + setShow(false); + onSelected(person); + }; + + const showError = show && query !== '' && filteredPeople.length === 0; + + return ( +
+
+ setShow(true)} + /> +
+ + {show && ( +
+
+ +
+
+ )} + + {showError && ( +
+ No matching suggestions +
+ )} +
+ ); +};