react-icons/fa#FaSearch TypeScript Examples

The following examples show how to use react-icons/fa#FaSearch. 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: Header.tsx    From twitch-clone with MIT License 6 votes vote down vote up
Header: React.FC = () => {
  return (
    <Container>
      <LeftMenu>
        <img src="/favicon.svg" alt="Twitch Clone" width="32" height="32" />
        <ul>
          <li>
            <a href="#">Browse</a>
          </li>
          <li>...</li>
        </ul>
      </LeftMenu>
      <MiddleMenu>
        <div>
          <input id="search" type="text" placeholder="Search" />
          <label aria-label="search" htmlFor="search">
            Search
          </label>
          <FaSearch size={16} />
        </div>
      </MiddleMenu>
      <RightMenu>
        <CgCrown size={24} />
        <Button buttonType="secondary">Log In</Button>
        <Button buttonType="primary">Sign Up</Button>
        <BsPerson size={24} color="#fafafa" />
      </RightMenu>
    </Container>
  );
}
Example #2
Source File: index.tsx    From pokedex with MIT License 6 votes vote down vote up
InputSearch: React.FC<InputSearchProps> = ({ value, onChange }) => {
  const [isFocused, setIsFocused] = useState(false);

  const handleInputBlur = useCallback(() => {
    setIsFocused(false);
  }, []);

  const handleInputFocus = useCallback(() => {
    setIsFocused(true);
  }, []);

  return (
    <Container isFocused={isFocused}>
      <FaSearch />
      <input
        placeholder={isFocused ? '' : 'Qual Pokémon você está procurando?'}
        value={value}
        onChange={e => onChange(e.target.value)}
        onFocus={handleInputFocus}
        onBlur={handleInputBlur}
      />
    </Container>
  );
}
Example #3
Source File: IPForm.tsx    From iplocate with MIT License 6 votes vote down vote up
IPForm: React.FC<Props> = (props) => {
  const { onSubmit } = props;
  const [value, setValue] = useState("");
  const [error, setError] = useState(false);

  const handleSubmit: React.FormEventHandler = (e) => {
    e.preventDefault();
    if (!validateIp(value)) {
      setError(true);
    } else {
      onSubmit(value);
      setValue("");
    }
  };

  const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
    setValue(e.currentTarget.value);
    setError(false);
  };

  return (
    <>
      <Form onSubmit={handleSubmit} error={error}>
        <Input
          value={value}
          onChange={handleChange}
          placeholder="IP Address"
          aria-label="IP Address input"
        />
        <button type="submit" aria-label="Search IP Address">
          <FaSearch />
        </button>
      </Form>
      {error && <ErrorMessage>Invalid IP Address.</ErrorMessage>}
    </>
  );
}
Example #4
Source File: columns.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 6 votes vote down vote up
createColumns: CreateColumns = () => [
  {
    Header: 'Details',
    Cell: ({ row: { original } }: CellProps<Domain>) => (
      <Link to={`/inventory/domain/${original.id}`}>
        <FaSearch className="margin-x-auto display-block" />
      </Link>
    )
  },
  {
    Header: 'Organization',
    accessor: (e) => e.organization.name,
    id: 'organizationName',
    Filter: ColumnFilter
  },
  {
    Header: 'Domain',
    accessor: 'name',
    id: 'reverseName',
    Filter: ColumnFilter
  },
  {
    Header: 'IP',
    accessor: 'ip',
    Filter: ColumnFilter
  },
  {
    Header: 'Ports',
    id: 'port',
    disableSortBy: true,
    accessor: ({ services }) =>
      services.map((service) => service.port).join(', '),
    Filter: ColumnFilter
  },
  {
    Header: 'Services',
    id: 'service',
    disableSortBy: true,
    accessor: (domain) => getServiceNames(domain),
    Filter: ColumnFilter
  },
  {
    Header: 'Vulnerabilities',
    id: 'vulnerability',
    accessor: (domain) =>
      domain.vulnerabilities &&
      domain.vulnerabilities
        .map((vulnerability) => vulnerability.cve)
        .join(', '),
    Filter: ColumnFilter
  },
  {
    Header: 'Last Seen',
    id: 'updatedAt',
    accessor: ({ updatedAt }) =>
      `${formatDistanceToNow(parseISO(updatedAt))} ago`,
    disableFilters: true
  },
  {
    Header: 'First Seen',
    id: 'createdAt',
    accessor: ({ createdAt }) =>
      `${formatDistanceToNow(parseISO(createdAt))} ago`,
    disableFilters: true
  }
]
Example #5
Source File: ColumnFilter.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 6 votes vote down vote up
ColumnFilter: React.FC<Props> = ({
  column: { filterValue, setFilter, id }
}) => {
  const [search, setSearch] = useState('');

  const onSubmit: React.FormEventHandler = (e) => {
    e.preventDefault();
    setFilter(search);
  };

  const clear: React.MouseEventHandler = (e) => {
    setSearch('');
    setFilter('');
  };

  return (
    <form onSubmit={onSubmit} className={classes.root}>
      <TextInput
        id={id}
        name={id}
        type="text"
        value={search}
        onChange={(e) => {
          setSearch(e.target.value);
        }}
        className={classes.input}
      />
      <FaSearch className={classes.searchIcon}></FaSearch>
      {filterValue && (
        <button type="button" className={classes.clearFilter} onClick={clear}>
          <FaTimes />
        </button>
      )}
    </form>
  );
}
Example #6
Source File: App.tsx    From clarity with Apache License 2.0 5 votes vote down vote up
SideMenuItems: (MenuItem | GroupedMenuItem)[] = [
  new MenuItem(
    Pages.Accounts,
    'Accounts',
    <IoMdKey fontSize={SideMenuIconSize} />
  ),
  new MenuItem(
    Pages.Faucet,
    'Faucet',
    <IoIosWater fontSize={SideMenuIconSize} />,
    false,
    FaucetAsterix
  ),
  new MenuItem(
    Pages.DeployContracts,
    'Deploy Contract',
    <IoMdRocket fontSize={SideMenuIconSize} />
  ),
  // new MenuItem(
  //   Pages.Explorer,
  //   'Explorer',
  //   <FaMapMarkedAlt fontSize={SideMenuIconSize} />
  // ),
  new MenuItem(Pages.Blocks, 'Blocks', <FiGrid fontSize={SideMenuIconSize} />),
  new MenuItem(
    Pages.Deploys,
    'Deploys',
    <FaListUl fontSize={SideMenuIconSize} />
  ),
  new MenuItem(
    Pages.Search,
    'Search',
    <FaSearch fontSize={SideMenuIconSize} />
  ),
  new MenuItem(
    Pages.Validators,
    'Validators',
    <FaUsers fontSize={SideMenuIconSize} />
  ),
  new MenuItem(
    Pages.ConnectedPeers,
    'Connected Peers',
    <FaNetworkWired fontSize={SideMenuIconSize} />
  )
  // new GroupedMenuItem(
  //   'clarityContracts',
  //   'Contracts',
  //   <FaFileContract fontSize={SideMenuIconSize} />,
  //   [new MenuItem(Pages.Vesting, 'Vesting')]
  // )
]
Example #7
Source File: App.tsx    From casper-clarity with Apache License 2.0 5 votes vote down vote up
SideMenuItems: (MenuItem | GroupedMenuItem)[] = [
  new MenuItem(
    Pages.Accounts,
    'Accounts',
    <IoMdKey fontSize={SideMenuIconSize} />
  ),
  faucetMenuItem,
  new MenuItem(
    Pages.DeployContracts,
    'Deploy Contract',
    <IoMdRocket fontSize={SideMenuIconSize} />
  ),
  // new MenuItem(
  //   Pages.Explorer,
  //   'Explorer',
  //   <FaMapMarkedAlt fontSize={SideMenuIconSize} />
  // ),
  new MenuItem(Pages.Blocks, 'Blocks', <FiGrid fontSize={SideMenuIconSize} />),
  new MenuItem(
    Pages.Deploys,
    'Deploys',
    <FaListUl fontSize={SideMenuIconSize} />
  ),
  new MenuItem(
    Pages.Search,
    'Search',
    <FaSearch fontSize={SideMenuIconSize} />
  ),
  new MenuItem(
    Pages.Validators,
    'Validators',
    <FaUsers fontSize={SideMenuIconSize} />
  ),
  new MenuItem(
    Pages.ConnectedPeers,
    'Connected Peers',
    <FaNetworkWired fontSize={SideMenuIconSize} />
  )
  // new GroupedMenuItem(
  //   'clarityContracts',
  //   'Contracts',
  //   <FaFileContract fontSize={SideMenuIconSize} />,
  //   [new MenuItem(Pages.Vesting, 'Vesting')]
  // )
]
Example #8
Source File: index.tsx    From netflix-clone with GNU General Public License v3.0 5 votes vote down vote up
NavBar: React.FC = () => {
  const [isBlack, setIsBlack] = useState(false);

  useEffect(() => {
    window.addEventListener('scroll', () => setIsBlack(window.scrollY > 10));

    // Executa quando a pagina for desconstruida
    return () => {
      window.removeEventListener('scroll', () =>
        setIsBlack(window.scrollY > 10),
      );
    };
  }, []);

  return (
    <Container isBlack={isBlack}>
      <RoutesMenu>
        <img src={LogoNetflix} alt="dahdjahdkja" />
        <ul>
          <li style={{ fontWeight: 'bold' }}>Inicio</li>
          <li>Series</li>
          <li>Filmes</li>
          <li>Mais Recentes</li>
          <li>Minha Lista</li>
        </ul>
      </RoutesMenu>
      <Profile>
        <FaSearch />
        <FaGift />
        <FaBell />
        <button type="button">
          <img
            src="https://occ-0-761-185.1.nflxso.net/dnm/api/v6/Z-WHgqd_TeJxSuha8aZ5WpyLcX8/AAAABR8DzEDMx6x6rgkSexM2EYh44oQISc8fyEFr6WnraR9_HyniHFDRbXRrElpLThfL9OYFOueAItK7VIEb2xH7AqA.png?r=c71"
            alt="imagem profile usuario"
          />
          <FaCaretDown />
        </button>
      </Profile>
    </Container>
  );
}
Example #9
Source File: AddReferenceWebDrawer.tsx    From personal-archive with MIT License 5 votes vote down vote up
AddReferenceWebDrawer: FC<Props> = ({show, onConfirm, onCancel}) => {
  const [url, setUrl] = useState('')

  const clear = () => {
    setUrl('')
  }

  const onClose = () => {
    clear()
    onCancel()
  }

  const onSubmit = () => {
    if (url.length === 0) {
      toast.error('url required')
      return
    }

    if (!url.startsWith('http')) {
      toast.error('invalid url')
      return
    }

    clear()
    onConfirm(url)
  }

  return (
    <>
      <Drawer shown={show} onClose={onClose} title="Add Web Reference">
        <InputField
          size="small"
          value={url}
          onChange={e => setUrl((e.target as any).value)}
          onKeyDown={e => {
            if (e.key === 'Enter') {
              onSubmit()
            }
          }}
          suffix={
            <span role="button" onClick={onSubmit} style={{ marginRight: '10px' }}>
            <FaSearch />
          </span>
          }
        />
        <ButtonWrapper>
          <Button onClick={onSubmit} size="small">Add</Button>
        </ButtonWrapper>
      </Drawer>
    </>
  )
}
Example #10
Source File: AddReferenceArticleDrawer.tsx    From personal-archive with MIT License 5 votes vote down vote up
AddReferenceArticleDrawer: FC<Props> = ({show, onConfirm, onCancel}) => {
  const [keyword, setKeyword] = useState('')
  const [fetching, searchArticles, clear, articles, pagination] = useRequestSearchArticles()

  const onSearch = (page: number) => searchArticles(keyword, page)

  const onClear = () => {
    setKeyword('')
    clear()
  }

  const onClose = () => {
    onClear()
    onCancel()
  }

  const onSelect = (article: Article) => {
    onClear()
    onConfirm(article)
  }

  return (
    <Drawer shown={show} onClose={onClose} title="Add Article Reference">
      <InputField
        size="small"
        value={keyword}
        onChange={e => setKeyword((e.target as any).value)}
        onKeyDown={e => {
          if (e.key === 'Enter') {
            onSearch(1)
          }
        }}
        suffix={
          <span role="button" onClick={() => onSearch(1)} style={{marginRight: '10px'}}>
            <FaSearch/>
          </span>
        }
      />
      {fetching ? <Loading type="boxLoader"/> : null}
      {
        articles.map((article, i) => (
          <ArticleWrapper key={article.id}>
            <a href="#!" onClick={() => onSelect(article)}>
              {article.title}
            </a>
          </ArticleWrapper>
        ))
      }
      <Pagination
        pageCount={pagination.totalPages}
        selectedPage={pagination.page}
        onPageChange={onSearch}
      />
      <div>
        <Button onClick={onClose} type="white">Close</Button>
      </div>
    </Drawer>
  )
}
Example #11
Source File: MainLayout.tsx    From personal-archive with MIT License 5 votes vote down vote up
SearchDrawer: FC<{ show: boolean, onClose: () => void }> = ({show, onClose}) => {
  const [keyword, setKeyword] = useState('')
  const history = useHistory()
  const ref = useRef<HTMLElement>(null)

  useEffect(() => {
    if (show && ref.current) {
      setTimeout(() => ref?.current?.focus(), 100)
    }
  }, [show])

  const onSubmit = () => {
    setKeyword('')
    onClose()
    history.push(`/articles/search?q=${encodeURIComponent(keyword)}`)
  }

  return (
    <Drawer
      shown={show}
      onClose={() => {
        setKeyword('')
        onClose()
      }}
    >
      <InputField
        ref={ref}
        size="small"
        value={keyword}
        onChange={e => setKeyword((e.target as any).value)}
        onKeyDown={e => {
          if (e.key === 'Enter') {
            onSubmit()
          }
        }}
        onBlur={onClose}
        suffix={
          <span role="button" onClick={onSubmit} style={{ marginRight: '10px' }}>
            <FaSearch />
          </span>
        }
      />
    </Drawer>
  )
}
Example #12
Source File: MainLayout.tsx    From personal-archive with MIT License 5 votes vote down vote up
MainLayout: FC<Props> = ({ side, title, children}) => {
  const [showSearchDrawer, setShowSearchDrawer] = useState(false)
  const [showNav, setShowNav] = useState(false)  // only works in mobile
  const history = useHistory()

  useEffect(() => {
    // 페이지 이동시 초기화
    history.listen(() => setShowNav(false))
  }, [history])

  useSubscribe(ShowSearchDrawer, (isOpened: boolean) => setShowSearchDrawer(isOpened))

  const customTokens = getTokens({})

  return (
    <ThemeProvider theme={{orbit: customTokens}}>
      <Parent>
        <Helmet>
          <title>{getTitle(title)}</title>
        </Helmet>
        <Header>
          <Logo>PA</Logo>
          <Menu onClick={() => history.push(`/tags/all`)}>
            <FaRegNewspaper size="21px" />
          </Menu>
          <Menu onClick={() => history.push(`/notes`)}>
            <FaRegStickyNote size="21px" />
          </Menu>
          <Menu onClick={() => setShowSearchDrawer(true)}>
            <FaSearch size="21px" />
          </Menu>
          <Menu onClick={() => history.push(`/settings`)}>
            <FaRegSun size="21px" />
          </Menu>
          <DarkModeSwitch />
        </Header>
        <Middle>
          <Mobile>
            <When condition={side != null}>
              <Menu onClick={() => setShowNav(true)}>
                <MenuHamburger/>
              </Menu>
            </When>
          </Mobile>
        </Middle>
        <Body>
          <Desktop>
            <When condition={side != null}>
              <Nav>
                {side}
              </Nav>
            </When>
          </Desktop>
          <Main>
            {children}
            <SearchDrawer
              show={showSearchDrawer}
              onClose={() => setShowSearchDrawer(false)}
            />
          </Main>
        </Body>
        <Drawer
          shown={showNav}
          onClose={() => setShowNav(false)}
        >
          <Stack>
            {side}
          </Stack>
        </Drawer>
      </Parent>
    </ThemeProvider>
  )
}
Example #13
Source File: InputTypeahead.tsx    From hub with Apache License 2.0 4 votes vote down vote up
InputTypeahead = forwardRef((props: Props, ref: Ref<RefInputTypeaheadField>) => {
  const inputEl = useRef<HTMLInputElement>(null);
  const itemsWrapper = useRef<HTMLDivElement | null>(null);
  const [inputValue, setInputValue] = useState('');
  const [highlightedText, setHighlightedText] = useState<RegExp | null>(null);
  const [highlightedItem, setHighlightedItem] = useState<number | null>(null);

  useImperativeHandle(ref, () => ({
    reset: () => {
      setInputValue('');
    },
    getValue(): string {
      return inputValue;
    },
    updateValue(newValue: string): void {
      setInputValue(newValue);
    },
  }));

  const getVisibleItems = useCallback((): Option[] | null => {
    let filteredItems: Option[] = [];
    let elements: any[] | null = null;

    if (!isNull(highlightedItem)) {
      setHighlightedItem(null);
    }

    if (isUndefined(props.displayItemsInValueLength) || inputValue.length >= props.displayItemsInValueLength) {
      filteredItems = props.options.filter((opt: Option) => opt.name.toLowerCase().includes(inputValue.toLowerCase()));
      elements = orderBy(
        filteredItems,
        [
          (item: Option) =>
            props.selected.hasOwnProperty(item.filterKey) && props.selected[item.filterKey].includes(item.id.toString())
              ? -1
              : 1,
          'total',
        ],
        ['asc', 'desc']
      );
      // Scroll top when new visible items are displayed
      if (itemsWrapper && itemsWrapper.current) {
        itemsWrapper.current.scroll(0, 0);
      }
    }

    return elements;
  }, [highlightedItem, props.displayItemsInValueLength, props.options, props.selected, inputValue]);

  const getSelectedItems = useCallback((): Option[] => {
    let selectedItems: Option[] = [];
    Object.keys(props.selected).forEach((fKey: string) => {
      props.selected[fKey].forEach((item: string) => {
        const selected = props.options.find((opt: Option) => opt.id.toString() === item && opt.filterKey === fKey);
        if (!isUndefined(selected)) {
          selectedItems.push(selected);
        }
      });
    });
    return orderBy(selectedItems, 'total', 'desc');
  }, [props.options, props.selected]);

  const getOptionName = (name: string): JSX.Element => {
    if (!isNull(highlightedText)) {
      const stringParts: string[] = compact(name.split(highlightedText));
      if (stringParts.length === 1) {
        return (
          <span
            className={classnames({
              'fw-bold highlighted': name.toLowerCase() === inputValue.toLowerCase(),
            })}
          >
            {name}
          </span>
        );
      }

      return (
        <>
          {stringParts.map((str: string, index: number) => (
            <span
              key={`${name}_${index}`}
              className={classnames({
                'fw-bold highlighted': str.toLowerCase() === inputValue.toLowerCase(),
              })}
            >
              {str}
            </span>
          ))}
        </>
      );
    } else {
      return <>{name}</>;
    }
  };

  const [visibleItems, setVisibleItems] = useState<Option[] | null>(null);
  const [selectedItems, setSelectedItems] = useState<Option[]>(getSelectedItems());

  useEffect(() => {
    setVisibleItems(getVisibleItems());
  }, [inputValue, props.options]); /* eslint-disable-line react-hooks/exhaustive-deps */

  useEffect(() => {
    setSelectedItems(getSelectedItems());
  }, [getSelectedItems, props.selected]);

  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.stopPropagation();
    e.preventDefault();

    setHighlightedItem(null);
    setInputValue(e.target.value);

    const escapedValue = escapeRegExp(e.target.value.toLowerCase());
    setHighlightedText(e.target.value !== '' ? new RegExp(`(${escapedValue})`, 'gi') : null);
  };

  const onSelect = (filterKey: string, id: string, isSelected: boolean) => {
    setHighlightedItem(null);
    props.onChange(filterKey, id, !isSelected);
    if (props.onChangeSelection) {
      props.onChangeSelection();
    }
  };

  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
    switch (e.key) {
      case 'ArrowDown':
        updateHighlightedItem('down');
        return;
      case 'ArrowUp':
        updateHighlightedItem('up');
        return;
      case 'Enter':
        if (!isNull(highlightedItem) && visibleItems) {
          const item = visibleItems[highlightedItem];
          const isSelected =
            props.selected.hasOwnProperty(item.filterKey) &&
            props.selected[item.filterKey].includes(item.id.toString());
          onSelect(item.filterKey, item.id.toString(), isSelected);
        }
        return;
      default:
        return;
    }
  };

  const scrollDropdown = (index: number) => {
    if (itemsWrapper && itemsWrapper.current) {
      const itemsOnScreen = Math.floor(itemsWrapper.current.clientHeight / ITEM_HEIGHT);
      if (index + 1 > itemsOnScreen) {
        itemsWrapper.current.scroll(0, (index + 1 - itemsOnScreen) * ITEM_HEIGHT);
      } else {
        itemsWrapper.current.scroll(0, 0);
      }
    }
  };

  const updateHighlightedItem = (arrow: 'up' | 'down') => {
    if (!isNull(highlightedItem)) {
      let newIndex: number = arrow === 'up' ? highlightedItem - 1 : highlightedItem + 1;
      if (newIndex > visibleItems!.length - 1) {
        newIndex = 0;
      }
      if (newIndex < 0) {
        newIndex = visibleItems!.length - 1;
      }
      scrollDropdown(newIndex);
      setHighlightedItem(newIndex);
    } else {
      if (visibleItems && visibleItems.length > 0) {
        const newIndex = arrow === 'up' ? visibleItems.length - 1 : 0;
        scrollDropdown(newIndex);
        setHighlightedItem(newIndex);
      }
    }
  };

  if (props.options.length === 0) return null;

  return (
    <>
      <div className={`mb-3 input-group-sm ${styles.inputWrapper} ${props.inputWrapperClassName}`}>
        <input
          ref={inputEl}
          type="text"
          placeholder={props.placeholder || `Search ${props.label}`}
          className={classnames(
            'flex-grow-1 form-control',
            styles.input,
            { 'ps-3 pe-4': props.searchIcon },
            { 'px-3': isUndefined(props.searchIcon) || !props.searchIcon }
          )}
          name={`inputTypeahead_${props.label}`}
          value={inputValue}
          onChange={onChange}
          onKeyDown={onKeyDown}
          spellCheck="false"
          autoFocus={!isUndefined(props.autofocus) && props.autofocus}
        />

        {props.searchIcon && <FaSearch className={`text-muted position-absolute ${styles.searchIcon}`} />}

        {!isUndefined(props.additionalInfo) && <div className="alert p-0 mt-3">{props.additionalInfo}</div>}
      </div>

      {selectedItems.length > 0 && props.visibleClear && (
        <div className="py-1 border-bottom">
          <button
            className="btn btn-sm w-100"
            onClick={() => {
              if (props.onClear) {
                props.onClear();
              }
            }}
            aria-label="Clear all"
          >
            <div className="d-flex flex-row align-items-center text-muted">
              <IoIosClose />
              <small className="ms-2">Clear all</small>
            </div>
          </button>
        </div>
      )}

      {visibleItems && (
        <>
          {visibleItems.length === 0 ? (
            <div className={`p-3 text-center ${props.listClassName}`}>
              <small className="text-muted">Sorry, no matches found</small>
            </div>
          ) : (
            <div className={`${styles.itemsList} ${props.listClassName}`} ref={itemsWrapper}>
              {visibleItems.map((opt: Option, index: number) => {
                const isSelected =
                  props.selected.hasOwnProperty(opt.filterKey) &&
                  props.selected[opt.filterKey].includes(opt.id.toString());
                const name = getOptionName(opt.name);

                return (
                  <button
                    key={`opt_${opt.filterKey}_${opt.id}`}
                    data-testid="typeaheadDropdownBtn"
                    className={classnames(
                      'dropdown-item',
                      styles.option,
                      props.optClassName,
                      {
                        [styles.selected]: isSelected,
                      },
                      {
                        [styles.highlighted]: index === highlightedItem,
                      }
                    )}
                    onClick={() => {
                      onSelect(opt.filterKey, opt.id.toString(), isSelected);
                      if (props.onChangeSelection) {
                        props.onChangeSelection();
                      }
                    }}
                    aria-label={`${isSelected ? 'Unselect' : 'Select'} option`}
                  >
                    <div className="d-flex flex-row align-items-center position-relative">
                      {isSelected && (
                        <div className={`position-absolute ${styles.checkMark}`}>
                          <IoIosCheckmark />
                        </div>
                      )}
                      <InputTypeaheadOptionItem opt={opt} name={name} iconClassName={styles.icon} />

                      {isSelected && (
                        <div className={`position-absolute ${styles.close}`}>
                          <IoIosClose />
                        </div>
                      )}
                    </div>
                  </button>
                );
              })}
            </div>
          )}
        </>
      )}
    </>
  );
})
Example #14
Source File: CompareTemplatesList.tsx    From hub with Apache License 2.0 4 votes vote down vote up
CompareTemplatesList = (props: Props) => {
  const [inputValue, setInputValue] = useState<string>('');
  const [visibleTemplates, setVisibleTemplates] = useState<CompareChartTemplate[]>(props.templates || []);

  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.stopPropagation();
    e.preventDefault();

    setInputValue(e.target.value);
  };

  const getStatusIcon = (status: CompareChartTemplateStatus): JSX.Element => {
    return (
      <>
        {(() => {
          switch (status) {
            case CompareChartTemplateStatus.Deleted:
              return (
                <div className="text-danger">
                  <GoDiffRemoved />
                </div>
              );
            case CompareChartTemplateStatus.Added:
              return (
                <span className="text-success">
                  <GoDiffAdded />
                </span>
              );
            case CompareChartTemplateStatus.Modified:
              return (
                <span className={styles.modifiedIcon}>
                  <GoDiffModified />
                </span>
              );
            default:
              return null;
          }
        })()}
      </>
    );
  };

  useEffect(() => {
    const getVisibleTemplates = (): CompareChartTemplate[] => {
      const tmpls = props.templates || [];
      return tmpls.filter((tmpl: CompareChartTemplate) => {
        const term = `${tmpl.name} ${tmpl.resourceKinds ? tmpl.resourceKinds.join(' ') : ''}`.toLowerCase();
        return term.includes(inputValue.toLowerCase());
      });
    };

    const reviewActiveTemplate = (filteredTemplates: CompareChartTemplate[]) => {
      if (filteredTemplates.length === 0 && !isUndefined(props.activeTemplateName)) {
        props.onTemplateChange(null);
      } else {
        if (props.activeTemplateName) {
          const activeTemplate = filteredTemplates.find(
            (tmpl: CompareChartTemplate) => tmpl.name === props.activeTemplateName
          );
          if (isUndefined(activeTemplate)) {
            props.onTemplateChange(filteredTemplates[0]);
          }
        } else {
          props.onTemplateChange(filteredTemplates[0]);
        }
      }
    };

    if (inputValue === '') {
      setVisibleTemplates(props.templates || []);
      if (isUndefined(props.activeTemplateName) && props.templates) {
        props.onTemplateChange(props.templates[0]);
      }
    } else {
      const filteredTemplates = getVisibleTemplates();
      reviewActiveTemplate(filteredTemplates);
      setVisibleTemplates(filteredTemplates);
    }
  }, [inputValue, props.templates]); /* eslint-disable-line react-hooks/exhaustive-deps */

  if (isNull(props.templates) || isUndefined(props.templates)) return null;
  return (
    <div className="h-100 d-flex flex-column overflow-auto pe-2">
      <div className="position-relative w-100">
        <div className="mb-3 input-group-sm">
          <input
            type="text"
            placeholder="Search by template or resource kind"
            className={`flex-grow-1 form-control ps-3 pe-4 ${styles.input}`}
            name="CompareChartTemplateInput"
            value={inputValue}
            onChange={onChange}
            spellCheck="false"
            disabled={isUndefined(props.templates) || props.templates.length === 0}
          />

          <FaSearch className={`text-muted position-absolute ${styles.searchIcon}`} />
        </div>
      </div>

      {visibleTemplates.length === 0 ? (
        <div
          className={`alert alert-dark p-2 text-center ${styles.alert}`}
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
        >
          <small className="text-muted">Sorry, no matches found</small>
        </div>
      ) : (
        <>
          {visibleTemplates.map((template: CompareChartTemplate, index: number) => {
            const isActive: boolean =
              !isUndefined(props.activeTemplateName) && props.activeTemplateName === template.name;
            return (
              <div key={`template_${index}`}>
                <button
                  className={classnames('btn btn-light btn-sm mb-2 text-start w-100', styles.btn, {
                    [`activeTemplate ${styles.active}`]: isActive,
                  })}
                  onClick={() => {
                    if (!isActive) {
                      props.onTemplateChange(template);
                    }
                  }}
                  aria-label={`Show template ${template.name}`}
                  aria-pressed={isActive}
                >
                  <div className="d-flex flex-column">
                    {(() => {
                      switch (template.type) {
                        case ChartTmplTypeFile.Template:
                          return (
                            <>
                              <div className="d-flex flex-row align-items-baseline mb-1">
                                <div className={styles.legend}>
                                  <small className="text-muted text-uppercase">Template:</small>
                                </div>
                                <div className={`text-truncate ${styles.templateName}`}>{template.name}</div>
                                <div className="ps-2 ms-auto">{getStatusIcon(template.status)}</div>
                              </div>
                              <div className="d-flex flex-row mb-1">
                                <div className={styles.legend}>
                                  <small className="text-muted text-uppercase">Resource:</small>
                                </div>
                                {template.resourceKinds && template.resourceKinds.length > 0 ? (
                                  <>
                                    {template.resourceKinds.length > 1 ? (
                                      <>
                                        <ResourceLabel text="Multiple kinds" />
                                      </>
                                    ) : (
                                      <ResourceLabel text={template.resourceKinds[0]} />
                                    )}
                                  </>
                                ) : (
                                  <>-</>
                                )}
                              </div>
                            </>
                          );
                        case ChartTmplTypeFile.Helper:
                          return (
                            <div className="d-flex flex-row align-items-baseline mb-1">
                              <div className={styles.legend}>
                                <small className="text-muted text-uppercase">Helper:</small>
                              </div>
                              <div className={`text-truncate ${styles.templateName}`}>{template.name}</div>
                              <div className="ps-2 ms-auto">{getStatusIcon(template.status)}</div>
                            </div>
                          );
                      }
                    })()}
                  </div>
                </button>
              </div>
            );
          })}
        </>
      )}
    </div>
  );
}
Example #15
Source File: TemplatesList.tsx    From hub with Apache License 2.0 4 votes vote down vote up
TemplatesList = (props: Props) => {
  const [inputValue, setInputValue] = useState<string>('');
  const [visibleTemplates, setVisibleTemplates] = useState<ChartTemplate[]>(props.templates || []);

  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.stopPropagation();
    e.preventDefault();

    setInputValue(e.target.value);
  };

  useEffect(() => {
    const getVisibleTemplates = (): ChartTemplate[] => {
      const tmpls = props.templates || [];
      return tmpls.filter((tmpl: ChartTemplate) => {
        const term = `${tmpl.name} ${tmpl.resourceKinds ? tmpl.resourceKinds.join(' ') : ''}`.toLowerCase();
        return term.includes(inputValue.toLowerCase());
      });
    };

    const reviewActiveTemplate = (filteredTemplates: ChartTemplate[]) => {
      if (filteredTemplates.length === 0 && !isUndefined(props.activeTemplateName)) {
        props.onTemplateChange(null);
      } else {
        if (props.activeTemplateName) {
          const activeTemplate = filteredTemplates.find(
            (tmpl: ChartTemplate) => tmpl.name === props.activeTemplateName
          );
          if (isUndefined(activeTemplate)) {
            props.onTemplateChange(filteredTemplates[0]);
          }
        } else {
          props.onTemplateChange(filteredTemplates[0]);
        }
      }
    };

    if (inputValue === '') {
      setVisibleTemplates(props.templates || []);
      if (isUndefined(props.activeTemplateName) && !isNull(props.templates)) {
        props.onTemplateChange(props.templates[0]);
      }
    } else {
      const filteredTemplates = getVisibleTemplates();
      reviewActiveTemplate(filteredTemplates);
      setVisibleTemplates(filteredTemplates);
    }
  }, [inputValue]); /* eslint-disable-line react-hooks/exhaustive-deps */

  if (isNull(props.templates)) return null;
  return (
    <div className="h-100 d-flex flex-column overflow-auto pe-2">
      <div className="position-relative w-100">
        <div className="mb-3 input-group-sm">
          <input
            type="text"
            placeholder="Search by template or resource kind"
            className={`flex-grow-1 form-control ps-3 pe-4 ${styles.input}`}
            name="chartTemplateInput"
            value={inputValue}
            onChange={onChange}
            spellCheck="false"
          />

          <FaSearch className={`text-muted position-absolute ${styles.searchIcon}`} />

          <div className="alert p-0 mt-3">
            <small className="text-muted text-break fst-italic">
              This chart version contains <span className="fw-bold">{props.templates.length}</span>{' '}
              {props.templates.length === 1 ? 'template' : 'templates'}
            </small>
          </div>
        </div>
      </div>

      {visibleTemplates.length === 0 ? (
        <div
          className={`alert alert-dark p-2 text-center ${styles.alert}`}
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
        >
          <small className="text-muted">Sorry, no matches found</small>
        </div>
      ) : (
        <>
          {visibleTemplates.map((template: ChartTemplate, index: number) => {
            const isActive: boolean =
              !isUndefined(props.activeTemplateName) && props.activeTemplateName === template.name;
            return (
              <div key={`template_${index}`}>
                <button
                  className={classnames('btn btn-light btn-sm mb-2 text-start w-100', styles.btn, {
                    [`activeTemplate ${styles.active}`]: isActive,
                  })}
                  onClick={() => {
                    if (!isActive) {
                      props.onTemplateChange(template);
                    }
                  }}
                  aria-label={`Show template ${template.name}`}
                  aria-pressed={isActive}
                >
                  <div className="d-flex flex-column">
                    {(() => {
                      switch (template.type) {
                        case ChartTmplTypeFile.Template:
                          return (
                            <>
                              <div className="d-flex flex-row align-items-baseline mb-1">
                                <div className={styles.legend}>
                                  <small className="text-muted text-uppercase">Template:</small>
                                </div>
                                <div className={`text-truncate ${styles.templateName}`}>{template.name}</div>
                              </div>
                              <div className="d-flex flex-row mb-1">
                                <div className={styles.legend}>
                                  <small className="text-muted text-uppercase">Resource:</small>
                                </div>
                                {template.resourceKinds && template.resourceKinds.length > 0 ? (
                                  <>
                                    {template.resourceKinds.length > 1 ? (
                                      <>
                                        <ResourceLabel text="Multiple kinds" />
                                      </>
                                    ) : (
                                      <ResourceLabel text={template.resourceKinds[0]} />
                                    )}
                                  </>
                                ) : (
                                  <>-</>
                                )}
                              </div>
                            </>
                          );
                        case ChartTmplTypeFile.Helper:
                          return (
                            <div className="d-flex flex-row align-items-baseline mb-1">
                              <div className={styles.legend}>
                                <small className="text-muted text-uppercase">Helper:</small>
                              </div>
                              <div className={`text-truncate ${styles.templateName}`}>{template.name}</div>
                            </div>
                          );
                      }
                    })()}
                  </div>
                </button>
              </div>
            );
          })}
        </>
      )}
    </div>
  );
}
Example #16
Source File: ContentDefaultModal.tsx    From hub with Apache License 2.0 4 votes vote down vote up
ContentDefaultModal = (props: Props) => {
  const history = useHistory();
  const anchor = useRef<HTMLDivElement>(null);
  const [openStatus, setOpenStatus] = useState<boolean>(false);
  const [selectedItem, setSelectedItem] = useState<any | null>(null);
  const [isChangingSelectedItem, setIsChangingSelectedItem] = useState<boolean>(false);
  const [code, setCode] = useState<string | undefined>(undefined);
  const [inputValue, setInputValue] = useState<string>('');
  const [visibleFiles, setVisibleFiles] = useState<any[]>(props.files || []);
  const [currentPkgId, setCurrentPkgId] = useState<string>(props.packageId);

  const onItemChange = (file: any | null) => {
    setIsChangingSelectedItem(true);
    setSelectedItem(file);
    updateUrl(file ? file.name.toLowerCase() : undefined);
    const getContent = (): string | undefined => {
      let content: string | undefined;
      switch (props.kind) {
        case ContentDefaultModalKind.CustomResourcesDefinition:
          if (!isNull(file) && !isUndefined(file.example)) {
            content = stringify(file.example, { sortMapEntries: true });
          }
          break;
        default:
          content = file.file;
          break;
      }

      return content;
    };

    if (!isNull(file)) {
      setCode(getContent());
      if (anchor && anchor.current) {
        anchor.current.scrollIntoView({
          block: 'start',
          inline: 'nearest',
          behavior: 'smooth',
        });
      }
    } else {
      setCode(undefined);
    }
    setIsChangingSelectedItem(false);
  };

  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.stopPropagation();
    e.preventDefault();

    setInputValue(e.target.value);
  };

  useEffect(() => {
    const getVisibleFiles = (): any[] => {
      return props.files!.filter((file: any) => {
        const term = `${file.name} ${file.kind || ''}`.toLowerCase();
        return term.includes(inputValue.toLowerCase());
      });
    };

    const reviewActiveFile = (currentFilteredFiles: any[][]) => {
      if (currentFilteredFiles.length === 0 && !isUndefined(selectedItem)) {
        onItemChange(null);
      } else {
        if (selectedItem) {
          const activeFile = currentFilteredFiles.find((file: any) => file === selectedItem);
          if (isUndefined(activeFile)) {
            onItemChange(currentFilteredFiles[0]);
          }
        } else {
          onItemChange(currentFilteredFiles[0]);
        }
      }
    };

    if (props.files && props.files.length > 0) {
      if (inputValue === '') {
        setVisibleFiles(props.files);
        if (isUndefined(selectedItem)) {
          onItemChange(props.files[0]);
        }
      } else {
        const filteredFiles = getVisibleFiles();
        reviewActiveFile(filteredFiles);
        setVisibleFiles(filteredFiles);
      }
    }
  }, [inputValue]); /* eslint-disable-line react-hooks/exhaustive-deps */

  const updateUrl = (fileName?: string) => {
    history.replace({
      search: `?modal=${props.modalName}${fileName ? `&file=${fileName}` : ''}`,
      state: { searchUrlReferer: props.searchUrlReferer, fromStarredPage: props.fromStarredPage },
    });
  };

  const cleanUrl = () => {
    history.replace({
      search: '',
      state: { searchUrlReferer: props.searchUrlReferer, fromStarredPage: props.fromStarredPage },
    });
  };

  useEffect(() => {
    if (props.visibleModal && !openStatus && props.files && props.files.length > 0) {
      onOpenModal();
    }
  }, []); /* eslint-disable-line react-hooks/exhaustive-deps */

  useEffect(() => {
    if (props.packageId !== currentPkgId) {
      setCurrentPkgId(props.packageId);
      if (openStatus) {
        setOpenStatus(false);
      } else if (!openStatus && props.visibleModal) {
        onOpenModal();
      }
    }
  }, [props.packageId]); /* eslint-disable-line react-hooks/exhaustive-deps */

  if (isUndefined(props.files) || props.files.length === 0) return null;

  const getSelectedFileName = (): string => {
    switch (props.kind) {
      case ContentDefaultModalKind.CustomResourcesDefinition:
        return `${props.normalizedName}-${selectedItem.kind}.yaml`;
      case ContentDefaultModalKind.Rules:
        return `${props.normalizedName}-${selectedItem.name.replace('.yaml', '')}.yaml`;
      case ContentDefaultModalKind.Policy:
        return `${props.normalizedName}-${selectedItem.name}`;
    }
  };

  const onOpenModal = () => {
    if (props.files && props.files.length > 0) {
      setVisibleFiles(props.files);
      let currentActiveFile = props.files[0];
      if (props.visibleFile) {
        const visibleFile = props.files.find((file: any) => file.name.toLowerCase() === props.visibleFile);
        if (visibleFile) {
          currentActiveFile = visibleFile;
        }
      }
      onItemChange(currentActiveFile);
      setOpenStatus(true);
    } else {
      cleanUrl();
    }
  };

  const onCloseModal = () => {
    setOpenStatus(false);
    setSelectedItem(null);
    setCode(undefined);
    setInputValue('');
    cleanUrl();
  };

  return (
    <div className="mb-2">
      <div className="text-center">
        <button
          className="btn btn-outline-secondary btn-sm text-nowrap w-100"
          onClick={onOpenModal}
          aria-label={`Open ${props.title} modal`}
          disabled={isUndefined(props.files) || props.files.length === 0}
        >
          {props.btnModalContent}
        </button>
      </div>

      {openStatus && (
        <Modal
          modalDialogClassName={styles.modalDialog}
          modalClassName="h-100"
          header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>{props.title}</div>}
          onClose={onCloseModal}
          open={openStatus}
          breakPoint="md"
          footerClassName={styles.modalFooter}
        >
          <div className="h-100 mw-100">
            <div className="d-flex flex-row align-items-stretch g-0 h-100 mh-100">
              <div className="col-3 h-100 overflow-auto">
                <div className="position-relative w-100 pe-2">
                  <div className="mb-3 input-group-sm">
                    <input
                      type="text"
                      placeholder={`Search by name ${
                        props.kind === ContentDefaultModalKind.CustomResourcesDefinition ? 'or resource kind' : ''
                      }`}
                      className={`flex-grow-1 form-control ps-3 pe-4 ${styles.input}`}
                      name="contentDefaultModalInput"
                      value={inputValue}
                      onChange={onChange}
                      spellCheck="false"
                    />

                    <FaSearch className={`text-muted position-absolute ${styles.searchIcon}`} />

                    <div className="alert p-0 mt-3">
                      <small className="text-muted text-break fst-italic">
                        This package version contains <span className="fw-bold">{props.files.length}</span>{' '}
                        {FILE_TYPE[props.kind][props.files.length === 1 ? 'singular' : 'plural']}
                      </small>
                    </div>
                  </div>
                </div>

                {visibleFiles.length === 0 ? (
                  <div
                    className="alert alert-dark p-2 text-center"
                    role="alert"
                    aria-live="assertive"
                    aria-atomic="true"
                  >
                    <small className="text-muted">Sorry, no matches found</small>
                  </div>
                ) : (
                  <div className="pe-2">
                    {visibleFiles.map((file: any, index: number) => {
                      const isActive = selectedItem === file;
                      return (
                        <button
                          key={`file_${file.name}_${index}`}
                          className={classnames('btn btn-light btn-sm mb-2 text-start w-100', styles.btn, {
                            [`activeTemplate ${styles.active}`]: isActive,
                          })}
                          onClick={() => {
                            if (!isActive) {
                              onItemChange(file);
                            }
                          }}
                          aria-label={`Show ${props.title} ${file.name}`}
                          aria-pressed={isActive}
                        >
                          <div className="d-flex flex-column align-self-center">
                            {(() => {
                              switch (props.kind) {
                                case ContentDefaultModalKind.CustomResourcesDefinition:
                                  const resource = file as CustomResourcesDefinition;
                                  return (
                                    <>
                                      <div className="d-flex flex-row align-items-baseline my-1">
                                        <div className={styles.legend}>
                                          <small className="text-muted text-uppercase">Kind:</small>
                                        </div>
                                        <span className={`text-truncate border fw-bold ${styles.label}`}>
                                          {resource.kind}
                                        </span>
                                      </div>

                                      <div className="d-flex flex-row align-items-baseline mb-1">
                                        <div className={styles.legend}>
                                          <small className="text-muted text-uppercase">Version:</small>
                                        </div>
                                        <div className={`text-truncate ${styles.btnItemContent}`}>
                                          {resource.version}
                                        </div>
                                      </div>
                                    </>
                                  );

                                default:
                                  return (
                                    <div className="d-flex flex-row align-items-baseline mb-1">
                                      <div>
                                        <small className="text-muted text-uppercase me-2">Name:</small>
                                      </div>
                                      <div className={`text-truncate ${styles.btnItemContent}`}>{file.name}</div>
                                    </div>
                                  );
                              }
                            })()}
                          </div>
                        </button>
                      );
                    })}
                  </div>
                )}
              </div>

              <div className="col-9 ps-3 h-100">
                <div className={`position-relative h-100 mh-100 border ${styles.templateWrapper}`}>
                  {isChangingSelectedItem && <Loading />}

                  <div className="d-flex flex-column h-100">
                    {!isNull(selectedItem) && (
                      <>
                        {(() => {
                          switch (props.kind) {
                            case ContentDefaultModalKind.CustomResourcesDefinition:
                              return (
                                <div className={`p-3 border-bottom ${styles.extraInfo}`}>
                                  <div className="h6 fw-bold">{selectedItem.displayName || selectedItem.name}</div>
                                  <div className="d-flex flex-row align-items-baseline mb-1">
                                    <div className={styles.legend}>
                                      <small className="text-muted text-uppercase">Name:</small>
                                    </div>
                                    <div className={`text-truncate ${styles.btnItemContent}`}>{selectedItem.name}</div>
                                  </div>

                                  <div className="d-flex flex-row align-items-baseline mb-1">
                                    <div className={styles.legend}>
                                      <small className="text-muted text-uppercase">Description:</small>
                                    </div>
                                    <div className={styles.btnItemContent}>
                                      {selectedItem.description.replace(/\n/g, ' ')}
                                    </div>
                                  </div>
                                </div>
                              );

                            default:
                              return null;
                          }
                        })()}
                      </>
                    )}
                    <div className="position-relative flex-grow-1 overflow-hidden h-100">
                      {visibleFiles.length > 0 && (
                        <>
                          {code && !isNull(selectedItem) ? (
                            <>
                              <BlockCodeButtons filename={getSelectedFileName()} content={code} />
                              <div className={`position-relative overflow-auto h-100 ${styles.fileWrapper}`}>
                                <div className={`position-absolute ${styles.anchor}`} ref={anchor} />

                                <SyntaxHighlighter
                                  language={props.language}
                                  style={docco}
                                  customStyle={{
                                    backgroundColor: 'transparent',
                                    padding: '1.5rem',
                                    lineHeight: '1.25rem',
                                    marginBottom: '0',
                                    height: '100%',
                                    fontSize: '80%',
                                    overflow: 'initial',
                                    color: '#636a6e',
                                  }}
                                  lineNumberStyle={{
                                    color: 'var(--color-black-25)',
                                    marginRight: '5px',
                                    fontSize: '0.8rem',
                                  }}
                                  showLineNumbers
                                >
                                  {code}
                                </SyntaxHighlighter>
                              </div>
                            </>
                          ) : (
                            <div className="fst-italic d-flex align-items-center justify-content-center h-100 h3">
                              No example provided
                            </div>
                          )}
                        </>
                      )}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}
Example #17
Source File: InviteMembers.tsx    From convoychat with GNU General Public License v3.0 4 votes vote down vote up
InviteMembers: React.FC<IInviteMembers> = ({ roomId }) => {
  const [selectedMembers, setSelectedMembers] = useState<any>({});
  const { state, dispatch } = useModalContext();

  const { register, handleSubmit, errors: formErrors } = useForm<Inputs>();
  const onSubmit = async (data: Inputs) => {};

  const { data: allUsers } = useListUsersQuery();
  const [
    createInvitationLink,
    { data: invitationLink },
  ] = useCreateInvitationLinkMutation({});

  const [inviteMembers, { loading: isLoading }] = useInviteMembersMutation();

  const toggleMemberSelection = (member: IMember) => {
    if (selectedMembers[member.id]) {
      let copy = { ...selectedMembers };
      delete copy[member.id];
      setSelectedMembers({ ...copy });
    } else {
      setSelectedMembers({ ...selectedMembers, [member.id]: member });
    }
  };

  const closeModal = () => {
    dispatch({ type: "CLOSE", modal: "InviteMembers" });
  };

  useEffect(() => {
    if (state.isInviteMembersModalOpen) {
      createInvitationLink({ variables: { roomId } });
    }
  }, [state.isInviteMembersModalOpen, roomId]);

  const selectedMembersIds = Object.keys(selectedMembers);

  return (
    <Modal
      closeTimeoutMS={300}
      isOpen={state.isInviteMembersModalOpen}
      onRequestClose={closeModal}
      contentLabel="Create New Room"
      className="ModalContent"
      overlayClassName="ModalOverlay"
    >
      <h2>Invite Members</h2>
      <small className="textcolor--gray">yeah... yeah spam them</small>

      <Spacer gap="huge" />

      <Input
        type="text"
        icon={FaLink}
        postfixIcon={FaCopy}
        placeholder="invitation link"
        defaultValue={invitationLink?.invitation?.link}
        onPostfixIconClick={e => copyToClipboard(e.value)}
        label={
          <span>
            Copy Invitation Link{" "}
            <span className="textcolor--gray">(expires after 24hours)</span>
          </span>
        }
      />

      <Spacer gap="large" />

      <div>
        <Input
          type="text"
          name="username"
          label="Find Users"
          placeholder="bear grylls"
          icon={FaSearch}
          errors={formErrors}
          inputRef={register({ required: "Username is required" })}
        />

        {allUsers && (
          <MemberSelector
            members={allUsers?.users}
            selectedMembers={selectedMembers}
            onMemberClick={toggleMemberSelection}
          />
        )}

        <Spacer gap="xlarge" />

        <ButtonGroup gap="medium" float="right">
          <Button onClick={closeModal} variant="danger" icon={FaTimes}>
            Cancel
          </Button>
          <Button
            icon={FaPaperPlane}
            isLoading={isLoading}
            disabled={selectedMembersIds.length < 1}
            onClick={() => {
              inviteMembers({
                variables: { roomId, members: selectedMembersIds },
              });
            }}
          >
            Invite members ({selectedMembersIds.length})
          </Button>
        </ButtonGroup>
      </div>
    </Modal>
  );
}