import React, {FunctionComponent, useCallback, useEffect, useState} from 'react'; import {SearchItem, SearchResult} from '~/resource/graphs/types'; import { backgroundColor, backgroundFocusedColor, css, ellipsis, em, primaryColor, rem, sameBorder, size, textLightColor, transitionProps, triangle } from '~/utils/style'; import Field from '~/components/Field'; import SearchInput from '~/components/SearchInput'; import styled from 'styled-components'; import useSearchValue from '~/hooks/useSearchValue'; import {useTranslation} from '~/utils/i18n'; const SearchField = styled(Field)` margin-bottom: ${rem(20)}; display: flex; justify-content: space-between; align-items: center; > :first-child { flex: auto; } > a:last-child { color: ${primaryColor}; cursor: pointer; margin-left: ${rem(10)}; flex: none; } `; const Empty = styled.div` padding: ${rem(100)} 0; text-align: center; color: ${textLightColor}; `; const Wrapper = styled.div` overflow: auto; `; const List = styled.ul` list-style: none; margin: 0; padding: 0; `; const Item = styled.li` padding: ${em(10)} ${em(12)}; cursor: pointer; width: 100%; background-color: ${backgroundColor}; display: flex; align-items: center; ${transitionProps('background-color')} > span { flex: auto; margin-left: ${em(10)}; ${ellipsis()} } &:hover { background-color: ${backgroundFocusedColor}; } `; const icon = css` color: #979797; flex: none; width: ${em(8)}; `; const EdgeIcon = styled.i` ${icon} position: relative; height: ${em(11)}; overflow: hidden; &::before { content: ''; display: block; ${size(em(6), em(1))} background-color: currentColor; position: absolute; top: 0; left: 50%; transform: translateX(-50%); } &::after { content: ''; display: block; ${triangle({ pointingDirection: 'bottom', height: em(5), width: em(7), foregroundColor: 'currentColor' })} position: absolute; top: ${em(6)}; left: 50%; transform: translateX(-50%); } `; const NodeIcon = styled.i` ${icon} height: ${em(7)}; ${sameBorder({radius: em(2), color: 'currentColor'})} background-color: #f7f7f7; `; const InitializerIcon = styled.i` ${icon} height: ${em(8)}; ${sameBorder({radius: em(4), color: 'currentColor'})} background-color: #f7f7f7; `; const icons = { input: EdgeIcon, output: EdgeIcon, node: NodeIcon, initializer: InitializerIcon } as const; type SearchProps = { text?: string; data: SearchResult; onChange?: (value: string) => unknown; onSelect?: (item: SearchItem) => unknown; onActive?: () => unknown; onDeactive?: () => unknown; }; const Search: FunctionComponent = ({text, data, onChange, onSelect, onActive, onDeactive}) => { const {t} = useTranslation(['graphs', 'common']); const [search, setSearch] = useState(text ?? ''); const [searching, setSearching] = useState(false); const [searchResult, setSearchResult] = useState(data.result); const debouncedSearchText = useSearchValue(search); useEffect(() => setSearch(text ?? ''), [text]); useEffect(() => { if (searching) { onChange?.(debouncedSearchText); } else { setSearchResult([]); } }, [debouncedSearchText, searching, onChange]); useEffect(() => { if (data.text === search) { setSearchResult(data.result); } }, [data, search]); const focus = useCallback(() => { setSearching(true); onActive?.(); }, [onActive]); const cancel = useCallback(() => { setSearch(''); onChange?.(''); setSearching(false); onDeactive?.(); }, [onChange, onDeactive]); const select = useCallback( (item: SearchItem) => { setSearch(item.name); onSelect?.(item); setSearching(false); onDeactive?.(); }, [onSelect, onDeactive] ); return ( <> {searching && {t('common:cancel')}} {searching && (searchResult.length ? ( {searchResult.map(item => { const Icon = icons[item.type]; return ( select(item)} title={item.name}> {item.name} ); })} ) : ( {t('graphs:nothing-matched')} ))} ); }; export default Search;