Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/components/Button/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ See [API](#api) for all available options.
- **Don't overwhelm your UI** with too many high-emphasis actions. There should
always be one but chances are that having more of them is not necessary.

- Ensure the **button action is well recognazible** across your target audience.
- Ensure the **button action is well recognizable** across your target audience.
This is especially important when using the button [with an icon only](#icon-buttons).

## Priorities
Expand Down
10 changes: 8 additions & 2 deletions src/components/FileInputField/FileInputField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,15 @@ export const FileInputField = React.forwardRef((props, ref) => {
</Text>
</button>
</div>
{helpText && (
{(helpText && !inputGroupContext) && (
<div
className={styles.helpText}
id={`${id}__helpText`}
>
{helpText}
</div>
)}
{validationText && (
{(validationText && !inputGroupContext) && (
<div
className={styles.validationText}
id={`${id}__validationText`}
Expand Down Expand Up @@ -268,6 +268,9 @@ FileInputField.propTypes = {
fullWidth: PropTypes.bool,
/**
* Optional help text.
*
* Help text is never rendered when the component is placed into `InputGroup`.
* If a help text is needed, it must be defined on the `InputGroup` component instead.
*/
helpText: PropTypes.node,
/**
Expand Down Expand Up @@ -321,6 +324,9 @@ FileInputField.propTypes = {
validationState: PropTypes.oneOf(['invalid', 'valid', 'warning']),
/**
* Validation message to be displayed.
*
* Validation text is never rendered when the component is placed into `InputGroup`.
* If a validation text is needed, it must be defined on the `InputGroup` component instead.
*/
validationText: PropTypes.node,
};
Expand Down
24 changes: 22 additions & 2 deletions src/components/InputGroup/InputGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import styles from './InputGroup.module.scss';
export const InputGroup = ({
children,
disabled,
helpTexts,
id,
isLabelVisible,
label,
Expand Down Expand Up @@ -89,7 +90,21 @@ export const InputGroup = ({
{children}
</InputGroupContext.Provider>
</div>
{validationTexts && (
{helpTexts && helpTexts.length > 0 && (
<ul
className={styles.helpText}
id={id && `${id}__helpTexts`}
>
{helpTexts.map((helpText) => (
<li key={helpText}>
<Text blockLevel>
{helpText}
</Text>
</li>
))}
</ul>
)}
{validationTexts && validationTexts.length > 0 && (
<ul
className={styles.validationText}
id={id && `${id}__validationTexts`}
Expand All @@ -111,6 +126,7 @@ export const InputGroup = ({
InputGroup.defaultProps = {
children: undefined,
disabled: false,
helpTexts: undefined,
id: undefined,
isLabelVisible: true,
layout: 'vertical',
Expand All @@ -133,6 +149,10 @@ InputGroup.propTypes = {
* If `true`, the whole input group with all nested inputs and buttons will be disabled.
*/
disabled: PropTypes.bool,
/**
* An array of help texts to be displayed.
*/
helpTexts: PropTypes.arrayOf(PropTypes.node),
/**
* ID of the root HTML element.
*
Expand Down Expand Up @@ -171,7 +191,7 @@ InputGroup.propTypes = {
/**
* An array of validation messages to be displayed.
*/
validationTexts: PropTypes.node,
validationTexts: PropTypes.arrayOf(PropTypes.node),
};

export const InputGroupWithGlobalProps = withGlobalProps(InputGroup, 'InputGroup');
Expand Down
3 changes: 2 additions & 1 deletion src/components/InputGroup/InputGroup.module.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 1. The class name is intentionally singular because it's targeted by other mixins too.
// 1. The class names are intentionally singular because they are also targeted by other mixins.
// 2. Use a block-level display mode to prevent extra white space below grouped inputs in Safari.
// 3. Let wide input groups honor the minimum input width and overflow horizontally without wrapping and distorting
// the inputs.
Expand Down Expand Up @@ -39,6 +39,7 @@
}

// 1.
.helpText,
.validationText {
@include reset.list();
@include foundation.help-text();
Expand Down
83 changes: 69 additions & 14 deletions src/components/InputGroup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,14 @@ See [API](#api) for all available options.
Make sure your inputs fit their container, especially on small screens.

- In the background, InputGroup uses the [`fieldset`][fieldset] element. Not
only it improves the [accessibility] of the group, it also allows you to make
use of its built-in features like disabling all nested inputs or pairing the
group with a form outside. Consult [the MDN docs][fieldset] to learn more.
only does it improve the [accessibility] of the group, it also allows you to
make use of its built-in features like disabling all nested inputs or pairing
the group with a form outside. Consult [the MDN docs][fieldset] to learn more.

- InputGroup currently **supports grouping of**
[TextField](/components/TextField), [SelectField](/components/SelectField),
and [Button](/components/Button) components.
[FileInputField](/components/FileInputField), and [Button](/components/Button)
components.

- To group [Buttons](/components/Button) only, use the
[ButtonGroup](/components/ButtonGroup) component which is designed
Expand Down Expand Up @@ -104,7 +105,7 @@ You can set the `size` property directly on InputGroup to be shared for all
fields and buttons inside the group. This property is then passed over to
individual elements. At the same time, it **cannot be overridden** on the
fields' or buttons' level. While technically possible, from the design point of
view it's undesirable to group elements of totally different types or sizes.
view, it's undesirable to group elements of totally different types or sizes.

## Invisible Label

Expand Down Expand Up @@ -136,9 +137,9 @@ the input.

## Horizontal layout

The default vertical layout is very easy to use and work with. However, there
are situations where horizontal layout suits better — and that's why React UI
supports this kind of layout as well.
The default vertical layout is straightforward to use and work with. However,
there are situations where horizontal layout suits better — and that's why
React UI supports this kind of layout as well.

```docoff-react-preview
<InputGroup
Expand All @@ -150,6 +151,57 @@ supports this kind of layout as well.
</InputGroup>
```

## Help Text

You may provide one or more additional help texts to clarify how the input group
should be filled.

These messages are not semantically tied to the `children` elements, the
connection should be expressed in textual form in the actual message.

⚠️ Help texts passed to input elements' `helpText` prop are ignored within
InputGroup.

```docoff-react-preview
React.createElement(() => {
const [fruit, setFruit] = React.useState('apple');
const options = [
{
label: 'Apple',
value: 'apple',
},
{
label: 'Pear',
value: 'pear',
},
{
label: 'Cherry',
value: 'cherry',
},
];
return (
<InputGroup
label="Your favourite fruit"
helpTexts={[
"Choose one or more kinds of fruit to feel happy.",
]}
>
<SelectField
label="Your favourite fruit"
onChange={(e) => setFruit(e.target.value)}
options={options}
value={fruit}
/>
<TextField
label="Variety"
placeholder="Eg. Golden delicious"
/>
<Button label="Submit" />
</InputGroup>
);
})
```

## States

### Disabled State
Expand All @@ -169,12 +221,15 @@ Validation states visually present the result of validation of the grouped
inputs. Input group's validation state is taken from its child inputs. You
should always **provide validation messages for states other than valid**
directly through `validationTexts` prop so users know what happened and what
action they should take or what options they have. These messages are not
semantically tied to the `children` elements, the connection should be expressed
in textual form in the actual message. The individual `children` elements must
not show any `validationText`, they only show their respective `validationState`.
Validation messages passed to input elements' `validationText` prop will be
ignored.
action they should take or what options they have.

These messages are not semantically tied to the `children` elements, the
connection should be expressed in textual form in the actual message. The
individual `children` elements must not show any `validationText`, they only
show their respective `validationState`.

⚠️ Validation messages passed to input elements' `validationText` prop are
ignored within InputGroup.

👉 While there is a `required` property to visually denote the whole input group
is required, there is no functional effect as there is no such HTML attribute
Expand Down
4 changes: 3 additions & 1 deletion src/components/InputGroup/__tests__/InputGroup.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ test.describe('InputGroup', () => {

const component = await mount(
<InputGroupForTest
helpTexts={['Help text.']}
id={id}
isLabelVisible
label={label}
Expand All @@ -74,7 +75,8 @@ test.describe('InputGroup', () => {
await expect(component.getByText(label).first()).toHaveAttribute('id', `${id}__label`);
await expect(component.getByText(label).last()).toHaveAttribute('id', `${id}__displayLabel`);
await expect(component.locator(`div[id=${id}__group]`)).not.toBeEmpty();
await expect(component.getByRole('list')).toHaveAttribute('id', `${id}__validationTexts`);
await expect(component.getByRole('list').first()).toHaveAttribute('id', `${id}__helpTexts`);
await expect(component.getByRole('list').last()).toHaveAttribute('id', `${id}__validationTexts`);
});
});

Expand Down
9 changes: 6 additions & 3 deletions src/components/SelectField/SelectField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const SelectField = React.forwardRef((props, ref) => {
<div className={styles.bottomLine} />
)}
</div>
{helpText && (
{(helpText && !inputGroupContext) && (
<div
className={styles.helpText}
id={id && `${id}__helpText`}
Expand Down Expand Up @@ -156,6 +156,9 @@ SelectField.propTypes = {
fullWidth: PropTypes.bool,
/**
* Optional help text.
*
* Help text is never rendered when the component is placed into `InputGroup`.
* If a help text is needed, it must be defined on the `InputGroup` component instead.
*/
helpText: PropTypes.node,
/**
Expand Down Expand Up @@ -249,8 +252,8 @@ SelectField.propTypes = {
/**
* Validation message to be displayed.
*
* Validation text is never rendered when the component is placed into `InputGroup`. Instead, the `InputGroup`
* component itself renders all validation texts of its nested components.
* Validation text is never rendered when the component is placed into `InputGroup`.
* If a validation text is needed, it must be defined on the `InputGroup` component instead.
*/
validationText: PropTypes.node,
/**
Expand Down
9 changes: 6 additions & 3 deletions src/components/TextField/TextField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const TextField = React.forwardRef((props, ref) => {
<div className={styles.bottomLine} />
)}
</div>
{helpText && (
{(helpText && !inputGroupContext) && (
<div
className={styles.helpText}
id={id && `${id}__helpText`}
Expand Down Expand Up @@ -132,6 +132,9 @@ TextField.propTypes = {
fullWidth: PropTypes.bool,
/**
* Optional help text.
*
* Help text is never rendered when the component is placed into `InputGroup`.
* If a help text is needed, it must be defined on the `InputGroup` component instead.
*/
helpText: PropTypes.node,
/**
Expand Down Expand Up @@ -185,8 +188,8 @@ TextField.propTypes = {
/**
* Validation message to be displayed.
*
* Validation text is never rendered when the component is placed into `InputGroup`. Instead, the `InputGroup`
* component itself renders all validation texts of its nested components.
* Validation text is never rendered when the component is placed into `InputGroup`.
* If a validation text is needed, it must be defined on the `InputGroup` component instead.
*/
validationText: PropTypes.node,
/**
Expand Down
Loading