@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 |
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 |
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 |
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 |
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>
);
}