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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
  );
}