react-bootstrap#InputGroup TypeScript Examples

The following examples show how to use react-bootstrap#InputGroup. 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: form-group.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 6 votes vote down vote up
FormGroupTextarea = (props: any) => {

    return (!props ? <></> :
        <Form.Group as={Row} controlId={props.controlId} hidden={props.hidden} className='mb-1'>
            <Form.Label className='input-label' column xs='5' sm='3'>{props.title + (props.required ? '*' : '')}</Form.Label>

            <Col xs='7' sm='9' className='d-flex'>
                <InputGroup>
                    <Form.Control
                        className='qt-input qt-input-area'
                        value={props.value}
                        readOnly={props.readOnly}
                        disabled={props.disabled}
                        onClick={props.onClick}
                        onChange={props.onChange}
                        placeholder={props.placeholder ? props.placeholder : props.title}
                        type={props.type ? props.type : 'text'}
                        required={props.required}
                        maxLength={props.maxLength}
                        rows={props.rows}
                        isInvalid={props.isInvalid}
                        as='textarea'
                    />
                    <Form.Control.Feedback type="invalid">
                        {props.invalidText}
                    </Form.Control.Feedback>
                </InputGroup>
            </Col>
        </Form.Group >
    )
}
Example #2
Source File: Searchbar.tsx    From devex with GNU General Public License v3.0 5 votes vote down vote up
Searchbar: React.FC<IProps> = ({ isHeaderSearchbar, isISSearchbar }) => {

  const history = useHistory()
  const location = useLocation()
  const [input, setInput] = useState("")
  const [searchType, setSearchType] = useState('Txn/Addr')

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => (
    setInput(e.target.value)
  )

  const handleSubmit = (e: React.SyntheticEvent) => {
    e.preventDefault()
    const trimmedInput = input.trim()
    switch (searchType) {
      case 'Txn/Addr':
        if (isValidAddr(trimmedInput))
          history.push({
            pathname: `/address/${trimmedInput}`,
            search: location.search
          })
        else
          history.push({
            pathname: `/tx/${trimmedInput}`,
            search: location.search
          })
        break
      case 'Tx Block':
        history.push({
          pathname: `/txbk/${trimmedInput}`,
          search: location.search
        })
        break
      case 'DS Block':
        history.push({
          pathname: `/dsbk/${trimmedInput}`,
          search: location.search
        })
        break
    }
    setInput('')
  }

  return <>
    <Form onSubmit={handleSubmit}>
      <InputGroup className="searchbar-ig" id={isHeaderSearchbar ? "header-searchbar-ig" : "searchbar-ig"}>
        {isISSearchbar
          ? <InputGroup.Prepend>
            <DropdownButton id='searchbar-dropdown' title={searchType}>
              <Dropdown.Item onClick={() => setSearchType('Txn/Addr')}>Txn/Addr</Dropdown.Item>
              <Dropdown.Item onClick={() => setSearchType('Tx Block')}>Tx Block</Dropdown.Item>
            </DropdownButton>
          </InputGroup.Prepend>
          :
          <InputGroup.Prepend>
            <DropdownButton id='searchbar-dropdown' title={searchType}>
              <Dropdown.Item onClick={() => setSearchType('Txn/Addr')}>Txn/Addr</Dropdown.Item>
              <Dropdown.Item onClick={() => setSearchType('Tx Block')}>Tx Block</Dropdown.Item>
              <Dropdown.Item onClick={() => setSearchType('DS Block')}>DS Block</Dropdown.Item>
            </DropdownButton>
          </InputGroup.Prepend>
        }
        <Form.Control type="text" value={input} autoFocus={!isHeaderSearchbar}
          placeholder={
            searchType === 'Txn/Addr'
              ? 'Search for a transaction or an address'
              : searchType === 'Tx Block'
                ? 'Search by Tx Block height'
                : 'Search by DS Block height'}
          onChange={handleChange} />
        <InputGroup.Append>
          <Button type="submit">
            {isHeaderSearchbar ? <FontAwesomeIcon icon={faSearch} /> : <div>Search</div>}
          </Button>
        </InputGroup.Append>
      </InputGroup>
    </Form>
  </>
}
Example #3
Source File: form-group.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 5 votes vote down vote up
FormGroupInput = (props: any) => {

    return (!props ? <></> :
        <Form.Group as={Row} controlId={props.controlId} hidden={props.hidden} className='mb-1'>
            <Form.Label className={`'input-label' ${props.lableAlign && 'align-self-'+props.lableAlign}`} column xs='5' sm='3'>{props.title + (props.required ? '*' : '')}</Form.Label>

            <Col xs='7' sm='9' className='d-flex'>
                <Row className='m-0 w-100'>
                    {props.infoText ? <Form.Label className='text-justify'>{props.infoText}</Form.Label> : <></>}
                    <InputGroup>
                        {!props.dropdown
                            ? <></>
                            : <DropdownButton
                                as={InputGroup.Prepend}
                                variant="outline-secondary"
                                title={props.dropdownTitle}
                                id="input-group-dropdown-1"
                            >
                                {props.dropdown}
                            </DropdownButton>
                        }
                        <Form.Control
                            className={!props.prepend ? 'qt-input' : 'qt-input-prepend'}
                            value={props.value}
                            readOnly={props.readOnly}
                            disabled={props.disabled}
                            onClick={props.onClick}
                            onChange={props.onChange}
                            placeholder={props.placeholder ? props.placeholder : props.title}
                            type={props.type ? props.type : 'text'}
                            required={props.required}
                            maxLength={props.maxLength}
                            minLength={props.minLength}
                            min={props.min}
                            max={props.max}
                            pattern={props.pattern}
                            list={props.datalistId}
                            isInvalid={props.isInvalid}
                        />
                        {
                            !(props.datalist && props.datalistId)
                                ? <></>
                                : <datalist id={props.datalistId}>
                                    {props.datalist}
                                </datalist>
                        }
                        {
                            !props.prepend
                                ? <></>
                                : <OverlayTrigger
                                    placement='top-end'
                                    overlay={
                                        <Tooltip id='prepend-tooltip'>
                                            {props.tooltip}
                                        </Tooltip>
                                    }
                                ><InputGroup.Text className='prepend px-3' >{props.prepend}</InputGroup.Text>
                                </OverlayTrigger>
                        }
                        <Form.Control.Feedback type="invalid">
                            {props.InvalidText}
                        </Form.Control.Feedback>
                    </InputGroup>
                </Row>
            </Col>
        </Form.Group>
    )
}
Example #4
Source File: InputChecked.tsx    From cftracker with MIT License 5 votes vote down vote up
InputChecked = (props: PropsType) => {
  return (
    <InputGroup
      className={"d-flex " + (props.className ? props.className : "")}
      title={props.title}
    >
      <InputGroup.Text className={props.textClass + " " + props.theme.bgText}>
        {props.header}
      </InputGroup.Text>

      {/* <InputGroup.Checkbox
        checked={props.checked}
        name={props.name}
        variant="dark"
        className={props.inputClass}
        onChange={() => {
          props.onChange(!props.checked);
        }}
      /> */}
      <div
        className={
          "input-group-text " + props.inputClass + " " + props.theme.bgText
        }
      >
        <input
          name={props.name}
          className={
            "form-check-input mt-0 " +
            props.inputClass +
            " "
          }
          type="checkbox"
          checked={props.checked}
          onChange={() => {
            props.onChange(!props.checked);
          }}
        />
      </div>
    </InputGroup>
  );
}
Example #5
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 5 votes vote down vote up
ProposalEditor = ({
  title,
  body,
  onTitleInput,
  onBodyInput,
}: {
  title: string;
  body: string;
  onTitleInput: (title: string) => void;
  onBodyInput: (body: string) => void;
}) => {
  const bodyPlaceholder = `## Summary\n\nInsert your summary here\n\n## Methodology\n\nInsert your methodology here\n\n## Conclusion\n\nInsert your conclusion here`;
  const [proposalText, setProposalText] = useState('');

  const onBodyChange = (body: string) => {
    setProposalText(body);
    onBodyInput(body);
  };

  return (
    <div>
      <InputGroup className={`${classes.proposalEditor} d-flex flex-column`}>
        <FormText>
          <Trans>Proposal</Trans>
        </FormText>
        <FormControl
          className={classes.titleInput}
          value={title}
          onChange={e => onTitleInput(e.target.value)}
          placeholder="Proposal Title"
        />
        <hr className={classes.divider} />
        <FormControl
          className={classes.bodyInput}
          value={body}
          onChange={e => onBodyChange(e.target.value)}
          as="textarea"
          placeholder={bodyPlaceholder}
        />
      </InputGroup>
      {proposalText !== '' && (
        <div className={classes.previewArea}>
          <h3>
            <Trans>Preview:</Trans>
          </h3>
          <ReactMarkdown
            className={classes.markdown}
            children={proposalText}
            remarkPlugins={[remarkBreaks]}
          />
        </div>
      )}
    </div>
  );
}
Example #6
Source File: common.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
StatelessInputGroupForm: React.FC<StatelessValidatedInputFormProps> = (props) => {
    const inputRef = useInitFocusRef<HTMLInputElement>(!!props.initFocus);
    const handleChange = (newValue: string) => props.onChangeValue(newValue);

    let buttonBlock;
    let handleSubmit;
    if (props.buttonOptions) {
        const { onSubmit, buttonType } = props.buttonOptions;
        const buttonClass = `btn btn-${buttonType}`;
        handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
            e.preventDefault();
            onSubmit(props.value);
        };
        buttonBlock = (
            <InputGroup.Append>
                <Button bsPrefix={buttonClass} type='submit' disabled={props.disabled}>
                    {props.children}
                </Button>
            </InputGroup.Append>
        );
    }

    let feedback;
    if (props.validationResult) {
        const { isInvalid, messages } = props.validationResult;
        const feedbackTextClass = isInvalid ? ' text-danger' : '';
        if (messages.length > 0) {
            // Only show one message at a time.
            feedback = <Form.Text className={`form-feedback${feedbackTextClass}`}>{messages[0]}</Form.Text>;
        }
    }

    return (
        <Form onSubmit={handleSubmit}>
            <InputGroup>
                <Form.Control
                    id={props.id}
                    as='input'
                    ref={inputRef}
                    value={props.value}
                    aria-label={props.formLabel}
                    placeholder={props.placeholder}
                    onChange={(e: any) => handleChange(e.currentTarget.value)}
                    disabled={props.disabled}
                    isInvalid={props.validationResult?.isInvalid}
                />
                {buttonBlock}
            </InputGroup>
            {feedback}
        </Form>
    );
}
Example #7
Source File: queueManager.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
function AddAttendeeForm(props: AddAttendeeFormProps) {
    const [attendee, setAttendee] = useState('');
    const [selectedBackend, setSelectedBackend] = useState(props.defaultBackend);
    const [attendeeValidationResult, validateAndSetAttendeeResult, clearAttendeeResult] = useStringValidation(uniqnameSchema);

    if (!props.allowedBackends.has(selectedBackend)) {
        setSelectedBackend(Array.from(props.allowedBackends)[0]);
    }

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        const curAttendeeValidationResult = !attendeeValidationResult ? validateAndSetAttendeeResult(attendee) : attendeeValidationResult;
        if (!curAttendeeValidationResult.isInvalid) {
            props.onSubmit(attendee, selectedBackend);
            clearAttendeeResult();
            setAttendee(''); // Clear successful input
        }
    }

    const handleAttendeeChange = (e: any) => {
        const newAttendee = e.currentTarget.value;
        setAttendee(newAttendee);
        validateAndSetAttendeeResult(newAttendee);
    }

    let feedback;
    if (attendeeValidationResult) {
        // Only show one message at a time.
        const feedbackTextClass = attendeeValidationResult.isInvalid ? ' text-danger' : '';
        feedback = <Form.Text className={`form-feedback${feedbackTextClass}`}>{attendeeValidationResult.messages[0]}</Form.Text>;
    }

    return (
        <Form onSubmit={handleSubmit} aria-label='Add Attendee'>
            <InputGroup>
                <Form.Control
                    id='add_attendee'
                    as='input'
                    value={attendee}
                    placeholder='Uniqname...'
                    onChange={handleAttendeeChange}
                    disabled={props.disabled}
                    isInvalid={attendeeValidationResult?.isInvalid}
                />
                <InputGroup.Append>
                    <MeetingBackendSelector
                        allowedBackends={props.allowedBackends}
                        backends={props.backends}
                        onChange={setSelectedBackend}
                        selectedBackend={selectedBackend}
                    />
                </InputGroup.Append>
                <InputGroup.Append>
                    <Button variant='success' type='submit' disabled={props.disabled}>
                        + Add Attendee
                    </Button>
                </InputGroup.Append>
            </InputGroup>
            {feedback}
        </Form>
    );
}
Example #8
Source File: Shop.tsx    From apps with MIT License 4 votes vote down vote up
ShopTab = ({ region, shops, filters, onChange, itemCache, questCache }: IProps) => {
    let [forceEnablePlanner, setForceEnablePlanner] = useState<boolean | undefined>(undefined);
    let [itemFilters, setItemFilters] = useState(new Set<number>());

    const allItems = new Map(shops.map((shop) => [shop.cost.item.id, shop.cost.item]));

    let shopEnabled = forceEnablePlanner === undefined ? Manager.shopPlannerEnabled() : forceEnablePlanner;
    let counted = shops
        .filter((shop) => (shopEnabled ? filters.has(shop.id) : true))
        .map(
            (shop) =>
                [shop.cost.item, (shopEnabled ? filters.get(shop.id)! : shop.limitNum) * shop.cost.amount] as const
        );

    let items = new Map(counted.map((tuple) => [tuple[0].id, tuple[0]]));

    let amounts = new Map<number, number>();
    for (let [item, amount] of counted)
        if (amounts.has(item.id)) amounts.set(item.id, (amounts.get(item.id) ?? 0) + amount);
        else amounts.set(item.id, amount);

    // reset filter if nothing is chosen
    if (!amounts.size && itemFilters.size) setItemFilters(new Set());

    const excludeItemIds = (itemIds: number[]) => {
        return new Map(
            shops
                .filter((shop) => shop.payType !== Shop.PayType.FREE)
                .filter((shop) => shop.limitNum !== 0)
                .filter((shop) => !itemIds.includes(shop.targetIds[0]) || shop.purchaseType !== Shop.PurchaseType.ITEM)
                .map((shop) => [shop.id, shop.limitNum])
        );
    };

    return (
        <>
            <Alert variant="success" style={{ margin: "1em 0", display: "flex" }}>
                <div style={{ flexGrow: 1 }}>
                    {shopEnabled
                        ? amounts.size > 0
                            ? "Total amount for chosen items: "
                            : "No item was chosen. Choose at least one to get calculations."
                        : "Total currency amount needed to clear the shop: "}
                    {[...amounts]
                        .filter(([_, amount]) => amount > 0)
                        .map(([itemId, amount]) => (
                            <span style={{ whiteSpace: "nowrap", paddingRight: "1ch" }} key={itemId}>
                                <IconLink region={region} item={items.get(itemId)!} />
                                <b>×{amount.toLocaleString()}</b>
                            </span>
                        ))}
                </div>
                <div style={{ flexBasis: "auto", paddingLeft: "10px" }}>
                    <Button
                        variant={shopEnabled ? "dark" : "success"}
                        onClick={() => setForceEnablePlanner(!forceEnablePlanner)}
                    >
                        <FontAwesomeIcon icon={faEdit} title={shopEnabled ? "Disable planner" : "Enable planner"} />
                    </Button>
                </div>
            </Alert>
            {shopEnabled && (
                <>
                    <ButtonGroup>
                        <Button disabled variant="outline-dark">
                            Quick toggle
                        </Button>
                        <Button variant="outline-success" onClick={() => onChange?.(excludeItemIds([]))}>
                            All
                        </Button>
                        <Button variant="outline-success" onClick={() => onChange?.(new Map())}>
                            None
                        </Button>
                        <Dropdown as={ButtonGroup}>
                            <Dropdown.Toggle variant="outline-success">Exclude</Dropdown.Toggle>
                            <Dropdown.Menu>
                                <Dropdown.Item
                                    as={Button}
                                    onClick={() =>
                                        onChange?.(excludeItemIds([...gemIds, ...magicGemIds, ...secretGemIds]))
                                    }
                                >
                                    Gems
                                </Dropdown.Item>
                                <Dropdown.Item
                                    as={Button}
                                    onClick={() => onChange?.(excludeItemIds([...monumentIds, ...pieceIds]))}
                                >
                                    Monuments & Pieces
                                </Dropdown.Item>
                                <Dropdown.Item as={Button} onClick={() => onChange?.(excludeItemIds(monumentIds))}>
                                    Monuments
                                </Dropdown.Item>
                                <Dropdown.Item as={Button} onClick={() => onChange?.(excludeItemIds(pieceIds))}>
                                    Pieces
                                </Dropdown.Item>
                            </Dropdown.Menu>
                        </Dropdown>
                    </ButtonGroup>
                    <div>&nbsp;</div>
                </>
            )}
            <Table hover responsive className="shopTable">
                <thead>
                    <tr>
                        <th style={{ textAlign: "left" }}>Detail</th>
                        <th style={{ whiteSpace: "nowrap" }}>
                            Currency&nbsp;
                            <Dropdown as={ButtonGroup}>
                                <Dropdown.Toggle size="sm">
                                    <FontAwesomeIcon style={{ display: "inline" }} icon={faFilter} />
                                </Dropdown.Toggle>
                                <Dropdown.Menu>
                                    {/* Actually a checkbox is the best here */}
                                    <Dropdown.Item
                                        as={Button}
                                        onClick={() => {
                                            setItemFilters(new Set());
                                        }}
                                    >
                                        Reset
                                    </Dropdown.Item>
                                    {[...allItems].map(([itemId, item]) => (
                                        <Dropdown.Item
                                            key={item.id}
                                            as={Button}
                                            onClick={() => {
                                                setItemFilters(new Set([itemId]));
                                            }}
                                        >
                                            <ItemIcon region={region} item={item} height={40} />
                                            {item.name}
                                        </Dropdown.Item>
                                    ))}
                                </Dropdown.Menu>
                            </Dropdown>
                        </th>
                        <th>Cost</th>
                        <th>Item</th>
                        <th>Set</th>
                        <th>Limit</th>
                        {shopEnabled && <th>Target</th>}
                    </tr>
                </thead>
                <tbody>
                    {shops
                        .filter((shop) =>
                            itemFilters.size && amounts.size ? itemFilters.has(shop.cost.item.id) : true
                        )
                        .sort((a, b) => a.priority - b.priority)
                        .map((shop) => {
                            let limitNumIndicator = shopEnabled ? (
                                <Button
                                    variant="light"
                                    onClick={() => {
                                        filters.set(shop.id, shop.limitNum);
                                        onChange?.(filters);
                                    }}
                                >
                                    {shop.limitNum.toLocaleString()}
                                </Button>
                            ) : (
                                <>{shop.limitNum.toLocaleString()}</>
                            );

                            return (
                                <tr key={shop.id}>
                                    <td style={{ minWidth: "10em" }}>
                                        <b>{shop.name}</b>
                                        <div style={{ fontSize: "0.75rem" }} className="newline">
                                            {colorString(shop.detail)}
                                            <ScriptLink region={region} shop={shop} />
                                            <br />
                                            <div>
                                                {shop.releaseConditions.length ? (
                                                    <ul className="condition-list">
                                                        {shop.releaseConditions.map((cond, index) => (
                                                            <li key={index} style={{ fontSize: "0.75rem" }}>
                                                                {cond.closedMessage && `${cond.closedMessage} — `}
                                                                <CondTargetNumDescriptor
                                                                    region={region}
                                                                    cond={cond.condType}
                                                                    targets={cond.condValues}
                                                                    num={cond.condNum}
                                                                    quests={questCache}
                                                                    items={itemCache}
                                                                />
                                                            </li>
                                                        ))}
                                                    </ul>
                                                ) : (
                                                    ""
                                                )}
                                            </div>
                                        </div>
                                    </td>
                                    <td style={{ textAlign: "center" }}>
                                        {shop.payType !== Shop.PayType.FREE ? (
                                            <IconLink region={region} item={shop.cost.item} />
                                        ) : null}
                                    </td>
                                    <td style={{ textAlign: "center" }}>
                                        {shop.payType !== Shop.PayType.FREE ? shop.cost.amount.toLocaleString() : null}
                                    </td>
                                    <td>
                                        <ShopPurchaseDescriptor region={region} shop={shop} itemMap={itemCache} />
                                    </td>
                                    <td style={{ textAlign: "center" }}>{shop.setNum.toLocaleString()}</td>
                                    <td style={{ textAlign: "center" }}>
                                        {shop.limitNum === 0 ? <>Unlimited</> : limitNumIndicator}
                                    </td>
                                    {shopEnabled && (
                                        <>
                                            <td style={{ textAlign: "center", maxWidth: "5em" }}>
                                                <InputGroup size="sm">
                                                    <Form.Control
                                                        type="number"
                                                        value={filters.get(shop.id) ?? 0}
                                                        min={0}
                                                        max={shop.limitNum || undefined}
                                                        onChange={(event) => {
                                                            let value = +event.target.value;
                                                            if (value) filters.set(shop.id, value);
                                                            else filters.delete(shop.id);
                                                            onChange?.(filters);
                                                        }}
                                                    />
                                                </InputGroup>
                                            </td>
                                        </>
                                    )}
                                </tr>
                            );
                        })}
                </tbody>
            </Table>
        </>
    );
}
Example #9
Source File: index.tsx    From react-bootstrap-country-select with MIT License 4 votes vote down vote up
CountrySelect = ({
  value,
  onChange = () => {},
  onTextChange,
  countries = [ ...COUNTRIES ],
  exclusions,
  additions,
  valueAs = 'object',
  flags = true,
  flush = true,
  disabled = false,
  placeholder = 'Type or select country...',
  noMatchesText = 'No matches',
  size,
  sort, // e.g. (c1, c2) => c1.name < c2.name ? -1 : (c1.name > c2.name ? 1 : 0),
  matchNameFromStart = true,
  matchAbbreviations = false,
  countryLabelFormatter = ({ name }) => name,
  throwInvalidValueError = false, 
  listMaxHeight,
  closeOnSelect = true,
  formControlProps = {},
  overlayProps = {},
  classPrefix = DEFAULT_CLASS_PREFIX,
  className,
}: CountrySelectProps) => {

  const inputGroupRef = useRef(null);
  const formControlRef = useRef(null);
  const hasInitRef = useRef(false);
  const [ width, setWidth ] = useState(-1);

  const [ {
    focused,
    inputText,
    list,
    activeListItemIndex,
    combinedCountries,
  }, dispatch ] = useReducer(reducer, INITIAL_STATE);

  const handleFocus = focus(dispatch);
  const handleBlur = blur(dispatch);
  const handleTextChange = textChange(dispatch);
  const handleListActiveItemChange = activeListItemChange(dispatch);
  const handleCountrySelect = countrySelect(dispatch);
  const handleClear = clear(dispatch);

  const getCountryId = (value: ICountry | string): string => (typeof value === 'string' ? value : value.id);

  const selectedCountry = value ? (combinedCountries || []).find(country => country.id === getCountryId(value)) : null;

  if (throwInvalidValueError && value && !selectedCountry)
    throw new Error(`No matching country for value: ${JSON.stringify(value)}`);

  useEffect(() => {

    if (hasInitRef.current) return;

    const combinedCountries = applyExclusionsAndAdditions(countries, exclusions, additions);

    const sorted = getInitialList(combinedCountries, sort);

    init(dispatch)(sorted);

    hasInitRef.current = true;

  }, [ countries, exclusions, additions, sort ]);

  useEffect(() => {

    setWidth(inputGroupRef.current.offsetWidth);

  }, [ inputGroupRef ]);

  const select = listItemIndex => {

    const country = list[listItemIndex];

    handleCountrySelect();
    onChange(valueAs === 'id' ? country.id : country);

  };

  const escape = () => {

    handleClear();
    onChange(null);

  };

  const inputChange = (text, ev) => {

    if (selectedCountry && flags) {

      text = removeEmojiFlag(text);

    }

    const [ updatedList, updatedActiveListItemIndex ]
      = getUpdatedList(text, list, activeListItemIndex, combinedCountries, sort, matchNameFromStart, matchAbbreviations);

    handleTextChange(text, updatedList, updatedActiveListItemIndex);

    if (onTextChange) onTextChange(text, ev);
    if (value) onChange(null);

  };

  const handleKey = ev => {

    if (ev.key === 'ArrowUp') {

      ev.preventDefault();

      const newIndex = activeListItemIndex <= 0 ? list.length - 1 : activeListItemIndex - 1;
      handleListActiveItemChange(newIndex);

    } else if (ev.key === 'ArrowDown') {

      const newIndex = activeListItemIndex >= list.length - 1 ? 0 : activeListItemIndex + 1;
      handleListActiveItemChange(newIndex);

    } else if (ev.key === 'Enter') {

      if (activeListItemIndex >= 0) select(activeListItemIndex)

    } else if (ev.key === 'Escape') {

      escape();

    }

  };
  
  const classes = classNames([
    className,
    classPrefix,
    flush && `${classPrefix}--flush`,
  ]);

  return (
    <div className={classes}>

      <InputGroup
        ref={inputGroupRef}
        className={`${classPrefix}__input-group`}
        size={size}
      >

        { (!flush && flags) && 
          <InputGroup.Prepend>

            <InputGroup.Text
              className={`${classPrefix}__input-group__flag`}
            >

              {selectedCountry ? selectedCountry.flag : ''}
            
            </InputGroup.Text>
            
          </InputGroup.Prepend>
        }

        <FormControl
          ref={formControlRef}
          className={`${classPrefix}__form-control`}
          value={selectedCountry ? `${flush && flags ? selectedCountry.flag + '   ' : ''}${selectedCountry.name}` : inputText}
          onKeyDown={handleKey}
          onChange={ev => inputChange(ev.target.value, ev)}
          onFocus={handleFocus}
          onBlur={handleBlur}
          placeholder={placeholder}
          disabled={disabled}
          spellCheck={false}
          autoComplete='new-value'
          {...formControlProps}
        />

      </InputGroup>

      <Overlay
        target={inputGroupRef.current}
        rootClose
        placement='bottom-start'
        show={focused && (!selectedCountry || !closeOnSelect)} // experimental; not documented
        onHide={() => {}}
        transition
        {...overlayProps}
      >

        {({ placement, arrowProps, show: _show, popper, ...props }) => (

          <div
            {...props}
            style={{
              width: (width > 0) ? `${width}px` : 'calc(100% - 10px)',
              ...props.style,
            }}
          >

            <OverlayContent
              classPrefix={classPrefix}
              list={list}
              activeListItemIndex={activeListItemIndex}
              countryLabelFormatter={countryLabelFormatter}
              flags={flags}
              noMatchesText={noMatchesText}
              maxHeight={listMaxHeight}
              onListItemClick={select}
            />

          </div>

        )}

      </Overlay>
    
    </div>
  );

}
Example #10
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
Bid: React.FC<{
  auction: Auction;
  auctionEnded: boolean;
}> = props => {
  const activeAccount = useAppSelector(state => state.account.activeAccount);
  const { library } = useEthers();
  let { auction, auctionEnded } = props;
  const activeLocale = useActiveLocale();
  const nounsAuctionHouseContract = new NounsAuctionHouseFactory().attach(
    config.addresses.nounsAuctionHouseProxy,
  );

  const account = useAppSelector(state => state.account.activeAccount);

  const bidInputRef = useRef<HTMLInputElement>(null);

  const [bidInput, setBidInput] = useState('');

  const [bidButtonContent, setBidButtonContent] = useState({
    loading: false,
    content: auctionEnded ? <Trans>Settle</Trans> : <Trans>Place bid</Trans>,
  });

  const [showConnectModal, setShowConnectModal] = useState(false);

  const hideModalHandler = () => {
    setShowConnectModal(false);
  };

  const dispatch = useAppDispatch();
  const setModal = useCallback((modal: AlertModal) => dispatch(setAlertModal(modal)), [dispatch]);

  const minBidIncPercentage = useAuctionMinBidIncPercentage();
  const minBid = computeMinimumNextBid(
    auction && new BigNumber(auction.amount.toString()),
    minBidIncPercentage,
  );

  const { send: placeBid, state: placeBidState } = useContractFunction(
    nounsAuctionHouseContract,
    AuctionHouseContractFunction.createBid,
  );
  const { send: settleAuction, state: settleAuctionState } = useContractFunction(
    nounsAuctionHouseContract,
    AuctionHouseContractFunction.settleCurrentAndCreateNewAuction,
  );

  const bidInputHandler = (event: ChangeEvent<HTMLInputElement>) => {
    const input = event.target.value;

    // disable more than 2 digits after decimal point
    if (input.includes('.') && event.target.value.split('.')[1].length > 2) {
      return;
    }

    setBidInput(event.target.value);
  };

  const placeBidHandler = async () => {
    if (!auction || !bidInputRef.current || !bidInputRef.current.value) {
      return;
    }

    if (currentBid(bidInputRef).isLessThan(minBid)) {
      setModal({
        show: true,
        title: <Trans>Insufficient bid amount ?</Trans>,
        message: (
          <Trans>
            Please place a bid higher than or equal to the minimum bid amount of {minBidEth(minBid)}{' '}
            ETH
          </Trans>
        ),
      });
      setBidInput(minBidEth(minBid));
      return;
    }

    const value = utils.parseEther(bidInputRef.current.value.toString());
    const contract = connectContractToSigner(nounsAuctionHouseContract, undefined, library);
    const gasLimit = await contract.estimateGas.createBid(auction.nounId, {
      value,
    });
    placeBid(auction.nounId, {
      value,
      gasLimit: gasLimit.add(10_000), // A 10,000 gas pad is used to avoid 'Out of gas' errors
    });
  };

  const settleAuctionHandler = () => {
    settleAuction();
  };

  const clearBidInput = () => {
    if (bidInputRef.current) {
      bidInputRef.current.value = '';
    }
  };

  // successful bid using redux store state
  useEffect(() => {
    if (!account) return;

    // tx state is mining
    const isMiningUserTx = placeBidState.status === 'Mining';
    // allows user to rebid against themselves so long as it is not the same tx
    const isCorrectTx = currentBid(bidInputRef).isEqualTo(new BigNumber(auction.amount.toString()));
    if (isMiningUserTx && auction.bidder === account && isCorrectTx) {
      placeBidState.status = 'Success';
      setModal({
        title: <Trans>Success</Trans>,
        message: <Trans>Bid was placed successfully!</Trans>,
        show: true,
      });
      setBidButtonContent({ loading: false, content: <Trans>Place bid</Trans> });
      clearBidInput();
    }
  }, [auction, placeBidState, account, setModal]);

  // placing bid transaction state hook
  useEffect(() => {
    switch (!auctionEnded && placeBidState.status) {
      case 'None':
        setBidButtonContent({
          loading: false,
          content: <Trans>Place bid</Trans>,
        });
        break;
      case 'Mining':
        setBidButtonContent({ loading: true, content: <></> });
        break;
      case 'Fail':
        setModal({
          title: <Trans>Transaction Failed</Trans>,
          message: placeBidState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Bid</Trans> });
        break;
      case 'Exception':
        setModal({
          title: <Trans>Error</Trans>,
          message: placeBidState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Bid</Trans> });
        break;
    }
  }, [placeBidState, auctionEnded, setModal]);

  // settle auction transaction state hook
  useEffect(() => {
    switch (auctionEnded && settleAuctionState.status) {
      case 'None':
        setBidButtonContent({
          loading: false,
          content: <Trans>Settle Auction</Trans>,
        });
        break;
      case 'Mining':
        setBidButtonContent({ loading: true, content: <></> });
        break;
      case 'Success':
        setModal({
          title: <Trans>Success</Trans>,
          message: <Trans>Settled auction successfully!</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Settle Auction</Trans> });
        break;
      case 'Fail':
        setModal({
          title: <Trans>Transaction Failed</Trans>,
          message: settleAuctionState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Settle Auction</Trans> });
        break;
      case 'Exception':
        setModal({
          title: <Trans>Error</Trans>,
          message: settleAuctionState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Settle Auction</Trans> });
        break;
    }
  }, [settleAuctionState, auctionEnded, setModal]);

  if (!auction) return null;

  const isDisabled =
    placeBidState.status === 'Mining' || settleAuctionState.status === 'Mining' || !activeAccount;

  const fomoNounsBtnOnClickHandler = () => {
    // Open Fomo Nouns in a new tab
    window.open('https://fomonouns.wtf', '_blank')?.focus();
  };

  const isWalletConnected = activeAccount !== undefined;

  return (
    <>
      {showConnectModal && activeAccount === undefined && (
        <WalletConnectModal onDismiss={hideModalHandler} />
      )}
      <InputGroup>
        {!auctionEnded && (
          <>
            <span className={classes.customPlaceholderBidAmt}>
              {!auctionEnded && !bidInput ? (
                <>
                  Ξ {minBidEth(minBid)}{' '}
                  <span
                    className={
                      activeLocale === 'ja-JP' ? responsiveUiUtilsClasses.disableSmallScreens : ''
                    }
                  >
                    <Trans>or more</Trans>
                  </span>
                </>
              ) : (
                ''
              )}
            </span>
            <FormControl
              className={classes.bidInput}
              type="number"
              min="0"
              onChange={bidInputHandler}
              ref={bidInputRef}
              value={bidInput}
            />
          </>
        )}
        {!auctionEnded ? (
          <Button
            className={auctionEnded ? classes.bidBtnAuctionEnded : classes.bidBtn}
            onClick={auctionEnded ? settleAuctionHandler : placeBidHandler}
            disabled={isDisabled}
          >
            {bidButtonContent.loading ? <Spinner animation="border" /> : bidButtonContent.content}
          </Button>
        ) : (
          <>
            <Col lg={12} className={classes.voteForNextNounBtnWrapper}>
              <Button className={classes.bidBtnAuctionEnded} onClick={fomoNounsBtnOnClickHandler}>
                <Trans>Vote for the next Noun</Trans> ⌐◧-◧
              </Button>
            </Col>
            {/* Only show force settle button if wallet connected */}
            {isWalletConnected && (
              <Col lg={12}>
                <SettleManuallyBtn settleAuctionHandler={settleAuctionHandler} auction={auction} />
              </Col>
            )}
          </>
        )}
      </InputGroup>
    </>
  );
}
Example #11
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
ProposalTransactionFormModal = ({
  show,
  onHide,
  onProposalTransactionAdded,
}: ProposalTransactionFormModalProps) => {
  const [address, setAddress] = useState('');
  const [abi, setABI] = useState<Interface>();
  const [value, setValue] = useState('');
  const [func, setFunction] = useState('');
  const [args, setArguments] = useState<string[]>([]);

  const [isABIUploadValid, setABIUploadValid] = useState<boolean>();
  const [abiFileName, setABIFileName] = useState<string | undefined>('');

  const addressValidator = (s: string) => {
    if (!utils.isAddress(s)) {
      return false;
    }
    // To avoid blocking stepper progress, do not `await`
    populateABIIfExists(s);
    return true;
  };

  const valueValidator = (v: string) => !v || !new BigNumber(v).isNaN();

  const argumentsValidator = (a: string[]) => {
    if (!func) {
      return true;
    }

    try {
      return !!abi?._encodeParams(abi?.functions[func]?.inputs, args);
    } catch {
      return false;
    }
  };

  const setArgument = (index: number, value: string) => {
    const values = [...args];
    values[index] = value;
    setArguments(values);
  };

  let abiErrorTimeout: NodeJS.Timeout;
  const setABIInvalid = () => {
    setABIUploadValid(false);
    setABIFileName(undefined);
    abiErrorTimeout = setTimeout(() => {
      setABIUploadValid(undefined);
    }, 5_000);
  };

  const validateAndSetABI = (file: File | undefined) => {
    if (abiErrorTimeout) {
      clearTimeout(abiErrorTimeout);
    }
    if (!file) {
      return;
    }

    const reader = new FileReader();
    reader.onload = async e => {
      try {
        const abi = e?.target?.result?.toString() ?? '';
        setABI(new Interface(JSON.parse(abi)));
        setABIUploadValid(true);
        setABIFileName(file.name);
      } catch {
        setABIInvalid();
      }
    };
    reader.readAsText(file);
  };

  const getContractInformation = async (address: string) => {
    const response = await fetch(buildEtherscanApiQuery(address));
    const json = await response.json();
    return json?.result?.[0];
  };

  const getABI = async (address: string) => {
    let info = await getContractInformation(address);
    if (info?.Proxy === '1' && utils.isAddress(info?.Implementation)) {
      info = await getContractInformation(info.Implementation);
    }
    return info.ABI;
  };

  const populateABIIfExists = async (address: string) => {
    if (abiErrorTimeout) {
      clearTimeout(abiErrorTimeout);
    }

    try {
      const result = await getABI(address);
      setABI(new Interface(JSON.parse(result)));
      setABIUploadValid(true);
      setABIFileName('etherscan-abi-download.json');
    } catch {
      setABIUploadValid(undefined);
      setABIFileName(undefined);
    }
  };

  const stepForwardOrCallback = () => {
    if (currentStep !== steps.length - 1) {
      return stepForward();
    }
    onProposalTransactionAdded({
      address,
      value: value ? utils.parseEther(value).toString() : '0',
      signature: func,
      calldata: (func && abi?._encodeParams(abi?.functions[func]?.inputs, args)) || '0x',
    });
    clearState();
  };

  const steps = [
    {
      label: 'Address',
      name: 'address',
      validator: () => addressValidator(address),
    },
    {
      label: 'Value',
      name: 'value',
      validator: () => valueValidator(value),
    },
    {
      label: 'Function',
      name: 'function',
    },
    {
      label: 'Arguments',
      name: 'arguments',
      validator: () => argumentsValidator(args),
    },
    {
      label: 'Summary',
      name: 'summary',
    },
  ];

  const { stepForward, stepBackwards, currentStep } = useStepProgress({
    steps,
    startingStep: 0,
  });

  const clearState = () => {
    setAddress('');
    setABI(undefined);
    setValue('');
    setFunction('');
    setArguments([]);
    setABIUploadValid(undefined);
    setABIFileName(undefined);

    for (let i = currentStep; i > 0; i--) {
      stepBackwards();
    }
  };

  return (
    <Modal
      show={show}
      onHide={() => {
        onHide();
        clearState();
      }}
      dialogClassName={classes.transactionFormModal}
      centered
    >
      <Modal.Header closeButton>
        <Modal.Title>
          <Trans>Add a Proposal Transaction</Trans>
        </Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <StepProgressBar className={classes.stepProgressBar} steps={steps} />
        <Step step={0}>
          <label htmlFor="callee-address">
            <Trans>Address (Callee or Recipient)</Trans>
          </label>
          <FormControl
            value={address}
            type="text"
            id="callee-address"
            onChange={e => setAddress(e.target.value)}
          />
        </Step>
        <Step step={1}>
          <label htmlFor="eth-value">
            <Trans>Value in ETH (Optional)</Trans>
          </label>
          <FormControl value={value} id="eth-value" onChange={e => setValue(e.target.value)} />
        </Step>
        <Step step={2}>
          <label htmlFor="function">
            <Trans>Function (Optional)</Trans>
          </label>
          <FormControl
            value={func}
            as="select"
            id="function"
            onChange={e => setFunction(e.target.value)}
          >
            <option className="text-muted">Select Contract Function</option>
            {abi && Object.keys(abi.functions).map(func => <option value={func}>{func}</option>)}
          </FormControl>
          <label style={{ marginTop: '1rem' }} htmlFor="import-abi">
            {abiFileName === 'etherscan-abi-download.json' ? abiFileName : 'ABI'}
          </label>
          <Form.Control
            type="file"
            id="import-abi"
            accept="application/JSON"
            isValid={isABIUploadValid}
            isInvalid={isABIUploadValid === false}
            onChange={(e: ChangeEvent<HTMLInputElement>) => validateAndSetABI(e.target.files?.[0])}
          />
        </Step>
        <Step step={3}>
          {abi?.functions[func]?.inputs?.length ? (
            <FormGroup as={Row}>
              {abi?.functions[func]?.inputs.map((input, i) => (
                <>
                  <FormLabel column sm="3">
                    {input.name}
                  </FormLabel>
                  <Col sm="9">
                    <InputGroup className="mb-2">
                      <InputGroup.Text className={classes.inputGroupText}>
                        {input.type}
                      </InputGroup.Text>
                      <FormControl
                        value={args[i] ?? ''}
                        onChange={e => setArgument(i, e.target.value)}
                      />
                    </InputGroup>
                  </Col>
                </>
              ))}
            </FormGroup>
          ) : (
            <Trans>No arguments required </Trans>
          )}
        </Step>
        <Step step={4}>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Address</Trans>
              </b>
            </Col>
            <Col sm="9" className="text-break">
              <a href={buildEtherscanAddressLink(address)} target="_blank" rel="noreferrer">
                {address}
              </a>
            </Col>
          </Row>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Value</Trans>
              </b>
            </Col>
            <Col sm="9">{value ? `${value} ETH` : <Trans>None</Trans>}</Col>
          </Row>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Function</Trans>
              </b>
            </Col>
            <Col sm="9" className="text-break">
              {func || <Trans>None</Trans>}
            </Col>
          </Row>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Arguments</Trans>
              </b>
            </Col>
            <Col sm="9">
              <hr />
            </Col>
            <Col sm="9">{abi?.functions[func]?.inputs?.length ? '' : <Trans>None</Trans>}</Col>
          </Row>
          {abi?.functions[func]?.inputs.map((input, i) => (
            <Row key={i}>
              <Col sm="3" className={classes.functionName}>
                {i + 1}. {input.name}
              </Col>
              <Col sm="9" className="text-break">
                {args[i]}
              </Col>
            </Row>
          ))}
        </Step>
        <div className="d-flex justify-content-between align-items-center pt-3">
          <Button
            onClick={stepBackwards}
            variant="outline-secondary"
            size="lg"
            disabled={currentStep === 0}
          >
            <Trans>Back</Trans>
          </Button>
          <Button onClick={stepForwardOrCallback} variant="primary" size="lg">
            {currentStep !== steps.length - 1 ? (
              <Trans>Next</Trans>
            ) : (
              <Trans>Add Transaction</Trans>
            )}
          </Button>
        </div>
      </Modal.Body>
    </Modal>
  );
}