diff --git a/frontend/src/components/common/CitySearchBar/CitySearchBar.tsx b/frontend/src/components/common/CitySearchBar/CitySearchBar.tsx index ab7b91b29..ace9390d9 100644 --- a/frontend/src/components/common/CitySearchBar/CitySearchBar.tsx +++ b/frontend/src/components/common/CitySearchBar/CitySearchBar.tsx @@ -30,7 +30,7 @@ const CitySearchBar = ({ initialCities, updateCityInfo, required = false }: City const { cityTags, addCityTag, deleteCityTag } = useCityTags(initialCities ?? []); const { isOpen: isSuggestionOpen, open: openSuggestion, close: closeSuggestion } = useOverlay(); const inputRef = useRef(null); - const debouncedQueryWord = useDebounce(queryWord, 300); + const debouncedQueryWord = useDebounce(queryWord, 150); useEffect(() => { updateCityInfo(cityTags); @@ -64,7 +64,7 @@ const CitySearchBar = ({ initialCities, updateCityInfo, required = false }: City }; const handleInputFocus = () => { - if (queryWord) { + if (debouncedQueryWord) { openSuggestion(); } }; @@ -104,7 +104,11 @@ const CitySearchBar = ({ initialCities, updateCityInfo, required = false }: City {isSuggestionOpen && ( - + )} diff --git a/frontend/src/components/common/CitySuggestion/CitySuggestion.tsx b/frontend/src/components/common/CitySuggestion/CitySuggestion.tsx index 11a8caa2d..18371ac66 100644 --- a/frontend/src/components/common/CitySuggestion/CitySuggestion.tsx +++ b/frontend/src/components/common/CitySuggestion/CitySuggestion.tsx @@ -18,45 +18,53 @@ import { interface SuggestionProps { queryWord: string; onItemSelect: (city: CityData) => void; + closeSuggestion: () => void; } -const CitySuggestion = ({ queryWord, onItemSelect }: SuggestionProps) => { +const CitySuggestion = ({ queryWord, onItemSelect, closeSuggestion }: SuggestionProps) => { const { suggestions, focusedSuggestionIndex, isFocused, setNewSuggestions } = useCitySuggestion({ onItemSelect, + closeSuggestion, }); const listRef = useRef(null); const itemRef = useRef(null); const { scrollToFocusedItem } = useAutoScroll(listRef, itemRef); useEffect(() => { + if (!queryWord) return; + setNewSuggestions(queryWord); }, [queryWord]); - const handleItemClick = (city: CityData) => () => { - onItemSelect(city); - }; - useEffect(() => { scrollToFocusedItem(); }, [focusedSuggestionIndex]); + const handleItemClick = (city: CityData) => () => { + onItemSelect(city); + }; + return ( - - {suggestions.length ? ( - suggestions.map((city, index) => ( - - {city.name} - - )) - ) : ( - 검색어에 해당하는 도시가 없습니다. + <> + {queryWord && ( + + {suggestions.length ? ( + suggestions.map((city, index) => ( + + {city.name} + + )) + ) : ( + 검색어에 해당하는 도시가 없습니다. + )} + )} - + ); }; diff --git a/frontend/src/components/common/DateInput/DateInput.tsx b/frontend/src/components/common/DateInput/DateInput.tsx index f341ff3e6..38eaae7bc 100644 --- a/frontend/src/components/common/DateInput/DateInput.tsx +++ b/frontend/src/components/common/DateInput/DateInput.tsx @@ -3,7 +3,7 @@ import type { DateRangeData } from '@type/trips'; import { Box, DateRangePicker, Flex, Input, Label, Menu, useOverlay } from 'hang-log-design-system'; import { useEffect, useState } from 'react'; -import { dateRangeToString } from '@utils/formatter'; +import { formatDateRange } from '@utils/formatter'; import { calendarStyling, @@ -22,7 +22,7 @@ const DateInput = ({ updateDateInfo, required = false, }: DateInputProps) => { - const [inputValue, setInputValue] = useState(dateRangeToString(initialDateRange)); + const [inputValue, setInputValue] = useState(formatDateRange(initialDateRange)); const [selectedDateRange, setSelectedDateRange] = useState(initialDateRange); const { isOpen: isCalendarOpen, close: closeCalendar, toggle: toggleCalendar } = useOverlay(); @@ -34,7 +34,7 @@ const DateInput = ({ if (!dateRange.end) return; setSelectedDateRange(dateRange); - setInputValue(dateRangeToString(dateRange)); + setInputValue(formatDateRange(dateRange)); }; return ( diff --git a/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.style.ts b/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.style.ts index b8180e969..a6dfbd5e7 100644 --- a/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.style.ts +++ b/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.style.ts @@ -11,6 +11,10 @@ export const formStyling = css({ }, }); +export const dateInputSupportingText = css({ + wordBreak: 'keep-all', +}); + export const titleStyling = css({ flexDirection: 'column', width: '400px', diff --git a/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx b/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx index 90ec9736b..96952fa0b 100644 --- a/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx +++ b/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx @@ -15,6 +15,7 @@ import { useTripEditForm } from '@hooks/trip/useTripEditForm'; import CitySearchBar from '@components/common/CitySearchBar/CitySearchBar'; import DateInput from '@components/common/DateInput/DateInput'; import { + dateInputSupportingText, formStyling, textareaStyling, titleStyling, @@ -59,7 +60,7 @@ const TripInfoEditModal = ({ isOpen, onClose, ...information }: TripInfoEditModa /> - + 방문 기간을 단축하면 마지막 날짜부터 작성한 기록들이 삭제됩니다. diff --git a/frontend/src/constants/regex.ts b/frontend/src/constants/regex.ts new file mode 100644 index 000000000..ddd7fddd5 --- /dev/null +++ b/frontend/src/constants/regex.ts @@ -0,0 +1,3 @@ +export const REGEX = { + ONLY_LETTER: '^[A-Za-z가-힣ㄱ-ㅎ]+$', +}; diff --git a/frontend/src/hooks/common/useCitySuggestion.ts b/frontend/src/hooks/common/useCitySuggestion.ts index e65dea27c..d60be65bb 100644 --- a/frontend/src/hooks/common/useCitySuggestion.ts +++ b/frontend/src/hooks/common/useCitySuggestion.ts @@ -3,15 +3,27 @@ import type { CityData } from '@type/city'; import { useEffect, useState } from 'react'; import { makeRegexByCho } from '@utils/cityFilter'; +import { formatStringToLetter } from '@utils/formatter'; -export const useCitySuggestion = ({ onItemSelect }: { onItemSelect: (item: CityData) => void }) => { +interface useCitySuggestionProps { + onItemSelect: (city: CityData) => void; + closeSuggestion: () => void; +} + +export const useCitySuggestion = ({ onItemSelect, closeSuggestion }: useCitySuggestionProps) => { const queryClient = useQueryClient(); const cityData = queryClient.getQueryData(['city']); const [suggestions, setSuggestions] = useState([]); const [focusedSuggestionIndex, setFocusedSuggestionIndex] = useState(-1); + useEffect(() => { + setFocusedSuggestionIndex(-1); + focusOnlySuggestion(); + }, [suggestions]); + const setNewSuggestions = (word: string) => { - const regex = makeRegexByCho(word); + const query = formatStringToLetter(word); + const regex = makeRegexByCho(query); if (cityData) { const filteredSuggestions = cityData.filter(({ name }) => regex.test(name)); @@ -31,6 +43,12 @@ export const useCitySuggestion = ({ onItemSelect }: { onItemSelect: (item: CityD ); }; + const focusOnlySuggestion = () => { + if (suggestions.length === 1) { + setFocusedSuggestionIndex(0); + } + }; + const isFocused = (index: number) => { return index === focusedSuggestionIndex; }; @@ -49,6 +67,10 @@ export const useCitySuggestion = ({ onItemSelect }: { onItemSelect: (item: CityD onItemSelect(suggestions[focusedSuggestionIndex]); } } + + if (e.key === 'Escape') { + closeSuggestion(); + } }; useEffect(() => { diff --git a/frontend/src/utils/formatter.ts b/frontend/src/utils/formatter.ts index c83e74247..6e885242f 100644 --- a/frontend/src/utils/formatter.ts +++ b/frontend/src/utils/formatter.ts @@ -1,3 +1,4 @@ +import { REGEX } from '@constants/regex'; import { DateRangeData } from '@type/trips'; export const formatDate = (date: string) => { @@ -12,10 +13,22 @@ export const formatNumberToMoney = (number: number) => { return number === 0 ? 0 : number.toLocaleString(); }; -export const dateRangeToString = (dateRange: DateRangeData) => { +export const formatDateRange = (dateRange: DateRangeData) => { const { start, end } = dateRange; if (!start || !end) return ''; return `${formatDate(start)} - ${formatDate(end)}`; }; + +export const formatStringToLetter = (string: string) => { + const letterRegex = new RegExp(REGEX.ONLY_LETTER, 'g'); + + const matches = string.match(letterRegex); + + if (!matches) { + return ''; + } + + return matches[0]; +};