Skip to content

Commit

Permalink
[Form controls] Added alert icon indicators for invalid state (#5738)
Browse files Browse the repository at this point in the history
* [EuiFormControlLayout] Added `isInvalid` prop which is passed through to `EuiFormControlLayoutIcon` which renders the `alert` icon in red

* Passed the `isInvalid` through for some form controls
- EuiFieldNumber, EuiFieldPassword, EuiFieldText, EuiSelect, EuiSuperSelect
- EuiFieldSearch attempts to create a new class for the number of icons

* Added a `getFormControlClassNameForIconCount` localized service for couting icons

* Change className output from rendering component-specific to generic to reduce output syles

* [EuiFormControlLayout] Added `isDropdown` to create and control the arrow down icon

* [EuiColorPicker] Fix usage of EuiFormControlLayout to not double

* [EuiSelect] & [EuiSuperSelect] Update to new EuiFormControlLayout props
- Fixed EuiSuperSelect border radius with append/prepend #5442
- Fixed EuiSuperSelect not respecting `readOnly` #3510

* [EuiValidatableControl] Add `aria-invalid`
  • Loading branch information
cchaos authored Mar 30, 2022
1 parent fe993d8 commit d31f753
Show file tree
Hide file tree
Showing 47 changed files with 768 additions and 703 deletions.
2 changes: 1 addition & 1 deletion src-docs/src/views/color_picker/kitchen_sink.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default () => {
return (
<React.Fragment>
{/* DisplayToggles wrapper for Docs only */}
<DisplayToggles canLoading={false} canPrepend={true} canAppend={true}>
<DisplayToggles canLoading={false} canPrepend canAppend canClear>
<EuiColorPicker color={color} onChange={setColor} />
</DisplayToggles>
<EuiSpacer />
Expand Down
64 changes: 24 additions & 40 deletions src-docs/src/views/form_controls/display_toggles.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { cloneElement, useState, Fragment } from 'react';
import PropTypes from 'prop-types';

import {
EuiFlexGroup,
Expand All @@ -13,18 +12,19 @@ import {
} from '../../../../src/components';

export const DisplayToggles = ({
canIsDisabled,
canDisabled,
canReadOnly,
canLoading,
canCompressed,
canFullWidth,
canPrepend,
canAppend,
canInvalid,
canIsDisabled = false,
canDisabled = true,
canReadOnly = true,
canLoading = true,
canCompressed = true,
canFullWidth = true,
canInvalid = true,
canPrepend = false,
canAppend = false,
canClear = false,
children,
extras,
spacerSize,
spacerSize = 'l',
}) => {
const [disabled, setDisabled] = useState(false);
const [readOnly, setReadOnly] = useState(false);
Expand All @@ -35,6 +35,7 @@ export const DisplayToggles = ({
const [append, setAppend] = useState(false);
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [invalid, setInvalid] = useState(false);
const [isClearable, setIsClearable] = useState(false);

const canProps = {};
if (canDisabled) canProps.disabled = disabled;
Expand All @@ -46,6 +47,7 @@ export const DisplayToggles = ({
if (canPrepend && prepend) canProps.prepend = 'Prepend';
if (canAppend && append) canProps.append = 'Append';
if (canInvalid) canProps.isInvalid = invalid;
if (canClear) canProps.isClearable = isClearable;

return (
<Fragment>
Expand Down Expand Up @@ -135,7 +137,7 @@ export const DisplayToggles = ({
compressed{' '}
<EuiToolTip content="Compressed usages are very specific. Click to view full compressed documentation">
<a href="#/forms/compressed-forms">
<EuiIcon type="help" />
<EuiIcon type="help" aria-label="help" />
</a>
</EuiToolTip>
</span>
Expand Down Expand Up @@ -165,6 +167,16 @@ export const DisplayToggles = ({
/>
</EuiFlexItem>
)}
{canClear && (
<EuiFlexItem grow={false}>
<EuiSwitch
compressed
label={'clearable'}
checked={isClearable}
onChange={(e) => setIsClearable(e.target.checked)}
/>
</EuiFlexItem>
)}
{extras &&
extras.map((extra, index) => {
return (
Expand All @@ -179,31 +191,3 @@ export const DisplayToggles = ({
</Fragment>
);
};

DisplayToggles.propTypes = {
canIsDisabled: PropTypes.bool,
canDisabled: PropTypes.bool,
canReadOnly: PropTypes.bool,
canLoading: PropTypes.bool,
canCompressed: PropTypes.bool,
canFullWidth: PropTypes.bool,
canPrepend: PropTypes.bool,
canAppend: PropTypes.bool,
canInvalid: PropTypes.bool,
extras: PropTypes.arrayOf(PropTypes.node),
// Manually building the spacer array to avoid having to import Spacer into codesandbox
spacerSize: PropTypes.oneOf(['xs', 's', 'm', 'l', 'xl', 'xxl']),
};

DisplayToggles.defaultProps = {
canIsDisabled: false,
canDisabled: true,
canReadOnly: true,
canLoading: true,
canCompressed: true,
canFullWidth: true,
canInvalid: true,
canPrepend: false,
canAppend: false,
spacerSize: 'l',
};
Original file line number Diff line number Diff line change
@@ -1,193 +1,144 @@
import React, { Fragment } from 'react';
import React from 'react';

import {
EuiFormControlLayout,
EuiSpacer,
EuiFormLabel,
EuiButtonEmpty,
EuiText,
} from '../../../../src/components';
import { useGeneratedHtmlId } from '../../../../src/services';
useGeneratedHtmlId,
useEuiTheme,
EuiFieldText,
} from '../../../../src';

export default () => {
const { euiTheme } = useEuiTheme();
const labelInputId = useGeneratedHtmlId({ prefix: 'labelInput' });
const readOnlyInputId = useGeneratedHtmlId({ prefix: 'readOnlyInput' });

return (
<Fragment>
<div
css={{
display: 'inline-flex',
flexWrap: 'wrap',
gap: euiTheme.size.l,
}}
>
<EuiFormControlLayout icon="search">
<input
type="text"
className="euiFieldText"
<EuiFieldText
type="search"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout isLoading>
<input
<EuiFieldText
type="text"
className="euiFieldText"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout clear={{ onClick: () => {} }}>
<input
<EuiFieldText
type="text"
className="euiFieldText"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout isLoading clear={{ onClick: () => {} }}>
<input
<EuiFormControlLayout isInvalid>
<EuiFieldText
type="text"
className="euiFieldText"
controlOnly
isInvalid
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout isLoading icon="search">
<input
type="text"
className="euiFieldText"
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
isLoading
icon={{ type: 'arrowDown', side: 'right' }}
>
<input
type="text"
className="euiFieldText"
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout clear={{ onClick: () => {} }} icon="search">
<input
type="text"
className="euiFieldText"
<EuiFieldText
type="search"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
clear={{ onClick: () => {} }}
icon={{ type: 'arrowDown', side: 'right' }}
>
<input
<EuiFormControlLayout isLoading isDropdown>
<EuiFieldText
type="text"
className="euiFieldText"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
isLoading
clear={{ onClick: () => {} }}
icon="search"
isDropdown
icon={{ type: 'stopFilled', color: 'success', side: 'left' }}
>
<input
<EuiFieldText
type="text"
className="euiFieldText"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
isLoading
clear={{ onClick: () => {} }}
icon={{ type: 'arrowDown', side: 'right' }}
icon={{ type: 'bolt', side: 'right' }}
isDropdown
>
<input
<EuiFieldText
type="text"
className="euiFieldText"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
isLoading
clear={{ onClick: () => {} }}
icon="search"
>
<input
type="text"
className="euiFieldText"
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
prepend={<EuiFormLabel htmlFor={labelInputId}>Label</EuiFormLabel>}
>
<input
<EuiFieldText
type="text"
className="euiFieldText euiFieldText--inGroup"
className="euiFieldText--inGroup"
controlOnly
id={labelInputId}
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
readOnly
prepend={
<EuiFormLabel htmlFor={readOnlyInputId}>Read only</EuiFormLabel>
}
append={<EuiButtonEmpty size="xs">Button</EuiButtonEmpty>}
>
<input
<EuiFieldText
type="text"
className="euiFieldText euiFieldText--inGroup"
className="euiFieldText--inGroup"
id={readOnlyInputId}
controlOnly
readOnly
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
append={
<EuiText size="xs">
<strong>%</strong>
</EuiText>
}
>
<input
<EuiFieldText
type="number"
className="euiFieldNumber euiFieldNumber--inGroup"
className="euiFieldNumber--inGroup"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>

<EuiSpacer size="m" />

<EuiFormControlLayout
isLoading
clear={{ onClick: () => {} }}
Expand All @@ -197,12 +148,13 @@ export default () => {
</EuiButtonEmpty>
}
>
<input
<EuiFieldText
type="text"
className="euiFieldText euiFieldText--inGroup"
className="euiFieldText--inGroup"
controlOnly
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFormControlLayout>
</Fragment>
</div>
);
};
Loading

0 comments on commit d31f753

Please sign in to comment.