@patternfly/react-core#HelperText JavaScript Examples

The following examples show how to use @patternfly/react-core#HelperText. 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: ImageOutputCheckbox.js    From edge-frontend with Apache License 2.0 6 votes vote down vote up
WarningInstallerHelperText = () => (
  <HelperText className="pf-u-ml-lg" hasIcon>
    <HelperTextItem className="pf-u-pb-md" variant="warning" hasIcon>
      Creating an installable version of your image increases the build time and
      is not needed for updating existing devices. <br />
      You can create an installable version of this image later if you continue
      with this option
    </HelperTextItem>
  </HelperText>
)
Example #2
Source File: EditModal.js    From edge-frontend with Apache License 2.0 5 votes vote down vote up
EditModal = ({ openModal, isOpen, id, name, baseURL, reloadData }) => {
  const dispatch = useDispatch();

  const handleEditRepository = (values) => {
    const statusMessages = {
      onSuccess: {
        title: 'Success',
        description: `${name} has been renamed to ${values.name} successfully`,
      },
      onError: { title: 'Error', description: 'Failed to edit a repository' },
    };
    apiWithToast(dispatch, () => editCustomRepository(values), statusMessages);
  };
  const editSchema = {
    fields: [
      {
        component: 'plain-text',
        name: 'title',
        label: 'Update information about this custom repository.',
      },
      {
        component: 'text-field',
        name: 'name',
        label: 'Name',
        placeholder: 'Repository name',
        helperText:
          'Can only contain letters, numbers, spaces, hyphens ( - ), and underscores( _ ).',
        isRequired: true,
        validate: [{ type: validatorTypes.REQUIRED }],
      },
      {
        component: 'textarea',
        name: 'baseURL',
        label: 'BaseURL',
        placeholder: 'https://',
        helperText: (
          <HelperText hasIcon>
            <HelperTextItem className="pf-u-pb-md" variant="warning" hasIcon>
              If you change the repo URL, you may not have access to the
              packages that were used to build images that reference this
              repository.
            </HelperTextItem>
          </HelperText>
        ),

        isRequired: true,
        validate: [
          { type: validatorTypes.REQUIRED },
          { type: validatorTypes.URL, message: 'Must be a valid url' },
        ],
      },
    ],
  };

  return (
    <Modal
      title="Edit repository"
      isOpen={isOpen}
      openModal={() => openModal({ type: 'edit' })}
      submitLabel="Update"
      schema={editSchema}
      initialValues={{ id, name, baseURL }}
      onSubmit={handleEditRepository}
      reloadData={reloadData}
    />
  );
}
Example #3
Source File: ImageOutputCheckbox.js    From edge-frontend with Apache License 2.0 5 votes vote down vote up
ImageOutputCheckbox = (props) => {
  const { getState } = useFormApi();
  const { input } = useFieldApi(props);
  const toggleCheckbox = useCallback(
    (checked, event) => {
      input.onChange(
        checked
          ? [...input.value, event.currentTarget.id]
          : input.value.filter((item) => item !== event.currentTarget.id)
      );
    },
    [input.onChange]
  );

  return (
    <FormGroup
      label="Output type"
      isHelperTextBeforeField
      hasNoPaddingTop
      isRequired
      isStack
    >
      {props.options.map(({ value, label }, index) => (
        <Fragment key={index}>
          <Checkbox
            label={label}
            id={value}
            isChecked={input.value.includes(value)}
            onChange={toggleCheckbox}
            isDisabled={value === 'rhel-edge-commit'}
          />
          <TextContent>
            {getState()?.initialValues?.isUpdate &&
            value === 'rhel-edge-installer' ? (
              <WarningInstallerHelperText />
            ) : (
              <HelperText className="pf-u-ml-lg pf-u-pb-sm">
                <HelperTextItem variant="indeterminate">
                  {outputHelperText[value]}
                </HelperTextItem>
              </HelperText>
            )}
          </TextContent>
          {value === 'rhel-edge-installer' && (
            <Fragment>
              <Text component={TextVariants.small}>
                <Text
                  target="_blank"
                  href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html-single/composing_installing_and_managing_rhel_for_edge_images/index#edge-how-to-compose-and-deploy-a-rhel-for-edge-image_introducing-rhel-for-edge-images"
                  component={TextVariants.a}
                  isVisitedLink
                >
                  Learn more about image types.
                  <ExternalLinkAltIcon className="pf-u-ml-sm" />
                </Text>
              </Text>
            </Fragment>
          )}
        </Fragment>
      ))}
    </FormGroup>
  );
}
Example #4
Source File: Packages.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
Packages = ({ defaultArch, ...props }) => {
  const { change, getState } = useFormApi();
  const { input } = useFieldApi(props);
  const [availableOptions, setAvailableOptions] = React.useState([]);
  const [chosenOptions, setChosenOptions] = React.useState([]);
  const [availableInputValue, setAvailableInputValue] = React.useState('');
  const [enterPressed, setEnterPressed] = useState(false);
  const [hasMoreResults, setHasMoreResults] = useState(false);
  const [scrollTo, setScrollTo] = useState(null);
  const [hasNoSearchResults, setHasNoSearchResults] = useState(false);
  const [searchFocused, setSearchFocused] = useState(false);
  const [exactMatch, setExactMatch] = useState(false);

  useEffect(() => {
    const loadedPackages = getState()?.values?.[input.name] || [];
    setChosenOptions(sortedOptions(mapPackagesToOptions(loadedPackages)));

    const availableSearchInput = document.querySelector(
      '[aria-label="available search input"]'
    );

    availableSearchInput?.addEventListener('keydown', handleSearchOnEnter);
    return () =>
      availableSearchInput.removeEventListener('keydown', handleSearchOnEnter);
  }, []);

  useEffect(() => {
    if (enterPressed) {
      handlePackageSearch();
      setEnterPressed(false);
    }
  }, [enterPressed]);

  useEffect(() => {
    scrollToAddedPackages();
  }, [scrollTo]);

  const handlePackageSearch = async () => {
    if (availableInputValue === '') {
      return;
    }

    const { data, meta } = await getPackages(
      getState()?.values?.release || 'rhel-85',
      getState()?.values?.architecture || defaultArch,
      availableInputValue
    );

    if (!data) {
      setHasNoSearchResults(true);
      setHasMoreResults(false);
      setAvailableOptions([]);
      return;
    }

    if (meta.count > 100) {
      setHasNoSearchResults(false);
      setHasMoreResults(true);

      let exactMatchIndex = null;
      data.forEach(({ name }, index) => {
        if (name === availableInputValue) {
          exactMatchIndex = index;
          return;
        }
      });

      const isNotChosen = !chosenOptions.find(
        (option) => option.name === data[exactMatchIndex].name
      );

      if (exactMatchIndex !== null && isNotChosen) {
        setExactMatch(true);
        setAvailableOptions(mapPackagesToOptions([data[exactMatchIndex]]));
        return;
      }

      setExactMatch(false);
      setAvailableOptions([]);
      return;
    } else {
      setHasMoreResults(false);

      setExactMatch(false);
    }

    const removeChosenPackages = data.filter(
      (newPkg) =>
        !chosenOptions.find((chosenPkg) => chosenPkg.name === newPkg.name)
    );
    setAvailableOptions(
      sortedOptions(mapPackagesToOptions(removeChosenPackages))
    );

    setHasNoSearchResults(false);
  };

  const handleSearchOnEnter = (e) => {
    if (e.key === 'Enter') {
      e.stopPropagation();
      setEnterPressed(true);
    }
  };

  const scrollToAddedPackages = () => {
    if (!scrollTo) {
      return;
    }

    const destination = document.querySelector(
      `.pf-m-${scrollTo.pane} .pf-c-dual-list-selector__menu`
    );
    scrollTo.pkgs.forEach((pkg) =>
      document
        .getElementById(`package-${pkg.name}`)
        .closest('.pf-c-dual-list-selector__list-item-row')
        .classList.add('pf-u-background-color-disabled-color-300')
    );
    setTimeout(() => {
      scrollTo.pkgs.forEach((pkg) =>
        document
          .getElementById(`package-${pkg.name}`)
          .closest('.pf-c-dual-list-selector__list-item-row')
          .classList.remove('pf-u-background-color-disabled-color-300')
      );
    }, 400);
    destination.scrollTop = scrollTo.scrollHeight;

    setScrollTo(null);
  };

  const moveSelected = (fromAvailable) => {
    const sourceOptions = fromAvailable ? availableOptions : chosenOptions;
    let destinationOptions = fromAvailable ? chosenOptions : availableOptions;

    const selectedOptions = [];
    for (let i = 0; i < sourceOptions.length; i++) {
      const option = sourceOptions[i];
      if (option.selected && option.isVisible) {
        selectedOptions.push(option);
        sourceOptions.splice(i, 1);
        option.selected = false;
        i--;
      }
    }

    destinationOptions = sortedOptions([
      ...destinationOptions,
      ...selectedOptions,
    ]);

    const packageHeight = 61;
    const scrollHeight =
      destinationOptions.findIndex(
        (pkg) => pkg.name === selectedOptions[0].name
      ) * packageHeight;

    if (fromAvailable) {
      setAvailableOptions(sortedOptions([...sourceOptions]));
      setChosenOptions(destinationOptions);
      change(input.name, destinationOptions);
    } else {
      setChosenOptions(sortedOptions([...sourceOptions]));
      setAvailableOptions(destinationOptions);
      change(input.name, [...sourceOptions]);
    }
    setScrollTo({
      pkgs: selectedOptions,
      pane: fromAvailable ? 'chosen' : 'available',
      scrollHeight,
    });
    setExactMatch(false);
  };

  const moveAll = (fromAvailable) => {
    if (fromAvailable) {
      setChosenOptions(
        sortedOptions([
          ...availableOptions.filter((x) => x.isVisible),
          ...chosenOptions,
        ])
      );
      setAvailableOptions(
        sortedOptions([...availableOptions.filter((x) => !x.isVisible)])
      );
      change(input.name, [...availableOptions, ...chosenOptions]);
    } else {
      setAvailableOptions(
        sortedOptions([
          ...chosenOptions.filter((x) => x.isVisible),
          ...availableOptions,
        ])
      );
      setChosenOptions(
        sortedOptions([...chosenOptions.filter((x) => !x.isVisible)])
      );
      change(input.name, []);
    }
    setExactMatch(false);
  };

  const onOptionSelect = (_event, index, isChosen) => {
    if (isChosen) {
      const newChosen = [...chosenOptions];
      newChosen[index].selected = !chosenOptions[index].selected;
      setChosenOptions(sortedOptions(newChosen));
    } else {
      const newAvailable = [...availableOptions];
      newAvailable[index].selected = !availableOptions[index].selected;
      setAvailableOptions(sortedOptions(newAvailable));
    }
  };

  const buildSearchInput = (isAvailable) => {
    const onChange = (value) => {
      setAvailableInputValue(value);
      if (!isAvailable) {
        const toFilter = [...chosenOptions];
        toFilter.forEach((option) => {
          option.isVisible =
            value === '' ||
            option.name.toLowerCase().includes(value.toLowerCase());
        });
      }
    };

    return (
      <>
        <InputGroup>
          <TextInput
            id={`${isAvailable ? 'available' : 'chosen'}-textinput`}
            type="search"
            onChange={onChange}
            placeholder="Search for packages"
            onFocus={() => setSearchFocused(true)}
            onBlur={() => setSearchFocused(false)}
            validated={
              hasMoreResults && isAvailable && !searchFocused ? 'warning' : ''
            }
            aria-label={
              isAvailable ? 'available search input' : 'chosen search input'
            }
            data-testid={
              isAvailable ? 'available-search-input' : 'chosen-search-input'
            }
          />
          {isAvailable ? (
            <Button
              onClick={handlePackageSearch}
              isDisabled={!isAvailable}
              variant="control"
              aria-label="search button for search input"
              data-testid="package-search"
            >
              <SearchIcon />
            </Button>
          ) : (
            <InputGroupText>
              <SearchIcon className="pf-u-ml-xs pf-u-mr-xs" />
            </InputGroupText>
          )}
        </InputGroup>
        {hasMoreResults && isAvailable && (
          <HelperText>
            <HelperTextItem variant="warning">
              Over 100 results found. Refine your search.
            </HelperTextItem>
          </HelperText>
        )}
      </>
    );
  };

  const displayPackagesFrom = (options, isChosen) => {
    return options.map((option, index) => {
      return option.isVisible ? (
        <DualListSelectorListItem
          key={index}
          isSelected={option.selected}
          id={`composable-option-${index}`}
          onOptionSelect={(e) => onOptionSelect(e, index, isChosen)}
        >
          <TextContent>
            <span
              id={`package-${option.name}`}
              className="pf-c-dual-list-selector__item-text"
            >
              {option.name}
            </span>
            <small>{option.summary}</small>
          </TextContent>
        </DualListSelectorListItem>
      ) : null;
    });
  };

  const selectedStatus = (options) => {
    const totalItemNum = options.filter((x) => x.isVisible).length;
    const selectedItemNum = options.filter(
      (x) => x.selected && x.isVisible
    ).length;
    return selectedItemNum > 0
      ? `${selectedItemNum} of ${totalItemNum} items selected`
      : `${totalItemNum} ${totalItemNum > 1 ? 'items' : 'item'}`;
  };

  return (
    <DualListSelector>
      <DualListSelectorPane
        title="Available packages"
        status={selectedStatus(availableOptions)}
        searchInput={buildSearchInput(true)}
      >
        <DualListSelectorList
          style={{ height: '290px' }}
          data-testid="available-packages-list"
        >
          {availableOptions.length > 0 && !exactMatch ? (
            displayPackagesFrom(availableOptions, false)
          ) : hasNoSearchResults ? (
            <NoResultsText
              heading="No Results Found"
              body="Adjust your search and try again"
            />
          ) : hasMoreResults ? (
            <>
              {exactMatch && (
                <HelperText>
                  <HelperTextItem
                    className="pf-u-ml-md pf-u-mt-md"
                    variant="indeterminate"
                  >
                    Exact match
                  </HelperTextItem>
                </HelperText>
              )}
              {exactMatch && displayPackagesFrom(availableOptions, false)}
              {exactMatch && (
                <Divider
                  className="pf-u-mt-md"
                  inset={{ default: 'insetMd' }}
                />
              )}
              <NoResultsText
                heading="Too many results to display"
                body="Please make the search more specific and try again"
              />
            </>
          ) : (
            <EmptyText text="Search above to add additional packages to your image." />
          )}
        </DualListSelectorList>
      </DualListSelectorPane>

      <DualListSelectorControlsWrapper aria-label="Selector controls">
        <DualListSelectorControl
          isDisabled={!availableOptions.some((option) => option.selected)}
          onClick={() => moveSelected(true)}
          aria-label="Add selected"
          tooltipContent="Add selected"
        >
          <AngleRightIcon />
        </DualListSelectorControl>
        <DualListSelectorControl
          isDisabled={availableOptions.length === 0}
          onClick={() => moveAll(true)}
          aria-label="Add all"
          tooltipContent="Add all"
        >
          <AngleDoubleRightIcon />
        </DualListSelectorControl>
        <DualListSelectorControl
          isDisabled={chosenOptions.length === 0}
          onClick={() => moveAll(false)}
          aria-label="Remove all"
          tooltipContent="Remove all"
        >
          <AngleDoubleLeftIcon />
        </DualListSelectorControl>
        <DualListSelectorControl
          onClick={() => moveSelected(false)}
          isDisabled={!chosenOptions.some((option) => option.selected)}
          aria-label="Remove selected"
          tooltipContent="Remove selected"
        >
          <AngleLeftIcon />
        </DualListSelectorControl>
      </DualListSelectorControlsWrapper>

      <DualListSelectorPane
        title="Chosen packages"
        status={selectedStatus(chosenOptions)}
        searchInput={buildSearchInput(false)}
        isChosen
      >
        <DualListSelectorList
          style={{ height: '290px' }}
          data-testid="chosen-packages-list"
        >
          {chosenOptions.length === 0 ? (
            <EmptyText text="No packages added." />
          ) : chosenOptions.filter((option) => option.isVisible).length > 0 ? (
            displayPackagesFrom(chosenOptions, true)
          ) : (
            <NoResultsText
              heading="No Results Found"
              body="Adjust your search and try again"
            />
          )}
        </DualListSelectorList>
      </DualListSelectorPane>
    </DualListSelector>
  );
}