react-icons/fi#FiSearch TypeScript Examples
The following examples show how to use
react-icons/fi#FiSearch.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: InputSearch.tsx From webapis-playground with MIT License | 6 votes |
InputSearch = React.forwardRef<HTMLInputElement, InputSearchProps>(
({ value, onClear, clearLabel = 'Clear', ...props }, ref) => {
return (
<Input
type="text"
renderPrefix={() => <FiSearch />}
renderSuffix={renderProps =>
value && onClear && clearLabel ? (
<IconButton
icon={<GrFormClose />}
ariaLabel={clearLabel}
onClick={onClear}
{...renderProps}
/>
) : null
}
value={value}
{...props}
ref={ref}
/>
);
}
)
Example #2
Source File: index.tsx From rocketredis with MIT License | 6 votes |
SearchInput: React.FC<SearchInputProps> = ({ ...rest }) => {
const [isFocused, setIsFocused] = useState(false)
const { t } = useTranslation('keyList')
const handleInputFocus = useCallback(() => {
setIsFocused(true)
}, [])
const handleInputBlur = useCallback(() => {
setIsFocused(false)
}, [])
return (
<Container isFocused={isFocused} onFocus={handleInputFocus}>
<FiSearch />
<input placeholder={t('search')} {...rest} onBlur={handleInputBlur} />
</Container>
)
}
Example #3
Source File: SearchInput.tsx From ksana.in with Apache License 2.0 | 6 votes |
export function SearchInput({ searchText, onChangeSearch }: ISearchInputProps) {
return (
<Box width={{ base: '100%' }}>
<Stack spacing={4}>
<InputGroup>
<Input
size="lg"
borderWidth="2px"
borderColor="orange.400"
name="searchText"
placeholder="Cari tautan kamu"
variant="filled"
value={searchText}
onChange={onChangeSearch}
/>
<InputRightElement
fontSize="2em"
color="orange.400"
mr="2"
mt="1"
children={<FiSearch />}
/>
</InputGroup>
</Stack>
</Box>
)
}
Example #4
Source File: Networks.tsx From Meshtastic with GNU General Public License v3.0 | 6 votes |
Networks = (): JSX.Element => {
const { siteConfig } = useDocusaurusContext();
const { data, error } = useSWR<Showcase[]>(
`${siteConfig.customFields.API_URL}/showcase`,
fetcher,
);
const selectedTags = useSelectedTags();
const filteredNetworks = useFilteredNetworks(data ?? []);
return (
<section className="margin-top--lg margin-bottom--xl">
{!error ? (
selectedTags.length === 0 ? (
<>
<NetworkSection
title="Our favorites"
icon={<FiHeart />}
iconColor="rgb(190 24 93)"
networks={data?.filter((network) =>
network.tags.find((tag) => tag.label === 'Favorite'),
)}
/>
<NetworkSection title="All networks" networks={data} />
</>
) : (
<NetworkSection
title="Results"
icon={<FiSearch />}
networks={filteredNetworks}
/>
)
) : (
<div>{JSON.stringify(error)}</div>
)}
</section>
);
}
Example #5
Source File: index.tsx From dxvote with GNU Affero General Public License v3.0 | 5 votes |
TokenPicker: React.FC<TokenPickerProps> = ({
walletAddress,
isOpen,
onSelect,
onClose,
}) => {
const [searchQuery, setSearchQuery] = useState('');
const { account } = useWeb3React();
const { data } = useAllERC20Balances(walletAddress || account);
const { instance, buildIndex, query } =
useMiniSearch<TokenWithBalanceIndexable>({
fields: ['name', 'symbol', 'address'],
storeFields: ['address'],
searchOptions: {
fuzzy: 2,
prefix: true,
},
});
const tokens = useMemo(() => {
return data.map(token => {
return {
...token,
id: token?.address,
};
});
}, [data]);
useEffect(() => {
if (instance?.documentCount !== tokens?.length) {
buildIndex(tokens);
}
}, [buildIndex, tokens, instance]);
const searchResults = useMemo(() => {
if (!searchQuery) return [];
return query({ queries: [searchQuery] });
}, [searchQuery, query]);
return (
<Modal
header={'Select a token'}
isOpen={isOpen}
onDismiss={onClose}
maxWidth={390}
>
<TokenPickerContainer>
<SearchWrapper>
<Input
icon={<FiSearch />}
placeholder="Search token"
value={searchQuery}
onChange={e => setSearchQuery(e?.target?.value)}
/>
</SearchWrapper>
<TokenList>
{(searchQuery ? searchResults : data)?.slice(0, 4).map(token => (
<Token
key={token.address}
token={token}
onSelect={() => onSelect(token.address)}
/>
))}
</TokenList>
</TokenPickerContainer>
</Modal>
);
}
Example #6
Source File: index.tsx From front-entenda-direito with GNU General Public License v3.0 | 5 votes |
Search: React.FC = () => {
const formRef = useRef<FormHandles>(null);
const { addToast, setDictionary, getDictionary } = useToast();
const handleSubmit = useCallback(
async (data: string) => {
try {
formRef.current?.setErrors({});
const schema = Yup.object().shape({
legalTerm: Yup.string().required('Campo obrigatório'),
});
await schema.validate(data, { abortEarly: false });
setDictionary();
} catch (err) {
if (err instanceof Yup.ValidationError) {
const errors = getValidationErrors(err);
formRef.current?.setErrors(errors);
return;
}
addToast({
type: 'error',
title: 'Erro no cadastro ?',
description: 'Ocorreu um erro ao fazer cadastro, tente novamente.',
});
}
},
[addToast, setDictionary],
);
return (
<>
<strong>Dicionário Jurídico</strong>
<FormStyled ref={formRef} onSubmit={handleSubmit}>
<Input
name="legalTerm"
icon={FiBookOpen}
maxLength={20}
placeholder="Digite: Carta"
/>
<p>
Digite o termo e clique no botão <FiSearch />.
</p>
<Button type="submit" style={{ width: 200 }}>
<FiSearch />
</Button>
</FormStyled>
<Section>{getDictionary() && <LegalTerm />}</Section>
</>
);
}
Example #7
Source File: SearchBar.tsx From hub with Apache License 2.0 | 4 votes |
SearchBar = (props: Props) => {
const history = useHistory();
const dropdownRef = useRef(null);
const [value, setValue] = useState(props.tsQueryWeb || '');
const inputEl = useRef<HTMLInputElement>(null);
const [packages, setPackages] = useState<Package[] | null>(null);
const [totalPackagesNumber, setTotalPackagesNumber] = useState<number | null>(null);
const [visibleDropdown, setVisibleDropdown] = useState<boolean>(false);
const point = useBreakpointDetect();
const [highlightedItem, setHighlightedItem] = useState<number | null>(null);
const [dropdownTimeout, setDropdownTimeout] = useState<NodeJS.Timeout | null>(null);
useOutsideClick([dropdownRef], visibleDropdown, () => {
cleanSearch();
});
const onChange = (e: ChangeEvent<HTMLInputElement>): void => {
setValue(e.target.value);
};
const cleanSearchBox = (): void => {
setValue('');
forceFocus();
};
const forceFocus = (): void => {
if (!isNull(inputEl) && !isNull(inputEl.current)) {
inputEl.current.focus();
}
};
const forceBlur = (): void => {
if (!isNull(inputEl) && !isNull(inputEl.current)) {
inputEl.current.blur();
}
};
const goToSearch = () => {
cleanTimeout();
forceBlur();
cleanSearch();
history.push({
pathname: '/packages/search',
search: prepareQueryString({
pageNumber: 1,
tsQueryWeb: value || undefined,
}),
});
};
const cleanTimeout = () => {
if (!isNull(dropdownTimeout)) {
clearTimeout(dropdownTimeout);
setDropdownTimeout(null);
}
};
const cleanSearch = () => {
setPackages(null);
setTotalPackagesNumber(null);
setVisibleDropdown(false);
setHighlightedItem(null);
};
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
switch (e.key) {
case 'Escape':
cleanSearch();
return;
case 'ArrowDown':
updateHighlightedItem('down');
return;
case 'ArrowUp':
updateHighlightedItem('up');
return;
case 'Enter':
e.preventDefault();
if (!isNull(packages) && !isNull(highlightedItem)) {
if (highlightedItem === packages.length) {
goToSearch();
} else {
const selectedPackage = packages[highlightedItem];
if (selectedPackage) {
goToPackage(selectedPackage);
}
}
} else {
goToSearch();
}
return;
default:
return;
}
};
const updateHighlightedItem = (arrow: 'up' | 'down') => {
if (!isNull(packages) && visibleDropdown) {
if (!isNull(highlightedItem)) {
let newIndex: number = arrow === 'up' ? highlightedItem - 1 : highlightedItem + 1;
if (newIndex > packages.length) {
newIndex = 0;
}
if (newIndex < 0) {
newIndex = packages.length;
}
setHighlightedItem(newIndex);
} else {
if (packages && packages.length > 0) {
const newIndex = arrow === 'up' ? packages.length : 0; // We don't subtract 1 because See all results (x) has to be count
setHighlightedItem(newIndex);
}
}
}
};
const goToPackage = (selectedPackage: Package) => {
forceBlur();
setValue('');
cleanSearch();
history.push({
pathname: buildPackageURL(selectedPackage.normalizedName, selectedPackage.repository, selectedPackage.version!),
});
};
useEffect(() => {
setValue(props.tsQueryWeb || '');
}, [props.tsQueryWeb]);
async function searchPackages() {
try {
const searchResults = await API.searchPackages(
{
tsQueryWeb: value,
filters: {},
limit: 5,
offset: 0,
},
false
);
const total = parseInt(searchResults.paginationTotalCount);
if (total > 0) {
const isInputFocused = inputEl.current === document.activeElement;
// We have to be sure that input has focus to display results
if (isInputFocused) {
setPackages(searchResults.packages);
setTotalPackagesNumber(total);
setVisibleDropdown(true);
} else {
cleanSearch();
}
} else {
cleanSearch();
}
} catch (err: any) {
cleanSearch();
}
}
useEffect(() => {
// Don't display search options for mobile devices
if (point !== 'xs') {
const isInputFocused = inputEl.current === document.activeElement;
if (value.length >= MIN_CHARACTERS_SEARCH && isInputFocused) {
cleanTimeout();
setDropdownTimeout(
setTimeout(() => {
setHighlightedItem(null);
searchPackages();
}, SEARCH_DELAY)
);
} else {
cleanSearch();
}
}
return () => {
if (!isNull(dropdownTimeout)) {
clearTimeout(dropdownTimeout);
}
};
}, [value]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<>
<div className={`position-relative ${props.formClassName}`}>
<div
className={`d-flex align-items-center overflow-hidden searchBar lh-base bg-white ${styles.searchBar} ${
styles[props.size]
}`}
role="combobox"
aria-haspopup="listbox"
aria-owns="search-list"
aria-expanded={visibleDropdown && !isNull(packages)}
aria-controls="search-list"
>
<div
data-testid="searchBarIcon"
className={`d-flex align-items-center ${styles.iconWrapper}`}
onClick={forceFocus}
>
<FiSearch />
</div>
<input
ref={inputEl}
className={`flex-grow-1 ps-2 ps-md-0 border-0 shadow-none bg-transparent ${styles.input}`}
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="none"
spellCheck="false"
placeholder="Search packages"
aria-label="Search packages"
value={value}
onChange={onChange}
onKeyDown={onKeyDown}
disabled={props.isSearching}
/>
<div className="d-none" tabIndex={0}>
<div aria-live="polite">{!isNull(packages) ? `${packages.length} results found` : ''}</div>
</div>
{props.isSearching && (
<div
className={classnames('position-absolute text-secondary', styles.loading, {
[styles.bigLoading]: props.size === 'big',
})}
>
<span data-testid="searchBarSpinning" className="spinner-border spinner-border-sm" />
</div>
)}
<button
type="button"
className={classnames('btn-close lh-lg ps-2 pe-3', styles.inputClean, {
invisible: value === '' || props.isSearching,
})}
onClick={cleanSearchBox}
aria-label="Close"
></button>
<div
className={classnames('position-absolute text-dark', styles.tipIcon, {
[styles.bigTipIcon]: props.size === 'big',
})}
>
<button
onClick={() => props.setOpenTips(true)}
className={classnames('btn btn-link p-2 text-light', {
'btn-lg': props.size === 'big',
})}
aria-label="Open search tips modal"
>
<FaRegQuestionCircle />
</button>
</div>
</div>
{visibleDropdown && !isNull(packages) && (
<div
ref={dropdownRef}
className={`dropdown-menu dropdown-menu-left p-0 shadow-sm w-100 show noFocus ${styles.dropdown}`}
role="listbox"
id="search-list"
aria-activedescendant={highlightedItem ? `sl-opt${highlightedItem}` : ''}
tabIndex={0}
aria-roledescription="Packages list"
>
<HoverableItem onLeave={() => setHighlightedItem(null)}>
<>
{packages.map((pkg: Package, index: number) => {
return (
<HoverableItem
key={`pkg_${pkg.packageId}`}
onHover={() => setHighlightedItem(index)}
onLeave={() => setHighlightedItem(null)}
>
<button
type="button"
className={classnames(
'btn btn-link w-100 border-bottom rounded-0 d-flex flex-row align-items-stretch text-decoration-none text-dark p-3',
{ [styles.activeDropdownItem]: index === highlightedItem }
)}
onClick={() => {
goToPackage(pkg);
}}
aria-label={`Open package ${pkg.displayName || pkg.name} detail`}
role="option"
aria-selected={index === highlightedItem}
id={`sl-opt${index}`}
>
<div
className={`d-none d-md-flex align-items-center justify-content-center overflow-hidden rounded-circle p-1 border border-2 bg-white position-relative ${styles.imageWrapper} imageWrapper`}
>
<Image
imageId={pkg.logoImageId}
alt={`Logo ${pkg.displayName || pkg.name}`}
className={styles.image}
kind={pkg.repository.kind}
/>
</div>
<div className={`ms-0 ms-md-3 flex-grow-1 ${styles.truncateWrapper}`}>
<div className="d-flex flex-row align-items-center">
<div className={`text-truncate fw-bold ${styles.title}`}>{pkg.displayName || pkg.name}</div>
<div
className={`align-self-start d-flex align-items-center text-uppercase ms-auto ps-2 ${styles.midText}`}
>
<StarBadge className="me-1" starsNumber={pkg.stars} />
<RepositoryIconLabel kind={pkg.repository.kind} iconClassName={styles.kindIcon} />
</div>
</div>
<div className="d-flex flex-row align-items-center mt-2">
<div className={`text-truncate ${styles.smallText}`}>
<small className="text-muted text-uppercase">
{pkg.repository.userAlias ? 'User' : 'Org'}:
</small>
<span className="ms-1 me-2">
{pkg.repository.userAlias ||
pkg.repository.organizationDisplayName ||
pkg.repository.organizationName}
</span>
<small className="text-muted text-uppercase">Repo:</small>
<span className="text-truncate ms-1">{pkg.repository.name}</span>
</div>
<div className="ms-auto d-flex flex-nowrap ps-2">
<OfficialBadge
official={isPackageOfficial(pkg)}
className="d-inline"
type="package"
withoutTooltip
/>
</div>
</div>
</div>
</button>
</HoverableItem>
);
})}
<HoverableItem
onHover={() => setHighlightedItem(packages.length)}
onLeave={() => setHighlightedItem(null)}
>
<button
type="button"
className={classnames('btn btn-link w-100 text-dark p-2', styles.dropdownItem, {
[styles.activeDropdownItem]: packages.length === highlightedItem,
})}
onClick={goToSearch}
aria-label="See all results"
role="option"
aria-selected={packages.length === highlightedItem}
id={`sl-opt${packages.length}`}
>
See all results ({totalPackagesNumber})
</button>
</HoverableItem>
</>
</HoverableItem>
</div>
)}
</div>
</>
);
}
Example #8
Source File: SearchPackages.tsx From hub with Apache License 2.0 | 4 votes |
SearchPackages = (props: Props) => {
const inputEl = useRef<HTMLInputElement>(null);
const dropdownRef = useRef(null);
const itemsWrapper = useRef<HTMLDivElement | null>(null);
const [isSearching, setIsSearching] = useState(false);
const [packages, setPackages] = useState<Package[] | null>(null);
const [searchQuery, setSearchQuery] = useState<string>('');
const [dropdownTimeout, setDropdownTimeout] = useState<NodeJS.Timeout | null>(null);
const [highlightedItem, setHighlightedItem] = useState<number | null>(null);
useOutsideClick([dropdownRef], !isNull(packages), () => setPackages(null));
async function searchPackages(tsQueryWeb: string) {
try {
setIsSearching(true);
const searchResults = await API.searchPackages(
{
tsQueryWeb: tsQueryWeb,
filters: {},
limit: DEFAULT_LIMIT,
offset: 0,
},
false
);
setPackages(searchResults.packages);
setIsSearching(false);
} catch (err: any) {
setPackages(null);
alertDispatcher.postAlert({
type: 'danger',
message: 'An error occurred searching packages, please try again later.',
});
setIsSearching(false);
}
}
const handleOnKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
switch (e.key) {
case 'Escape':
cleanSearch();
return;
case 'ArrowDown':
updateHighlightedItem('down');
return;
case 'ArrowUp':
updateHighlightedItem('up');
return;
case 'Enter':
e.preventDefault();
if (!isNull(packages) && !isNull(highlightedItem)) {
const selectedPkg = packages[highlightedItem];
if (selectedPkg && !props.disabledPackages.includes(selectedPkg.packageId)) {
saveSelectedPackage(selectedPkg);
}
}
return;
default:
return;
}
};
const updateHighlightedItem = (arrow: 'up' | 'down') => {
if (!isNull(packages) && packages.length > 0) {
if (!isNull(highlightedItem)) {
let newIndex: number = arrow === 'up' ? highlightedItem - 1 : highlightedItem + 1;
if (newIndex > packages.length - 1) {
newIndex = 0;
}
if (newIndex < 0) {
newIndex = packages.length - 1;
}
scrollDropdown(newIndex);
setHighlightedItem(newIndex);
} else {
const newIndex = arrow === 'up' ? packages.length - 1 : 0;
scrollDropdown(newIndex);
setHighlightedItem(newIndex);
}
}
};
const saveSelectedPackage = (item: Package): void => {
setPackages(null);
setSearchQuery('');
inputEl.current!.value = '';
props.onSelection(item);
setHighlightedItem(null);
};
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value);
if (packages) {
setPackages(null);
setHighlightedItem(null);
}
};
const cleanTimeout = () => {
if (!isNull(dropdownTimeout)) {
clearTimeout(dropdownTimeout);
setDropdownTimeout(null);
}
};
const cleanSearch = () => {
setPackages(null);
setSearchQuery('');
};
const scrollDropdown = (index: number) => {
if (itemsWrapper && itemsWrapper.current) {
const itemsOnScreen = Math.floor(itemsWrapper.current.clientHeight / ITEM_HEIGHT) - 1;
if (index + 1 > itemsOnScreen) {
itemsWrapper.current.scroll(0, (index - itemsOnScreen) * ITEM_HEIGHT);
} else {
itemsWrapper.current.scroll(0, 0);
}
}
};
const forceFocus = (): void => {
if (!isNull(inputEl) && !isNull(inputEl.current)) {
inputEl.current.focus();
}
};
useEffect(() => {
const isInputFocused = inputEl.current === document.activeElement;
if (searchQuery.length >= MIN_CHARACTERS_SEARCH && isInputFocused) {
cleanTimeout();
setDropdownTimeout(
setTimeout(() => {
searchPackages(searchQuery);
}, SEARCH_DELAY)
);
} else {
cleanSearch();
}
return () => {
if (!isNull(dropdownTimeout)) {
clearTimeout(dropdownTimeout);
}
};
}, [searchQuery]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<div className="position-relative">
<div className="d-flex flex-row">
<div
className={`flex-grow-1 d-flex align-items-stretch overflow-hidden position-relative lh-base bg-white searchBar ${styles.inputWrapper}`}
>
<div
data-testid="searchPkgIcon"
className={`d-flex align-items-center ${styles.iconWrapper}`}
onClick={forceFocus}
>
<FiSearch />
</div>
<input
ref={inputEl}
type="text"
className={`flex-grow-1 pe-4 ps-2 ps-md-0 border-0 shadow-none bg-transparent ${styles.input}`}
name="searchInput"
autoComplete="new-input"
onKeyDown={handleOnKeyDown}
onChange={onChange}
spellCheck="false"
aria-label="Search packages"
/>
{isSearching && (
<div className={`position-absolute text-secondary ${styles.loading}`}>
<span data-testid="searchBarSpinning" className="spinner-border spinner-border-sm" />
</div>
)}
</div>
</div>
{!isNull(packages) && (
<div ref={dropdownRef} className={`dropdown-menu w-100 p-0 shadow-sm show overflow-hidden ${styles.dropdown}`}>
{packages.length === 0 ? (
<p className="m-3 text-center">
We can't seem to find any packages that match your search for{' '}
<span className="fw-bold">{searchQuery}</span>
</p>
) : (
<div className={`overflow-scroll ${styles.tableWrapper}`} ref={itemsWrapper}>
<table
className={`table table-hover table-sm mb-0 tex-break ${styles.table}`}
role="grid"
aria-labelledby={props.label}
>
<thead>
<tr>
<th scope="col" className={`${styles.fitCell} d-none d-sm-table-cell`}></th>
<th scope="col" className="w-50">
Package
</th>
<th scope="col" className="w-50">
Publisher
</th>
</tr>
</thead>
<tbody>
{packages.map((item: Package, index: number) => {
const isDisabled = props.disabledPackages.includes(item.packageId);
return (
<tr
data-testid="packageItem"
role="button"
className={classnames(
{ [styles.clickableCell]: !isDisabled },
{ [styles.disabledCell]: isDisabled },
{ [styles.activeCell]: index === highlightedItem }
)}
onClick={() => {
if (!isDisabled) {
saveSelectedPackage(item);
}
}}
key={`search_${item.packageId}`}
onMouseOver={() => setHighlightedItem(index)}
onMouseOut={() => setHighlightedItem(null)}
>
<td className="align-middle text-center d-none d-sm-table-cell">
<RepositoryIcon kind={item.repository.kind} className={`mx-2 w-auto ${styles.icon}`} />
</td>
<td className="align-middle">
<div className="d-flex flex-row align-items-center">
<div
className={`d-none d-sm-flex align-items-center justify-content-center overflow-hidden p-1 border border-2 bg-white rounded-circle ${styles.imageWrapper} imageWrapper`}
>
<Image
imageId={item.logoImageId}
alt={`Logo ${item.displayName || item.name}`}
className={`fs-4 ${styles.image}`}
kind={item.repository.kind}
/>
</div>
<div className="text-dark ms-2">{item.displayName || item.name}</div>
</div>
</td>
<td className="align-middle">
<div className="text-dark">
{item.repository.userAlias ||
item.repository.organizationDisplayName ||
item.repository.organizationName}
<small className="ms-2 d-none d-sm-inline">
(
<small className={`text-uppercase d-none d-md-inline text-muted ${styles.legend}`}>
Repo:{' '}
</small>
{item.repository.displayName || item.repository.name})
</small>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
)}
</div>
);
}
Example #9
Source File: SearchRepositories.tsx From hub with Apache License 2.0 | 4 votes |
SearchRepositories = (props: Props) => {
const inputEl = useRef<HTMLInputElement>(null);
const dropdownRef = useRef(null);
const itemsWrapper = useRef<HTMLDivElement | null>(null);
const [isSearching, setIsSearching] = useState(false);
const [repositories, setRepositories] = useState<Repository[] | null>(null);
const [searchName, setSearchName] = useState<string>('');
const [dropdownTimeout, setDropdownTimeout] = useState<NodeJS.Timeout | null>(null);
const [highlightedItem, setHighlightedItem] = useState<number | null>(null);
useOutsideClick([dropdownRef], !isNull(repositories), () => cleanSearch());
async function searchRepositories() {
try {
setIsSearching(true);
let query: SearchQuery = {
name: searchName,
limit: DEFAULT_LIMIT,
offset: 0,
};
if (props.extraQueryParams) {
query = { ...query, filters: props.extraQueryParams };
}
const data = await API.searchRepositories(query);
setRepositories(data.items);
setIsSearching(false);
} catch (err: any) {
if (err.kind !== ErrorKind.Unauthorized) {
alertDispatcher.postAlert({
type: 'danger',
message: 'An error occurred searching repositories, please try again later.',
});
} else {
props.onAuthError();
}
setRepositories(null);
setIsSearching(false);
}
}
const saveSelectedRepository = (item: Repository): void => {
setRepositories(null);
setSearchName('');
inputEl.current!.value = '';
props.onSelection(item);
setHighlightedItem(null);
};
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchName(e.target.value);
setHighlightedItem(null);
};
const checkIfRepoIsDisabled = (item: Repository): boolean => {
let isDisabled = false;
if (!isUndefined(props.disabledRepositories)) {
isDisabled =
(!isUndefined(props.disabledRepositories.ids) && props.disabledRepositories.ids.includes(item.repositoryId!)) ||
(!isUndefined(props.disabledRepositories.users) &&
!isNull(item.userAlias) &&
!isUndefined(item.userAlias) &&
props.disabledRepositories.users.includes(item.userAlias)) ||
(!isUndefined(props.disabledRepositories.organizations) &&
!isNull(item.organizationName) &&
!isUndefined(item.organizationName) &&
props.disabledRepositories.organizations.includes(item.organizationName));
}
return isDisabled;
};
const onKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
switch (e.key) {
case 'Escape':
cleanSearch();
return;
case 'ArrowDown':
updateHighlightedItem('down');
return;
case 'ArrowUp':
updateHighlightedItem('up');
return;
case 'Enter':
e.preventDefault();
if (!isNull(repositories) && !isNull(highlightedItem)) {
const selectedRepo = repositories[highlightedItem];
if (selectedRepo && !checkIfRepoIsDisabled(selectedRepo)) {
saveSelectedRepository(selectedRepo);
}
}
return;
default:
return;
}
};
const updateHighlightedItem = (arrow: 'up' | 'down') => {
if (!isNull(repositories) && repositories.length > 0) {
if (!isNull(highlightedItem)) {
let newIndex: number = arrow === 'up' ? highlightedItem - 1 : highlightedItem + 1;
if (newIndex > repositories.length - 1) {
newIndex = 0;
}
if (newIndex < 0) {
newIndex = repositories.length - 1;
}
scrollDropdown(newIndex);
setHighlightedItem(newIndex);
} else {
const newIndex = arrow === 'up' ? repositories.length - 1 : 0;
scrollDropdown(newIndex);
setHighlightedItem(newIndex);
}
}
};
const forceFocus = (): void => {
if (!isNull(inputEl) && !isNull(inputEl.current)) {
inputEl.current.focus();
}
};
const cleanTimeout = () => {
if (!isNull(dropdownTimeout)) {
clearTimeout(dropdownTimeout);
setDropdownTimeout(null);
}
};
const cleanSearch = () => {
setRepositories(null);
setSearchName('');
setHighlightedItem(null);
};
const scrollDropdown = (index: number) => {
if (itemsWrapper && itemsWrapper.current) {
const itemsOnScreen = Math.floor(itemsWrapper.current.clientHeight / ITEM_HEIGHT) - 1;
if (index + 1 > itemsOnScreen) {
itemsWrapper.current.scroll(0, (index - itemsOnScreen) * ITEM_HEIGHT);
} else {
itemsWrapper.current.scroll(0, 0);
}
}
};
useEffect(() => {
const isInputFocused = inputEl.current === document.activeElement;
if (searchName.length >= MIN_CHARACTERS_SEARCH && isInputFocused) {
cleanTimeout();
setDropdownTimeout(
setTimeout(() => {
searchRepositories();
}, SEARCH_DELAY)
);
} else {
cleanSearch();
}
return () => {
if (!isNull(dropdownTimeout)) {
clearTimeout(dropdownTimeout);
}
};
}, [searchName]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<div className="position-relative">
<div className="d-flex flex-row">
<div
className={`flex-grow-1 d-flex align-items-stretch overflow-hidden position-relative searchBar lh-base bg-white ${styles.inputWrapper}`}
>
<div
data-testid="searchBarIcon"
className={`d-flex align-items-center ${styles.iconWrapper}`}
onClick={forceFocus}
>
<FiSearch />
</div>
<input
ref={inputEl}
type="text"
className={`flex-grow-1 pe-4 ps-2 ps-md-0 border-0 shadow-none bg-transparent ${styles.input}`}
name="searchRepositoriesInput"
aria-label="Search repositories"
autoComplete="new-input"
onChange={onChange}
onKeyDown={onKeyDown}
spellCheck="false"
/>
{isSearching && (
<div className={`position-absolute text-secondary ${styles.loading}`}>
<span data-testid="searchBarSpinning" className="spinner-border spinner-border-sm" />
</div>
)}
</div>
</div>
{!isNull(repositories) && (
<div ref={dropdownRef} className={`dropdown-menu w-100 p-0 shadow-sm show overflow-hidden ${styles.dropdown}`}>
{repositories.length === 0 ? (
<p className="m-3 text-center">
We can't seem to find any repositories that match your search for{' '}
<span className="fw-bold">{searchName}</span>
</p>
) : (
<div className={`overflow-scroll ${styles.tableWrapper}`} ref={itemsWrapper}>
<table
className={`table table-hover table-sm mb-0 text-break ${styles.table}`}
role="grid"
aria-labelledby={props.label}
>
<thead>
<tr>
<th scope="col" className={`${styles.fitCell} d-none d-sm-table-cell`}></th>
<th scope="col" className={styles.repoCell}>
Repository
</th>
{props.visibleUrl && (
<th scope="col" className="d-none d-md-table-cell">
Url
</th>
)}
<th scope="col">Publisher</th>
</tr>
</thead>
<tbody>
{repositories.map((item: Repository, index: number) => {
const isDisabled = checkIfRepoIsDisabled(item);
return (
<tr
data-testid="repoItem"
role="button"
className={classnames(
{ [styles.clickableCell]: !isDisabled },
{ [styles.disabledCell]: isDisabled },
{ [styles.activeCell]: index === highlightedItem }
)}
onClick={() => {
if (!isDisabled) {
saveSelectedRepository(item);
}
}}
key={`repo_${item.name!}`}
onMouseOver={() => setHighlightedItem(index)}
onMouseOut={() => setHighlightedItem(null)}
>
<td className="align-middle text-center d-none d-sm-table-cell">
<div className="mx-2">
<RepositoryIcon kind={item.kind} className={`w-auto ${styles.icon}`} />
</div>
</td>
<td className="align-middle">
<div className={styles.truncateWrapper}>
<div className="text-truncate">
{searchName === '' ? (
<>{item.name}</>
) : (
<>
{regexifyString({
pattern: new RegExp(escapeRegExp(searchName), 'gi'),
decorator: (match: string, index: number) => {
return (
<span key={`match_${item.name}_${index}`} className="fw-bold highlighted">
{match}
</span>
);
},
input: item.name,
})}
</>
)}
</div>
</div>
</td>
{props.visibleUrl && (
<td className="align-middle d-none d-md-table-cell">
<div className={styles.truncateWrapper}>
<div className="text-truncate">
<small>{item.url}</small>
</div>
</div>
</td>
)}
<td className="align-middle">
<div className="text-dark d-flex flex-row align-items-center">
<span className={`me-1 ${styles.tinyIcon}`}>
{item.userAlias ? <FaUser /> : <MdBusiness />}
</span>
{item.userAlias || item.organizationDisplayName || item.organizationName}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
)}
</div>
);
}
Example #10
Source File: index.tsx From react-pdv with MIT License | 4 votes |
Dashboard: React.FC = () => {
const [products, setProducts] = useState<Product[]>([]);
const dispatch = useDispatch();
useEffect(() => {
async function loadProducts() {
const response = await api.get<Product[]>(
'/products',
);
const data = response.data.map((product) => ({
...product,
priceFormatted: formatPrice(product.price),
}));
setProducts(data);
}
loadProducts();
}, []);
function handleAddProduct(product: Product) {
dispatch({ type: 'ADD_TO_CART', payload: product });
}
return (
<Layout>
<s.Header>
<s.Title>Painel</s.Title>
<s.Info>
<span className="cashier">
CAIXA ABERTO
</span>
<span className="date">
Sexta, 10 julho 2020
</span>
</s.Info>
</s.Header>
<s.CardContainer>
<s.Card>
<header>
<p>Dinheiro</p>
<FiDollarSign size="24px" color="green" />
</header>
<section>
<p>R$</p>
<h1>0,00</h1>
</section>
</s.Card>
<s.Card>
<header>
<p>Cartão</p>
<FiCreditCard size="24px" color="orange" />
</header>
<section>
<p>R$</p>
<h1>0,00</h1>
</section>
</s.Card>
<s.Card>
<header>
<p>Caixa</p>
<FiHardDrive size="24px" color="grey" />
</header>
<section>
<p>R$</p>
<h1>0,00</h1>
</section>
</s.Card>
</s.CardContainer>
<s.Search>
<FiSearch size="24px" color="grey" />
<s.SearchInput placeholder="Consultar Material" />
</s.Search>
<s.CategoryContainer>
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={190}
totalSlides={6}
visibleSlides={5}
infinite
>
<Slider>
<Slide index={0}>
<s.CategoryItem>
<header>
<p>Delicatessen</p>
<img src={delicatessen} alt="" />
</header>
</s.CategoryItem>
</Slide>
<Slide index={1}>
<s.CategoryItem>
<header>
<p>Frios</p>
<img src={frios} alt="" />
</header>
</s.CategoryItem>
</Slide>
<Slide index={2}>
<s.CategoryItem>
<header>
<p>Salgados</p>
<img src={salgados} alt="" />
</header>
</s.CategoryItem>
</Slide>
<Slide index={3}>
<s.CategoryItem>
<header>
<p>Bebidas</p>
<img src={bebidas} alt="" />
</header>
</s.CategoryItem>
</Slide>
<Slide index={4}>
<s.CategoryItem>
<header>
<p>Sorvetes</p>
<img src={sorvetes} alt="" />
</header>
</s.CategoryItem>
</Slide>
<Slide index={5}>
<s.CategoryItem>
<header>
<p>Sanduíches</p>
<img src={sanduiche} alt="" />
</header>
</s.CategoryItem>
</Slide>
</Slider>
<ButtonBack className="buttonBack"><FiChevronLeft size="24px" color="grey" /></ButtonBack>
<ButtonNext className="buttonNext"><FiChevronRight size="24px" color="grey" /></ButtonNext>
</CarouselProvider>
</s.CategoryContainer>
<ProductList>
{products.map((product) => (
<li key={product.id}>
<img src={product.image} alt={product.title} />
<span>
<strong>{product.title}</strong>
<p>{product.priceFormatted}</p>
</span>
<button type="button" onClick={() => handleAddProduct(product)}>
<FiPlusCircle size="24px" color="grey" />
</button>
</li>
))}
</ProductList>
</Layout>
);
}