debian-image-builder-frontend/src/Components/CreateImageWizard/LabelInput.tsx
regexowl 47b5eb8392 Wizard: Turn required category name into LabelInput prop
This swaps hardcoded value of the required `categoryName` in `LabelInput` for a prop, allowing to customize text for the reuired category independently on the component.
2025-03-31 19:46:52 -05:00

158 lines
3.8 KiB
TypeScript

import React, { useState } from 'react';
import {
Button,
HelperText,
HelperTextItem,
Label,
LabelGroup,
TextInputGroup,
TextInputGroupMain,
TextInputGroupUtilities,
} from '@patternfly/react-core/dist/esm';
import { PlusCircleIcon, TimesIcon } from '@patternfly/react-icons';
import { UnknownAction } from 'redux';
import { StepValidation } from './utilities/useValidation';
import { useAppDispatch } from '../../store/hooks';
type LabelInputProps = {
ariaLabel: string;
placeholder: string;
validator: (value: string) => boolean;
requiredList?: string[] | undefined;
requiredCategoryName?: string;
list: string[] | undefined;
item: string;
addAction: (value: string) => UnknownAction;
removeAction: (value: string) => UnknownAction;
stepValidation: StepValidation;
fieldName: string;
};
const LabelInput = ({
ariaLabel,
placeholder,
validator,
list,
requiredList,
requiredCategoryName,
item,
addAction,
removeAction,
stepValidation,
fieldName,
}: LabelInputProps) => {
const dispatch = useAppDispatch();
const [inputValue, setInputValue] = useState('');
const [errorText, setErrorText] = useState(stepValidation.errors[fieldName]);
const onTextInputChange = (
_event: React.FormEvent<HTMLInputElement>,
value: string
) => {
setInputValue(value);
setErrorText('');
};
const addItem = (value: string) => {
if (list?.includes(value) || requiredList?.includes(value)) {
setErrorText(`${item} already exists.`);
return;
}
if (!validator(value)) {
setErrorText('Invalid format.');
return;
}
dispatch(addAction(value));
setInputValue('');
setErrorText('');
};
const handleKeyDown = (e: React.KeyboardEvent, value: string) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
addItem(value);
}
};
const handleAddItem = (e: React.MouseEvent, value: string) => {
addItem(value);
};
const handleRemoveItem = (e: React.MouseEvent, value: string) => {
dispatch(removeAction(value));
setErrorText('');
};
const handleClear = () => {
setInputValue('');
setErrorText('');
};
return (
<>
<TextInputGroup>
<TextInputGroupMain
placeholder={placeholder}
onChange={onTextInputChange}
value={inputValue}
onKeyDown={(e) => handleKeyDown(e, inputValue)}
/>
<TextInputGroupUtilities>
<Button
variant="plain"
onClick={(e) => handleAddItem(e, inputValue)}
isDisabled={!inputValue}
aria-label={ariaLabel}
>
<PlusCircleIcon className="pf-v5-u-primary-color-100" />
</Button>
<Button
variant="plain"
onClick={handleClear}
isDisabled={!inputValue}
aria-label="Clear input"
>
<TimesIcon />
</Button>
</TextInputGroupUtilities>
</TextInputGroup>
{errorText && (
<HelperText>
<HelperTextItem variant={'error'}>{errorText}</HelperTextItem>
</HelperText>
)}
{requiredList && requiredList.length > 0 && (
<LabelGroup
categoryName={requiredCategoryName}
numLabels={20}
className="pf-v5-u-mt-sm pf-v5-u-w-100"
>
{requiredList.map((item) => (
<Label key={item} isCompact>
{item}
</Label>
))}
</LabelGroup>
)}
<LabelGroup numLabels={20} className="pf-v5-u-mt-sm pf-v5-u-w-100">
{list?.map((item) => (
<Label
key={item}
isCompact
onClose={(e) => handleRemoveItem(e, item)}
>
{item}
</Label>
))}
</LabelGroup>
</>
);
};
export default LabelInput;