Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { FileUpload, Form, FormGroup } from '@patternfly/react-core';
import { FileUpload, Form, FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core';

export const TextFileUploadWithRestrictions: React.FunctionComponent = () => {
const [value, setValue] = React.useState('');
Expand Down Expand Up @@ -35,12 +35,7 @@ export const TextFileUploadWithRestrictions: React.FunctionComponent = () => {

return (
<Form>
<FormGroup
fieldId="text-file-with-restrictions-example"
helperText="Upload a CSV file"
helperTextInvalid="Must be a CSV file no larger than 1 KB"
validated={isRejected ? 'error' : 'default'}
>
<FormGroup fieldId="text-file-with-restrictions-example">
<FileUpload
id="text-file-with-restrictions-example"
type="text"
Expand All @@ -62,6 +57,13 @@ export const TextFileUploadWithRestrictions: React.FunctionComponent = () => {
validated={isRejected ? 'error' : 'default'}
browseButtonText="Upload"
/>
<FormHelperText>
<HelperText>
<HelperTextItem variant={isRejected ? 'error' : 'default'}>
{isRejected ? 'Must be a CSV file no larger than 1 KB' : 'Upload a CSV file'}
</HelperTextItem>
</HelperText>
</FormHelperText>
</FormGroup>
</Form>
);
Expand Down
61 changes: 1 addition & 60 deletions packages/react-core/src/components/Form/FormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import styles from '@patternfly/react-styles/css/components/Form/form';
import { ASTERISK } from '../../helpers/htmlConstants';
import { css } from '@patternfly/react-styles';
import { ValidatedOptions } from '../../helpers/constants';
import { GenerateId } from '../../helpers/GenerateId/GenerateId';

export interface FormGroupProps extends Omit<React.HTMLProps<HTMLDivElement>, 'label'> {
Expand All @@ -18,28 +17,12 @@ export interface FormGroupProps extends Omit<React.HTMLProps<HTMLDivElement>, 'l
labelIcon?: React.ReactElement;
/** Sets the FormGroup required. */
isRequired?: boolean;
/**
* Sets the FormGroup validated. If you set to success, text color of helper text will be modified to indicate valid state.
* If set to error, text color of helper text will be modified to indicate error state.
* If set to warning, text color of helper text will be modified to indicate warning state.
*/
validated?: 'success' | 'warning' | 'error' | 'default';
/** Sets the FormGroup isInline. */
isInline?: boolean;
/** Sets the FormGroupControl to be stacked */
isStack?: boolean;
/** Removes top spacer from label. */
hasNoPaddingTop?: boolean;
/** Helper text regarding the field. It can be a simple text or an object. */
helperText?: React.ReactNode;
/** Flag to position the helper text before the field. False by default */
isHelperTextBeforeField?: boolean;
/** Helper text after the field when the field is invalid. It can be a simple text or an object. */
helperTextInvalid?: React.ReactNode;
/** Icon displayed to the left of the helper text. */
helperTextIcon?: React.ReactNode;
/** Icon displayed to the left of the helper text when the field is invalid. */
helperTextInvalidIcon?: React.ReactNode;
/** ID of an individual field or a group of multiple fields. Required when a role of "group" or "radiogroup" is passed in.
* If only one field is included, its ID attribute and this prop must be the same.
*/
Expand All @@ -57,53 +40,13 @@ export const FormGroup: React.FunctionComponent<FormGroupProps> = ({
labelInfo,
labelIcon,
isRequired = false,
validated = 'default',
isInline = false,
hasNoPaddingTop = false,
isStack = false,
helperText,
isHelperTextBeforeField = false,
helperTextInvalid,
helperTextIcon,
helperTextInvalidIcon,
fieldId,
role,
...props
}: FormGroupProps) => {
const validHelperText =
typeof helperText !== 'string' ? (
helperText
) : (
<div
className={css(
styles.formHelperText,
validated === ValidatedOptions.success && styles.modifiers.success,
validated === ValidatedOptions.warning && styles.modifiers.warning
)}
id={`${fieldId}-helper`}
aria-live="polite"
>
{helperTextIcon && <span className={css(styles.formHelperTextIcon)}>{helperTextIcon}</span>}
{helperText}
</div>
);

const inValidHelperText =
typeof helperTextInvalid !== 'string' ? (
helperTextInvalid
) : (
<div className={css(styles.formHelperText, styles.modifiers.error)} id={`${fieldId}-helper`} aria-live="polite">
{helperTextInvalidIcon && <span className={css(styles.formHelperTextIcon)}>{helperTextInvalidIcon}</span>}
{helperTextInvalid}
</div>
);

const showValidHelperTxt = (validationType: 'success' | 'warning' | 'error' | 'default') =>
validationType !== ValidatedOptions.error && helperText ? validHelperText : '';

const helperTextToDisplay =
validated === ValidatedOptions.error && helperTextInvalid ? inValidHelperText : showValidHelperTxt(validated);

const isGroupOrRadioGroup = role === 'group' || role === 'radiogroup';
const LabelComponent = isGroupOrRadioGroup ? 'span' : 'label';

Expand All @@ -124,7 +67,7 @@ export const FormGroup: React.FunctionComponent<FormGroupProps> = ({

return (
<GenerateId>
{randomId => (
{(randomId) => (
<div
className={css(styles.formGroup, className)}
{...(role && { role })}
Expand Down Expand Up @@ -156,9 +99,7 @@ export const FormGroup: React.FunctionComponent<FormGroupProps> = ({
isStack && styles.modifiers.stack
)}
>
{isHelperTextBeforeField && helperTextToDisplay}
{children}
{!isHelperTextBeforeField && helperTextToDisplay}
</div>
</div>
)}
Expand Down
38 changes: 7 additions & 31 deletions packages/react-core/src/components/Form/FormHelperText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,19 @@ import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Form/form';

export interface FormHelperTextProps extends React.HTMLProps<HTMLDivElement> {
/** Content rendered inside the Helper Text Item */
/** Content rendered inside the helper text wrapper */
children?: React.ReactNode;
/** Adds error styling to the Helper Text * */
isError?: boolean;
/** Hides the helper text * */
isHidden?: boolean;
/** Additional classes added to the Helper Text Item */
/** Additional classes added to the helper text wrapper */
className?: string;
/** Icon displayed to the left of the helper text. */
icon?: React.ReactNode;
/** Component type of the form helper text */
component?: 'p' | 'div';
}

export const FormHelperText: React.FunctionComponent<FormHelperTextProps> = ({
children = null,
isError = false,
isHidden = true,
className = '',
icon = null,
component = 'p',
...props
}: FormHelperTextProps) => {
const Component = component as any;
return (
<Component
className={css(
styles.formHelperText,
isError && styles.modifiers.error,
isHidden && styles.modifiers.hidden,
className
)}
{...props}
>
{icon && <span className={css(styles.formHelperTextIcon)}>{icon}</span>}
{children}
</Component>
);
};
}: FormHelperTextProps) => (
<div className={css(styles.formHelperText, className)} {...props}>
{children}
</div>
);
FormHelperText.displayName = 'FormHelperText';
101 changes: 6 additions & 95 deletions packages/react-core/src/components/Form/__tests__/FormGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('FormGroup', () => {

test('should render default form group variant', () => {
const { asFragment } = render(
<FormGroup label="label" fieldId="label-id" helperText="this is helper text">
<FormGroup label="label" fieldId="label-id">
<input id="label-id" />
</FormGroup>
);
Expand All @@ -21,7 +21,7 @@ describe('FormGroup', () => {

test('should render inline form group variant', () => {
render(
<FormGroup isInline label="label" fieldId="label-id" helperText="this is helper text">
<FormGroup isInline label="label" fieldId="label-id">
<input id="label-id" />
</FormGroup>
);
Expand All @@ -30,13 +30,7 @@ describe('FormGroup', () => {

test('should render no padding-top form group variant', () => {
render(
<FormGroup
hasNoPaddingTop
label="label"
fieldId="label-id"
helperText="this is helper text"
data-testid="form-group-test-id"
>
<FormGroup hasNoPaddingTop label="label" fieldId="label-id" data-testid="form-group-test-id">
<input id="label-id" />
</FormGroup>
);
Expand Down Expand Up @@ -80,30 +74,10 @@ describe('FormGroup', () => {
expect(screen.getByText('label')).toBeInTheDocument();
});

test('should render form group variant with node helperText', () => {
render(
<FormGroup label="Label" fieldId="label-id" helperText={<h1>Header</h1>}>
<input id="label-id" />
</FormGroup>
);

expect(screen.getByRole('heading', { name: 'Header' })).toBeInTheDocument();
});

test('should render form group variant with function helperText', () => {
render(
<FormGroup label="Label" fieldId="label-id" helperText={returnFunction()}>
<input id="label-id" />
</FormGroup>
);

expect(screen.getByText('label')).toBeInTheDocument();
});

test('should render horizontal form group variant', () => {
const { asFragment } = render(
<Form isHorizontal>
<FormGroup label="label" fieldId="label-id" helperText="this is helperText">
<FormGroup label="label" fieldId="label-id">
<input id="label-id" />
</FormGroup>
</Form>
Expand All @@ -114,18 +88,7 @@ describe('FormGroup', () => {
test('should render stacked horizontal form group variant', () => {
const { asFragment } = render(
<Form isHorizontal>
<FormGroup label="label" fieldId="label-id" isStack helperText="this is helperText">
<input id="label-id" />
</FormGroup>
</Form>
);
expect(asFragment()).toMatchSnapshot();
});

test('should render helper text above input', () => {
const { asFragment } = render(
<Form isHorizontal>
<FormGroup label="label" fieldId="label-id" helperText="this is helperText" isHelperTextBeforeField>
<FormGroup label="label" fieldId="label-id" isStack>
<input id="label-id" />
</FormGroup>
</Form>
Expand All @@ -142,58 +105,6 @@ describe('FormGroup', () => {
expect(asFragment()).toMatchSnapshot();
});

test('should render form group invalid variant', () => {
const { asFragment } = render(
<FormGroup label="label" fieldId="label-id" validated={'error'} helperTextInvalid="Invalid FormGroup">
<input id="id" />
</FormGroup>
);
expect(asFragment()).toMatchSnapshot();
});

test('should render form group validated success variant', () => {
const { asFragment } = render(
<FormGroup
label="label"
fieldId="label-id"
validated={ValidatedOptions.success}
helperText="Validated FormGroup"
data-testid="form-group-test-id"
>
<input id="id" />
</FormGroup>
);

expect(screen.getByTestId('form-group-test-id').querySelector('input + div')).toHaveClass('pf-m-success');
expect(asFragment()).toMatchSnapshot();
});

test('should render form group validated error variant', () => {
const { asFragment } = render(
<FormGroup label="label" fieldId="label-id" validated={ValidatedOptions.error} helperText="Validated FormGroup">
<input id="id" />
</FormGroup>
);
expect(asFragment()).toMatchSnapshot();
});

test('should render form group validated warning variant', () => {
const { asFragment } = render(
<FormGroup
label="label"
fieldId="label-id"
validated={ValidatedOptions.warning}
helperText="Validated FormGroup"
data-testid="form-group-test-id"
>
<input id="id" />
</FormGroup>
);

expect(screen.getByTestId('form-group-test-id').querySelector('input + div')).toHaveClass('pf-m-warning');
expect(asFragment()).toMatchSnapshot();
});

test('should render correctly when label is not a string with Children = Array', () => {
const { asFragment } = render(
<FormGroup fieldId="id" label={returnFunction()}>
Expand Down Expand Up @@ -245,7 +156,7 @@ describe('FormGroup', () => {
const inputs = screen.getAllByRole('textbox');

await user.click(labelElement);
inputs.forEach(input => {
inputs.forEach((input) => {
expect(input).not.toHaveFocus();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,7 @@ import { FormHelperText } from '../FormHelperText';

describe('FormHelperText', () => {
test('renders with PatternFly Core styles', () => {
const { asFragment } = render(
<FormHelperText isError isHidden={false}>
test
</FormHelperText>
);
expect(asFragment()).toMatchSnapshot();
});

test('renders with icon', () => {
const { asFragment } = render(
<FormHelperText isError isHidden={false} icon={<ExclamationCircleIcon />}>
test
</FormHelperText>
);
const { asFragment } = render(<FormHelperText>test</FormHelperText>);
expect(asFragment()).toMatchSnapshot();
});

Expand Down
Loading