diff --git a/__tests__/utils.test.js b/__tests__/utils.test.js index 10ce4e14..2c3b3152 100644 --- a/__tests__/utils.test.js +++ b/__tests__/utils.test.js @@ -1,10 +1,11 @@ -import { parsePackageString } from '../utils/common.utils' +const { parsePackageString, isEmpty } = require('../utils/common.utils') describe('parsePackageString', () => { it('handles scoped packages correctly', () => { expect(parsePackageString('@babel/core@9.8.0')).toEqual({ scoped: true, name: '@babel/core', + scope: 'babel', version: '9.8.0', }) }) @@ -13,6 +14,7 @@ describe('parsePackageString', () => { expect(parsePackageString('@babel/core')).toEqual({ scoped: true, name: '@babel/core', + scope: 'babel', version: null, }) }) @@ -49,3 +51,15 @@ describe('parsePackageString', () => { }) }) }) + +describe('isEmpty', () => { + it('should return true for empty string', () => { + expect(isEmpty('')).toBe(true) + expect(isEmpty(' ')).toBe(true) + }) + + it('should return false for non-empty string', () => { + expect(isEmpty('Hello')).toBe(false) + expect(isEmpty(' Hello ')).toBe(false) + }) +}) diff --git a/client/components/AutocompleteInput/AutocompleteInput.scss b/client/components/AutocompleteInput/AutocompleteInput.scss index 3354db5a..d81c0578 100644 --- a/client/components/AutocompleteInput/AutocompleteInput.scss +++ b/client/components/AutocompleteInput/AutocompleteInput.scss @@ -8,6 +8,34 @@ width: 100%; } +.autocomplete-input__container--error { + animation-duration: 0.75s; + animation-fill-mode: both; + animation-name: shakeX; +} + +@keyframes shakeX { + from, + to { + transform: translate3d(0, 0, 0); + } + + 10%, + 30%, + 50%, + 70%, + 90% { + transform: translate3d(-6px, 0, 0); + } + + 20%, + 40%, + 60%, + 80% { + transform: translate3d(6px, 0, 0); + } +} + .autocomplete-input { width: 40vw; border: none; diff --git a/client/components/AutocompleteInput/AutocompleteInput.tsx b/client/components/AutocompleteInput/AutocompleteInput.tsx index 427bfbcc..b377021b 100644 --- a/client/components/AutocompleteInput/AutocompleteInput.tsx +++ b/client/components/AutocompleteInput/AutocompleteInput.tsx @@ -34,6 +34,8 @@ export const AutocompleteInput = ({ handleInputChange, setIsMenuVisible, setSuggestions, + isValidationError, + errorClearHandler, } = useAutocompleteInput({ initialValue, onSubmit: onSearchSubmit }) const { searchFontSize } = useFontSize({ value }) @@ -51,7 +53,9 @@ export const AutocompleteInput = ({ className={cx('autocomplete-input__container', className, { 'autocomplete-input__container--menu-visible': isMenuVisible && !!suggestions.length, + 'autocomplete-input__container--error': isValidationError, })} + onAnimationEnd={errorClearHandler} > item.package.name} diff --git a/client/components/AutocompleteInput/hooks/useAutocompleteInput.ts b/client/components/AutocompleteInput/hooks/useAutocompleteInput.ts index e6baee2e..147518e1 100644 --- a/client/components/AutocompleteInput/hooks/useAutocompleteInput.ts +++ b/client/components/AutocompleteInput/hooks/useAutocompleteInput.ts @@ -1,7 +1,7 @@ import React from 'react' import debounce from 'debounce' -import { parsePackageString } from '../../../../utils/common.utils' +import { parsePackageString, isEmpty } from '../../../../utils/common.utils' import API from '../../../api' interface UseAutocompleteInputArgs { @@ -16,6 +16,7 @@ export function useAutocompleteInput({ const [value, setValue] = React.useState(initialValue) const [suggestions, setSuggestions] = React.useState([]) const [isMenuVisible, setIsMenuVisible] = React.useState(false) + const [isValidationError, setIsValidationError] = React.useState(false) const getSuggestions = React.useMemo( () => @@ -27,9 +28,17 @@ export function useAutocompleteInput({ [] ) + const errorClearHandler = () => { + setIsValidationError(false) + } + const handleSubmit = (e: React.FormEvent) => { e.preventDefault() - onSubmit(value) + if (!isEmpty(value)) { + onSubmit(value) + } else { + setIsValidationError(true) + } } const handleInputChange = ( @@ -57,5 +66,7 @@ export function useAutocompleteInput({ handleInputChange, setIsMenuVisible, setSuggestions, + isValidationError, + errorClearHandler, } } diff --git a/utils/common.utils.js b/utils/common.utils.js index ddbb7f42..ec6f4d90 100644 --- a/utils/common.utils.js +++ b/utils/common.utils.js @@ -50,4 +50,13 @@ function sanitizeHTML(html) { }) } -module.exports = { parsePackageString, daysFromToday, sanitizeHTML } +/** + * replace it when refactor to TS + * @param {string} str - input string + * @returns boolean + * */ +function isEmpty(str) { + return !str.trim().length +} + +module.exports = { parsePackageString, daysFromToday, sanitizeHTML, isEmpty }