react-icons/io#IoMdCloseCircleOutline TypeScript Examples

The following examples show how to use react-icons/io#IoMdCloseCircleOutline. 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: Snackbar.tsx    From crosshare with GNU Affero General Public License v3.0 5 votes vote down vote up
function Toast({ id, message }: { id: number; message: string }) {
  const { dispatch } = useContext(SnackbarContext);
  const [closing, setClosing] = useState(false);
  const [closed, setClosed] = useState(false);

  const close = useCallback(() => {
    // Close the toast which causes it to slide right
    setClosing(true);

    // After slide right we set closing which causes it to shrink vertically
    setTimeout(() => {
      setClosed(true);
    }, ANIMATION_DELAY);

    // After shrink vertically we remove the toast
    setTimeout(() => {
      dispatch({ type: ActionTypes.RemoveToast, id });
    }, 2 * ANIMATION_DELAY);
  }, [dispatch, id]);

  useEffect(() => {
    const timer = setTimeout(() => {
      close();
    }, DURATION);
    return () => clearTimeout(timer);
  }, [close]);

  return (
    <div
      css={{
        transition: 'all ' + ANIMATION_DELAY + 'ms ease-in-out 0s',
        maxHeight: 500,
        ...(closed && { maxHeight: 0 }),
        [SMALL_AND_UP]: {
          '& + &': {
            marginTop: '1em',
          },
        },
      }}
    >
      <div
        role="button"
        tabIndex={0}
        css={{
          cursor: 'pointer',
          backgroundColor: 'var(--overlay-inner)',
          color: 'var(--text)',
          padding: '1em',
          width: '100%',
          marginLeft: '110%',
          boxShadow: '0px 0px 3px 3px rgba(0, 0, 0, 0.5)',
          animation: `${slidein} 0.3s ease-in-out`,
          transition: 'all ' + ANIMATION_DELAY + 'ms ease-in-out 0s',
          ...(message &&
            !closing && {
            marginLeft: 0,
          }),
        }}
        onClick={close}
        onKeyPress={close}
      >
        <IoMdCloseCircleOutline
          css={{
            float: 'right',
          }}
        />
        {message}
      </div>
    </div>
  );
}
Example #2
Source File: TopBar.tsx    From crosshare with GNU Affero General Public License v3.0 5 votes vote down vote up
NotificationLink = ({
  notification: n,
}: {
  notification: NotificationT;
}): JSX.Element => {
  const [closing, setClosing] = useState(false);

  const close = useCallback(() => {
    // Close the toast which causes it to start shrinking
    setClosing(true);
    // After shrink vertically we remove the toast
    setTimeout(() => {
      App.firestore().collection('n').doc(n.id).update({ r: true });
    }, ANIMATION_DELAY);
  }, [n.id]);

  let link: JSX.Element;
  switch (n.k) {
  case 'comment':
    link = (
      <Link css={NotificationLinkCSS} href={`/crosswords/${n.p}/${slugify(n.pn)}`}>
        {n.cn} commented on <u>{n.pn}</u>
      </Link>
    );
    break;
  case 'reply':
    link = (
      <Link css={NotificationLinkCSS} href={`/crosswords/${n.p}/${slugify(n.pn)}`}>
        {n.cn} replied to your comment on <u>{n.pn}</u>
      </Link>
    );
    break;
  case 'newpuzzle':
    link = (
      <Link css={NotificationLinkCSS} href={`/crosswords/${n.p}/${slugify(n.pn)}`}>
        {n.an} published a new puzzle: <u>{n.pn}</u>
      </Link>
    );
    break;
  case 'featured':
    link = (
      <Link css={NotificationLinkCSS} href={`/crosswords/${n.p}/${slugify(n.pn)}`}>
          Crosshare is featuring your puzzle <u>{n.pn}</u>
        {n.as ? ` as ${n.as}` : ' on the homepage'}!
      </Link>
    );
    break;
  }
  return (
    <div
      css={{
        transition: 'all ' + ANIMATION_DELAY + 'ms ease-in-out 0s',
        maxHeight: 500,
        overflow: 'hidden',
        ...(closing && { maxHeight: 0 }),
        display: 'flex',
        alignItems: 'center',
        '& + &': {
          borderTop: '1px solid var(--text-input-border)',
        },
      }}
    >
      {link}
      <div
        role="button"
        tabIndex={0}
        onClick={close}
        onKeyPress={close}
        css={{
          paddingLeft: '1em',
          cursor: 'pointer',
        }}
      >
        <IoMdCloseCircleOutline />
      </div>
    </div>
  );
}
Example #3
Source File: Filters.tsx    From hub with Apache License 2.0 4 votes vote down vote up
Filters = (props: Props) => {
  const getFacetsByFilterKey = (filterKey: string): Facets | undefined => {
    return find(props.facets, (facets: Facets) => filterKey === facets.filterKey);
  };

  const getPublishers = (): JSX.Element | null => {
    let crElement = null;
    const publisherList = getFacetsByFilterKey('publisher');
    if (!isUndefined(publisherList) && publisherList.options.length > 0) {
      const isChecked = (facetOptionId: string, filterKey: string) => {
        return (props.activeFilters[filterKey] || []).includes(facetOptionId.toString());
      };

      const options = publisherList.options.map((facet: FacetOption) => ({
        ...facet,
        filterKey: facet.filterKey!,
      }));

      const publisherOptions = options.map((option: Option) => (
        <CheckBox
          key={`${option.filterKey}_${option.id.toString()}`}
          name={option.filterKey}
          device={props.device}
          value={option.id.toString()}
          labelClassName="mw-100"
          className={styles.checkbox}
          legend={option.total}
          label={option.name}
          checked={isChecked(option.id.toString(), option.filterKey)}
          onChange={(e: ChangeEvent<HTMLInputElement>) =>
            props.onChange(e.target.name, e.target.value, e.target.checked)
          }
        />
      ));

      crElement = (
        <div role="menuitem" className={`mt-2 mt-sm-3 pt-1 ${styles.facet}`}>
          <SmallTitle text="Publisher" className="text-dark fw-bold" />
          <div className="mt-3" role="group">
            <ExpandableList visibleItems={5} items={publisherOptions} forceCollapseList={props.forceCollapseList} />
          </div>
        </div>
      );
    }

    return crElement;
  };

  const getKindFacets = (): JSX.Element | null => {
    let kindElement = null;
    const kind = getFacetsByFilterKey('kind');
    if (!isUndefined(kind) && kind.options.length > 0 && kind.filterKey) {
      const active = props.activeFilters.hasOwnProperty(kind.filterKey) ? props.activeFilters[kind.filterKey] : [];
      const isChecked = (facetOptionId: string) => {
        return active.includes(facetOptionId.toString());
      };

      kindElement = (
        <div role="menuitem" className={`mt-1 mt-sm-2 pt-1 ${styles.facet}`}>
          <SmallTitle text={kind.title} className="text-dark fw-bold" id={`repo-${kind.filterKey}-${props.device}`} />
          <div className="mt-3" role="group" aria-labelledby={`repo-${kind.filterKey}-${props.device}`}>
            {kind.options.map((option: FacetOption) => (
              <CheckBox
                key={`kind_${option.id.toString()}`}
                name={kind.filterKey!}
                device={props.device}
                value={option.id.toString()}
                labelClassName="mw-100"
                className={styles.checkbox}
                legend={option.total}
                label={option.name}
                checked={isChecked(option.id.toString())}
                onChange={(e: ChangeEvent<HTMLInputElement>) =>
                  props.onChange(e.target.name, e.target.value, e.target.checked)
                }
              />
            ))}
          </div>
        </div>
      );
    }

    return kindElement;
  };

  const getCapabilitiesFacets = (): JSX.Element | null => {
    let element = null;
    const capabilities = getFacetsByFilterKey('capabilities');
    if (!isUndefined(capabilities) && capabilities.options.length > 0 && capabilities.filterKey) {
      const active = props.activeFilters.hasOwnProperty(capabilities.filterKey)
        ? props.activeFilters[capabilities.filterKey]
        : [];
      const isChecked = (facetOptionId: string) => {
        return active.includes(facetOptionId.toString());
      };

      const sortedCapabilities = sortBy(capabilities.options, [
        (facet: FacetOption) => {
          return OPERATOR_CAPABILITIES.findIndex((level: string) => level === facet.id);
        },
      ]);

      element = (
        <div role="menuitem" className={`mt-2 mt-sm-3 pt-1 ${styles.facet}`}>
          <SmallTitle
            text={capabilities.title}
            className="text-dark fw-bold"
            id={`pkg-${capabilities.filterKey}-${props.device}`}
          />
          <div className="mt-3" role="group" aria-labelledby={`pkg-${capabilities.filterKey}-${props.device}`}>
            {sortedCapabilities.map((option: FacetOption) => (
              <CheckBox
                key={`capabilities_${option.id.toString()}`}
                name={capabilities.filterKey!}
                device={props.device}
                value={option.id.toString()}
                labelClassName="mw-100"
                className={`text-capitalize ${styles.checkbox}`}
                legend={option.total}
                label={option.name}
                checked={isChecked(option.id.toString())}
                onChange={(e: ChangeEvent<HTMLInputElement>) =>
                  props.onChange(e.target.name, e.target.value, e.target.checked)
                }
              />
            ))}
          </div>
        </div>
      );
    }

    return element;
  };

  const getRepositoryFacets = (): JSX.Element | null => {
    let crElement = null;
    const repo = getFacetsByFilterKey('repo');
    if (!isUndefined(repo) && repo.options.length > 0 && repo.filterKey) {
      const options = repo.options.map((facet: FacetOption) => ({
        ...facet,
        filterKey: repo.filterKey,
      }));

      const isChecked = (facetOptionId: string) => {
        return (props.activeFilters.repo || []).includes(facetOptionId.toString());
      };

      const repoOptions = options.map((option: FacetOption) => (
        <CheckBox
          key={`repo_${option.id.toString()}`}
          name={repo.filterKey!}
          device={props.device}
          value={option.id.toString()}
          labelClassName="mw-100"
          className={styles.checkbox}
          legend={option.total}
          label={option.name}
          checked={isChecked(option.id.toString())}
          onChange={(e: ChangeEvent<HTMLInputElement>) =>
            props.onChange(e.target.name, e.target.value, e.target.checked)
          }
        />
      ));

      crElement = (
        <div role="menuitem" className={`mt-2 mt-sm-3 pt-1 ${styles.facet}`}>
          <SmallTitle text={repo.title} className="text-dark fw-bold" id={`pkg-${repo.filterKey}-${props.device}`} />
          <div className="mt-3" role="group" aria-labelledby={`pkg-${repo.filterKey}-${props.device}`}>
            <ExpandableList visibleItems={5} items={repoOptions} forceCollapseList={props.forceCollapseList} />
          </div>
        </div>
      );
    }

    return crElement;
  };

  const getLicenseFacets = (): JSX.Element | null => {
    let crElement = null;
    const license = getFacetsByFilterKey('license');
    if (!isUndefined(license) && license.options.length > 0 && license.filterKey) {
      const options = license.options.map((facet: FacetOption) => ({
        ...facet,
        filterKey: license.filterKey,
      }));

      const isChecked = (facetOptionId: string) => {
        return (props.activeFilters.license || []).includes(facetOptionId.toString());
      };

      const licenseOptions = options.map((option: FacetOption) => (
        <CheckBox
          key={`license_${option.id.toString()}`}
          name={license.filterKey!}
          device={props.device}
          value={option.id.toString()}
          labelClassName="mw-100"
          className={`text-capitalize ${styles.checkbox}`}
          legend={option.total}
          label={option.name}
          checked={isChecked(option.id.toString())}
          onChange={(e: ChangeEvent<HTMLInputElement>) =>
            props.onChange(e.target.name, e.target.value, e.target.checked)
          }
        />
      ));

      crElement = (
        <div role="menuitem" className={`mt-2 mt-sm-3 pt-1 ${styles.facet}`}>
          <SmallTitle
            text={license.title}
            className="text-dark fw-bold"
            id={`pkg-${license.filterKey}-${props.device}`}
          />
          <div className="mt-3" role="group" aria-labelledby={`pkg-${license.filterKey}-${props.device}`}>
            <ExpandableList visibleItems={5} items={licenseOptions} forceCollapseList={props.forceCollapseList} />
          </div>
        </div>
      );
    }

    return crElement;
  };

  return (
    <div className={classnames(styles.filters, { 'pt-2 mt-3 mb-5': props.visibleTitle })}>
      {props.visibleTitle && (
        <div className="d-flex flex-row align-items-center justify-content-between pb-2 mb-4 border-bottom">
          <div className="h6 text-uppercase mb-0 lh-base">Filters</div>
          {(!isEmpty(props.activeFilters) ||
            props.deprecated ||
            props.operators ||
            props.verifiedPublisher ||
            props.official ||
            !isEmpty(props.activeTsQuery)) && (
            <div className={`d-flex align-items-center ${styles.resetBtnWrapper}`}>
              <IoMdCloseCircleOutline className={`text-dark ${styles.resetBtnDecorator}`} />
              <button
                className={`btn btn-link btn-sm p-0 ps-1 text-dark ${styles.resetBtn}`}
                onClick={props.onResetFilters}
                aria-label="Reset filters"
              >
                Reset
              </button>
            </div>
          )}
        </div>
      )}

      <div className="d-flex flex-row align-items-baseline">
        <CheckBox
          name="official"
          value="official"
          device={props.device}
          className={styles.checkbox}
          labelClassName="mw-100"
          label="Official"
          checked={props.official || false}
          onChange={props.onOfficialChange}
        />

        <div className="d-none d-md-block">
          <ElementWithTooltip
            tooltipClassName={styles.tooltipMessage}
            className={`position-relative ${styles.tooltipIcon}`}
            element={<MdInfoOutline />}
            tooltipMessage="The publisher owns the software deployed by the packages"
            visibleTooltip
            active
          />
        </div>
      </div>

      <div className="d-flex flex-row align-items-baseline">
        <CheckBox
          name="verifiedPublisher"
          value="verifiedPublisher"
          device={props.device}
          className={styles.checkbox}
          labelClassName="mw-100"
          label="Verified publishers"
          checked={props.verifiedPublisher || false}
          onChange={props.onVerifiedPublisherChange}
        />

        <div className="d-none d-md-block">
          <ElementWithTooltip
            tooltipClassName={styles.tooltipMessage}
            className={styles.tooltipIcon}
            element={<MdInfoOutline />}
            tooltipMessage="The publisher owns the repository"
            visibleTooltip
            active
          />
        </div>
      </div>

      {getKindFacets()}
      <TsQuery device={props.device} active={props.activeTsQuery || []} onChange={props.onTsQueryChange} />
      {getPublishers()}
      {getRepositoryFacets()}
      {getLicenseFacets()}
      {getCapabilitiesFacets()}

      <div role="menuitem" className={`mt-2 mt-sm-3 pt-1 ${styles.facet}`}>
        <SmallTitle text="Others" className="text-dark fw-bold" />

        <div className="mt-3">
          <CheckBox
            name="operators"
            value="operators"
            device={props.device}
            labelClassName="mw-100"
            className={styles.checkbox}
            label="Only operators"
            checked={props.operators || false}
            onChange={props.onOperatorsChange}
          />

          <CheckBox
            name="deprecated"
            value="deprecated"
            device={props.device}
            className={styles.checkbox}
            label="Include deprecated"
            labelClassName="mw-100"
            checked={props.deprecated || false}
            onChange={props.onDeprecatedChange}
          />
        </div>
      </div>
    </div>
  );
}
Example #4
Source File: index.tsx    From hub with Apache License 2.0 4 votes vote down vote up
SearchView = (props: Props) => {
  const { ctx, dispatch } = useContext(AppCtx);
  const history = useHistory();
  const sampleQueries = getSampleQueries();
  const [searchResults, setSearchResults] = useState<SearchResults>({
    facets: null,
    packages: null,
    paginationTotalCount: '0',
  });
  const { isSearching, setIsSearching, scrollPosition, setScrollPosition } = props;
  const [apiError, setApiError] = useState<string | null>(null);
  const [currentTsQueryWeb, setCurrentTsQueryWeb] = useState(props.tsQueryWeb);

  const calculateOffset = (): number => {
    return props.pageNumber && ctx.prefs.search.limit ? (props.pageNumber - 1) * ctx.prefs.search.limit : 0;
  };

  const [offset, setOffset] = useState<number>(calculateOffset());

  const getFilterName = (key: string, label: string): FilterLabel | null => {
    const correctKey = ['user', 'org'].includes(key) ? 'publisher' : key;
    if (searchResults.facets) {
      const selectedKey = searchResults.facets.find((fac: Facets) => fac.filterKey === correctKey);
      if (selectedKey) {
        const selectedOpt = selectedKey.options.find((opt: FacetOption) => opt.id.toString() === label);
        if (selectedOpt) {
          return { key: selectedKey.title, name: selectedOpt.name };
        } else {
          return null;
        }
      } else {
        return null;
      }
    }
    return null;
  };

  const getTsQueryName = (ts: string): string | null => {
    const selectedTsQuery = TS_QUERY.find((query: TsQuery) => query.label === ts);
    if (selectedTsQuery) {
      return selectedTsQuery.name;
    } else {
      return null;
    }
  };

  const isEmptyFacets = (): boolean => {
    if (searchResults.facets) {
      return every(searchResults.facets, (f: Facets) => {
        return f.options.length === 0;
      });
    } else {
      return true;
    }
  };

  useScrollRestorationFix();

  const saveScrollPosition = () => {
    setScrollPosition(window.scrollY);
  };

  const updateWindowScrollPosition = (newPosition: number) => {
    window.scrollTo(0, newPosition);
  };

  const prepareSelectedFilters = (name: string, newFilters: string[], prevFilters: FiltersProp): FiltersProp => {
    let cleanFilters: FiltersProp = {};
    switch (name) {
      case 'kind':
        // Remove selected chart repositories when some kind different to Chart is selected and Chart is not selected
        if (newFilters.length > 0 && !newFilters.includes(RepositoryKind.Helm.toString())) {
          cleanFilters['repo'] = [];
        }
        break;
    }

    return {
      ...prevFilters,
      [name]: newFilters,
      ...cleanFilters,
    };
  };

  const getCurrentFilters = (): SearchFiltersURL => {
    return {
      pageNumber: props.pageNumber,
      tsQueryWeb: props.tsQueryWeb,
      tsQuery: props.tsQuery,
      filters: props.filters,
      deprecated: props.deprecated,
      operators: props.operators,
      verifiedPublisher: props.verifiedPublisher,
      official: props.official,
      sort: props.sort,
    };
  };

  const updateCurrentPage = (searchChanges: any) => {
    history.push({
      pathname: '/packages/search',
      search: prepareQueryString({
        ...getCurrentFilters(),
        pageNumber: 1,
        ...searchChanges,
      }),
    });
  };

  const onFiltersChange = (name: string, value: string, checked: boolean): void => {
    const currentFilters = props.filters || {};
    let newFilters = isUndefined(currentFilters[name]) ? [] : currentFilters[name].slice();
    if (checked) {
      newFilters.push(value);
    } else {
      newFilters = newFilters.filter((el) => el !== value);
    }

    updateCurrentPage({
      filters: prepareSelectedFilters(name, newFilters, currentFilters),
    });
  };

  const onResetSomeFilters = (filterKeys: string[]): void => {
    let newFilters: FiltersProp = {};
    filterKeys.forEach((fKey: string) => {
      newFilters[fKey] = [];
    });

    updateCurrentPage({
      filters: { ...props.filters, ...newFilters },
    });
  };

  const onTsQueryChange = (value: string, checked: boolean): void => {
    let query = isUndefined(props.tsQuery) ? [] : props.tsQuery.slice();
    if (checked) {
      query.push(value);
    } else {
      query = query.filter((el) => el !== value);
    }

    updateCurrentPage({
      tsQuery: query,
    });
  };

  const onDeprecatedChange = (): void => {
    updateCurrentPage({
      deprecated: !isUndefined(props.deprecated) && !isNull(props.deprecated) ? !props.deprecated : true,
    });
  };

  const onOperatorsChange = (): void => {
    updateCurrentPage({
      operators: !isUndefined(props.operators) && !isNull(props.operators) ? !props.operators : true,
    });
  };

  const onVerifiedPublisherChange = (): void => {
    updateCurrentPage({
      verifiedPublisher:
        !isUndefined(props.verifiedPublisher) && !isNull(props.verifiedPublisher) ? !props.verifiedPublisher : true,
    });
  };

  const onOfficialChange = (): void => {
    updateCurrentPage({
      official: !isUndefined(props.official) && !isNull(props.official) ? !props.official : true,
    });
  };

  const onResetFilters = (): void => {
    history.push({
      pathname: '/packages/search',
      search: prepareQueryString({
        pageNumber: 1,
        tsQueryWeb: props.tsQueryWeb,
        tsQuery: [],
        filters: {},
        sort: DEFAULT_SORT,
      }),
    });
  };

  const onPageNumberChange = (pageNumber: number): void => {
    updateCurrentPage({
      pageNumber: pageNumber,
    });
  };

  const onSortChange = (sort: string): void => {
    history.replace({
      pathname: '/packages/search',
      search: prepareQueryString({
        ...getCurrentFilters(),
        sort: sort,
        pageNumber: 1,
      }),
    });
    setScrollPosition(0);
    updateWindowScrollPosition(0);
  };

  const onPaginationLimitChange = (newLimit: number): void => {
    history.replace({
      pathname: '/packages/search',
      search: prepareQueryString({
        ...getCurrentFilters(),
        pageNumber: 1,
      }),
    });
    setScrollPosition(0);
    updateWindowScrollPosition(0);
    dispatch(updateLimit(newLimit));
  };

  useEffect(() => {
    async function fetchSearchResults() {
      setIsSearching(true);
      const query = {
        tsQueryWeb: props.tsQueryWeb,
        tsQuery: props.tsQuery,
        filters: props.filters,
        offset: calculateOffset(),
        limit: ctx.prefs.search.limit,
        deprecated: props.deprecated,
        operators: props.operators,
        verifiedPublisher: props.verifiedPublisher,
        official: props.official,
        sort: props.sort || DEFAULT_SORT,
      };

      try {
        let newSearchResults = await API.searchPackages(query);
        if (
          newSearchResults.paginationTotalCount === '0' &&
          searchResults.facets &&
          !isEmpty(searchResults.facets) &&
          currentTsQueryWeb === props.tsQueryWeb // When some filters have changed, but not ts_query_web
        ) {
          newSearchResults = {
            ...newSearchResults,
            facets: searchResults.facets,
          };
        }
        setSearchResults(newSearchResults);
        setOffset(query.offset);
        setCurrentTsQueryWeb(props.tsQueryWeb);
        setApiError(null);
      } catch {
        setSearchResults({
          facets: [],
          packages: [],
          paginationTotalCount: '0',
        });
        setApiError('An error occurred searching packages, please try again later.');
      } finally {
        setIsSearching(false);
        // Update scroll position
        if (history.action === 'PUSH') {
          // When search page is open from detail page
          if (props.fromDetail && !isUndefined(scrollPosition)) {
            updateWindowScrollPosition(scrollPosition);
            // When search has changed
          } else {
            updateWindowScrollPosition(0);
          }
          // On pop action and when scroll position has been previously saved
        } else if (!isUndefined(scrollPosition)) {
          updateWindowScrollPosition(scrollPosition);
        }
      }
    }
    fetchSearchResults();

    // prettier-ignore
    /* eslint-disable react-hooks/exhaustive-deps */
  }, [
    props.tsQueryWeb,
    JSON.stringify(props.tsQuery),
    props.pageNumber,
    JSON.stringify(props.filters), // https://twitter.com/dan_abramov/status/1104414272753487872
    props.deprecated,
    props.operators,
    props.verifiedPublisher,
    props.official,
    ctx.prefs.search.limit,
    props.sort,
  ]);
  /* eslint-enable react-hooks/exhaustive-deps */

  const activeFilters =
    props.deprecated ||
    props.operators ||
    props.verifiedPublisher ||
    props.official ||
    !isUndefined(props.tsQuery) ||
    !isEmpty(props.filters);

  return (
    <>
      <SubNavbar className={`h-auto ${styles.subnavbar}`}>
        <div className="d-flex flex-column w-100">
          <div className="d-flex align-items-center justify-content-between flex-nowrap">
            <div className="d-flex align-items-center text-truncate w-100">
              {!isNull(searchResults.packages) && (
                <>
                  {/* Mobile filters */}
                  {!isEmptyFacets() && (
                    <Sidebar
                      label="Filters"
                      className="d-inline-block d-md-none me-2"
                      wrapperClassName="px-4"
                      buttonType={classnames('btn-sm rounded-circle position-relative', styles.btnMobileFilters, {
                        [styles.filtersBadge]: activeFilters,
                      })}
                      buttonIcon={<FaFilter />}
                      closeButton={
                        <>
                          {isSearching ? (
                            <>
                              <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                              <span className="ms-2">Loading...</span>
                            </>
                          ) : (
                            <>See {searchResults.paginationTotalCount} results</>
                          )}
                        </>
                      }
                      leftButton={
                        <>
                          <div className="d-flex align-items-center">
                            <IoMdCloseCircleOutline className={`text-dark ${styles.resetBtnDecorator}`} />
                            <button
                              className="btn btn-link btn-sm p-0 ps-1 text-dark"
                              onClick={onResetFilters}
                              aria-label="Reset filters"
                            >
                              Reset
                            </button>
                          </div>
                        </>
                      }
                      header={<div className="h6 text-uppercase mb-0 flex-grow-1">Filters</div>}
                    >
                      <div role="menu">
                        <Filters
                          forceCollapseList={props.tsQueryWeb !== currentTsQueryWeb}
                          facets={searchResults.facets}
                          activeFilters={props.filters || {}}
                          activeTsQuery={props.tsQuery}
                          onChange={onFiltersChange}
                          onResetSomeFilters={onResetSomeFilters}
                          onTsQueryChange={onTsQueryChange}
                          deprecated={props.deprecated}
                          operators={props.operators}
                          verifiedPublisher={props.verifiedPublisher}
                          official={props.official}
                          onDeprecatedChange={onDeprecatedChange}
                          onOperatorsChange={onOperatorsChange}
                          onVerifiedPublisherChange={onVerifiedPublisherChange}
                          onOfficialChange={onOfficialChange}
                          onResetFilters={onResetFilters}
                          visibleTitle={false}
                          device="mobile"
                        />
                      </div>
                    </Sidebar>
                  )}

                  {!isSearching && (
                    <div className="d-flex flex-column w-100 text-truncate">
                      <div className={`text-truncate ${styles.searchText}`} role="status">
                        {parseInt(searchResults.paginationTotalCount) > 0 && (
                          <span className="pe-1">
                            {offset + 1} -{' '}
                            {parseInt(searchResults.paginationTotalCount) < ctx.prefs.search.limit * props.pageNumber
                              ? searchResults.paginationTotalCount
                              : ctx.prefs.search.limit * props.pageNumber}{' '}
                            <span className="ms-1">of</span>{' '}
                          </span>
                        )}
                        {searchResults.paginationTotalCount}
                        <span className="ps-1"> results </span>
                        {props.tsQueryWeb && props.tsQueryWeb !== '' && (
                          <span className="d-none d-sm-inline ps-1">
                            for "<span className="fw-bold">{props.tsQueryWeb}</span>"
                          </span>
                        )}
                        {activeFilters && (
                          <small className="d-inline d-lg-none fst-italic ms-1"> (some filters applied)</small>
                        )}
                      </div>
                    </div>
                  )}
                </>
              )}
            </div>

            <div className="ms-3">
              <div className="d-flex flex-row">
                {/* Only display sort options when ts_query_web is defined */}
                {props.tsQueryWeb && props.tsQueryWeb !== '' && (
                  <SortOptions
                    activeSort={props.sort || DEFAULT_SORT}
                    updateSort={onSortChange}
                    disabled={isNull(searchResults.packages) || searchResults.packages.length === 0}
                  />
                )}
                <div className="d-none d-sm-flex">
                  <PaginationLimit
                    limit={ctx.prefs.search.limit}
                    updateLimit={onPaginationLimitChange}
                    disabled={isNull(searchResults.packages) || searchResults.packages.length === 0}
                  />
                </div>
                <MoreActionsButton />
              </div>
            </div>
          </div>

          {activeFilters && (
            <div className="d-none d-lg-inline">
              <div className="d-flex flex-row flex-wrap align-items-center pt-2">
                <span className="me-2 pe-1 mb-2">Filters:</span>
                {props.official && <FilterBadge name="Only official" onClick={onOfficialChange} />}
                {props.verifiedPublisher && (
                  <FilterBadge name="Only verified publishers" onClick={onVerifiedPublisherChange} />
                )}
                {!isUndefined(props.filters) && (
                  <>
                    {Object.keys(props.filters).map((type: string) => {
                      const opts = props.filters![type];
                      return (
                        <Fragment key={`opts_${type}`}>
                          {opts.map((opt: string) => {
                            const filter = getFilterName(type, opt);
                            if (isNull(filter)) return null;
                            return (
                              <FilterBadge
                                key={`btn_${type}_${opt}`}
                                type={filter.key}
                                name={filter.name}
                                onClick={() => onFiltersChange(type, opt, false)}
                              />
                            );
                          })}
                        </Fragment>
                      );
                    })}
                  </>
                )}
                {props.tsQuery && (
                  <>
                    {props.tsQuery.map((ts: string) => {
                      const value = getTsQueryName(ts);
                      if (isNull(value)) return null;
                      return (
                        <FilterBadge
                          key={`btn_${ts}`}
                          type="Category"
                          name={value}
                          onClick={() => onTsQueryChange(ts, false)}
                        />
                      );
                    })}
                  </>
                )}
                {props.operators && <FilterBadge name="Only operators" onClick={onOperatorsChange} />}
                {props.deprecated && <FilterBadge name="Include deprecated" onClick={onDeprecatedChange} />}
              </div>
            </div>
          )}
        </div>
      </SubNavbar>

      <div className="d-flex position-relative pt-3 pb-3 flex-grow-1">
        {(isSearching || isNull(searchResults.packages)) && <Loading spinnerClassName="position-fixed top-50" />}

        <main role="main" className="container-lg px-sm-4 px-lg-0 d-flex flex-row justify-content-between">
          {!isEmptyFacets() && (
            <aside
              className={`px-xs-0 px-sm-3 px-lg-0 d-none d-md-block position-relative ${styles.sidebar}`}
              aria-label="Filters"
            >
              <div className="me-5" role="menu">
                <Filters
                  forceCollapseList={props.tsQueryWeb !== currentTsQueryWeb}
                  facets={searchResults.facets}
                  activeFilters={props.filters || {}}
                  activeTsQuery={props.tsQuery}
                  onChange={onFiltersChange}
                  onResetSomeFilters={onResetSomeFilters}
                  onTsQueryChange={onTsQueryChange}
                  deprecated={props.deprecated}
                  operators={props.operators}
                  verifiedPublisher={props.verifiedPublisher}
                  official={props.official}
                  onDeprecatedChange={onDeprecatedChange}
                  onOperatorsChange={onOperatorsChange}
                  onVerifiedPublisherChange={onVerifiedPublisherChange}
                  onOfficialChange={onOfficialChange}
                  onResetFilters={onResetFilters}
                  visibleTitle
                  device="desktop"
                />
              </div>
            </aside>
          )}

          <div
            className={classnames('flex-grow-1 mt-3 px-xs-0 px-sm-3 px-lg-0', styles.list, {
              [styles.emptyList]: isNull(searchResults.packages) || searchResults.packages.length === 0,
            })}
          >
            {!isNull(searchResults.packages) && (
              <>
                {searchResults.packages.length === 0 ? (
                  <NoData issuesLinkVisible={!isNull(apiError)}>
                    {isNull(apiError) ? (
                      <>
                        We're sorry!
                        <p className="h6 mb-0 mt-3 lh-base">
                          <span> We can't seem to find any packages that match your search </span>
                          {props.tsQueryWeb && (
                            <span className="ps-1">
                              for "<span className="fw-bold">{props.tsQueryWeb}</span>"
                            </span>
                          )}
                          {!isEmpty(props.filters) && <span className="ps-1">with the selected filters</span>}
                        </p>
                        <p className="h6 mb-0 mt-5 lh-base">
                          You can{' '}
                          {!isEmpty(props.filters) ? (
                            <button
                              className="btn btn-link text-dark fw-bold py-0 pb-1 px-0"
                              onClick={onResetFilters}
                              aria-label="Reset filters"
                            >
                              <u>reset the filters</u>
                            </button>
                          ) : (
                            <button
                              className="btn btn-link text-dark fw-bold py-0 pb-1 px-0"
                              onClick={() => {
                                history.push({
                                  pathname: '/packages/search',
                                  search: prepareQueryString({
                                    pageNumber: 1,
                                    tsQueryWeb: '',
                                    tsQuery: [],
                                    filters: {},
                                  }),
                                });
                              }}
                              aria-label="Browse all packages"
                            >
                              <u>browse all packages</u>
                            </button>
                          )}
                          {sampleQueries.length > 0 ? (
                            <>, try a new search or start with one of the sample queries:</>
                          ) : (
                            <> or try a new search.</>
                          )}
                        </p>
                        <div className="h5 d-flex flex-row align-items-end justify-content-center flex-wrap">
                          <SampleQueries className="bg-light text-dark border-secondary text-dark" />
                        </div>
                      </>
                    ) : (
                      <>{apiError}</>
                    )}
                  </NoData>
                ) : (
                  <>
                    <div className="mb-2 noFocus" id="content" tabIndex={-1} aria-label="Packages list">
                      <div className="row" role="list">
                        {searchResults.packages.map((item: Package) => (
                          <SearchCard
                            key={item.packageId}
                            package={item}
                            searchUrlReferer={{
                              tsQueryWeb: props.tsQueryWeb,
                              tsQuery: props.tsQuery,
                              pageNumber: props.pageNumber,
                              filters: props.filters,
                              deprecated: props.deprecated,
                              operators: props.operators,
                              verifiedPublisher: props.verifiedPublisher,
                              official: props.official,
                              sort: props.sort,
                            }}
                            saveScrollPosition={saveScrollPosition}
                          />
                        ))}
                      </div>
                    </div>

                    <Pagination
                      limit={ctx.prefs.search.limit}
                      offset={offset}
                      total={parseInt(searchResults.paginationTotalCount)}
                      active={props.pageNumber}
                      className="my-5"
                      onChange={onPageNumberChange}
                    />
                  </>
                )}
              </>
            )}
          </div>
        </main>
      </div>

      <Footer isHidden={isSearching || isNull(searchResults.packages)} />
    </>
  );
}
Example #5
Source File: Overlay.tsx    From crosshare with GNU Affero General Public License v3.0 4 votes vote down vote up
Overlay = (props: {
  coverImage?: string | null;
  onClick?: () => void;
  hidden?: boolean;
  closeCallback?: () => void;
  children: React.ReactNode;
}) => {
  const ref = useRef<HTMLElement | null>(null);
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    ref.current = document.getElementById('modal');
    if (!ref.current) {
      ref.current = document.createElement('div');
      ref.current.setAttribute('id', 'modal');
      document.body.appendChild(ref.current);
    }
    setMounted(true);
  }, []);

  const overlay = (
    // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
    <div
      onClick={props.onClick || (() => undefined)}
      css={{
        display: props.hidden ? 'none' : 'block',
        position: 'fixed',
        backgroundColor: 'var(--overlay-bg)',
        top: 0,
        left: 0,
        width: '100%',
        overflowY: 'scroll',
        overscrollBehavior: 'contain',
        height: '100%',
        zIndex: 10000,
      }}
    >
      <div
        css={{
          position: 'relative',
          width: '95%',
          margin: '1em auto',
          [SMALL_AND_UP]: {
            width: '90%',
            margin: '2em auto',
          },
          maxWidth: '1200px',
          backgroundColor: 'var(--overlay-inner)',
          border: '1px solid black',
        }}
      >
        {props.coverImage ? <CoverPic coverPicture={props.coverImage} /> : ''}
        <div
          css={{
            padding: '3em 1.5em',
          }}
        >
          {props.closeCallback ? (
            <button
              css={{
                background: 'transparent',
                color: 'var(--text)',
                ...(props.coverImage && { color: 'var(--social-text)' }),
                border: 'none',
                position: 'absolute',
                padding: 0,
                fontSize: '2.5em',
                verticalAlign: 'text-top',
                width: '1em',
                height: '1em',
                top: '0.5em',
                right: '0.5em',
              }}
              onClick={props.closeCallback}
            >
              <IoMdCloseCircleOutline
                aria-label="close"
                css={{ position: 'absolute', top: 0, right: 0 }}
              />
            </button>
          ) : (
            ''
          )}
          {props.children}
        </div>
      </div>
    </div>
  );

  return mounted && ref.current ? createPortal(overlay, ref.current) : overlay;
}