diff --git a/src-docs/src/views/color_picker/kitchen_sink.js b/src-docs/src/views/color_picker/kitchen_sink.js index b895da2b55b..d850b102356 100644 --- a/src-docs/src/views/color_picker/kitchen_sink.js +++ b/src-docs/src/views/color_picker/kitchen_sink.js @@ -19,7 +19,7 @@ export default () => { return ( {/* DisplayToggles wrapper for Docs only */} - + diff --git a/src-docs/src/views/form_controls/display_toggles.js b/src-docs/src/views/form_controls/display_toggles.js index fc79d3ca7f9..ab8157cff62 100644 --- a/src-docs/src/views/form_controls/display_toggles.js +++ b/src-docs/src/views/form_controls/display_toggles.js @@ -1,5 +1,4 @@ import React, { cloneElement, useState, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { EuiFlexGroup, @@ -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); @@ -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; @@ -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 ( @@ -135,7 +137,7 @@ export const DisplayToggles = ({ compressed{' '} - + @@ -165,6 +167,16 @@ export const DisplayToggles = ({ /> )} + {canClear && ( + + setIsClearable(e.target.checked)} + /> + + )} {extras && extras.map((extra, index) => { return ( @@ -179,31 +191,3 @@ export const DisplayToggles = ({ ); }; - -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', -}; diff --git a/src-docs/src/views/form_controls/form_control_layout.js b/src-docs/src/views/form_controls/form_control_layout.tsx similarity index 53% rename from src-docs/src/views/form_controls/form_control_layout.js rename to src-docs/src/views/form_controls/form_control_layout.tsx index af7cad20cdb..02821d6134e 100644 --- a/src-docs/src/views/form_controls/form_control_layout.js +++ b/src-docs/src/views/form_controls/form_control_layout.tsx @@ -1,160 +1,113 @@ -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 ( - +
- - - - - - {} }}> - - - - {} }}> - + - - - - - - - - - - - - - - {} }} icon="search"> - - - - {} }} - icon={{ type: 'arrowDown', side: 'right' }} - > - + - - {} }} - icon="search" + isDropdown + icon={{ type: 'stopFilled', color: 'success', side: 'left' }} > - - - {} }} - icon={{ type: 'arrowDown', side: 'right' }} + icon={{ type: 'bolt', side: 'right' }} + isDropdown > - - - - {} }} - icon="search" - > - - - - - Label} > - - - { } append={Button} > - - - @@ -179,15 +131,14 @@ export default () => { } > - - - {} }} @@ -197,12 +148,13 @@ export default () => { } > - - +
); }; diff --git a/src-docs/src/views/form_controls/form_control_layout_range.js b/src-docs/src/views/form_controls/form_control_layout_range.tsx similarity index 94% rename from src-docs/src/views/form_controls/form_control_layout_range.js rename to src-docs/src/views/form_controls/form_control_layout_range.tsx index eec6e49a035..9425c27c43e 100644 --- a/src-docs/src/views/form_controls/form_control_layout_range.js +++ b/src-docs/src/views/form_controls/form_control_layout_range.tsx @@ -1,15 +1,21 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import { EuiFormControlLayoutDelimited, - EuiSpacer, EuiFormLabel, EuiIcon, } from '../../../../src/components'; export default () => ( - +
( } /> - px} startControl={ ( } /> - px} startControl={ ( } /> - {} }} - isLoading + icon="vector" startControl={ ( } /> - {} }} + isLoading startControl={ ( } /> - ( } /> - ( } /> - - Add} startControl={ @@ -178,8 +175,6 @@ export default () => ( } /> - - Merge} startControl={ @@ -201,8 +196,6 @@ export default () => ( } /> - - Read only} @@ -225,5 +218,5 @@ export default () => ( /> } /> - +
); diff --git a/src-docs/src/views/form_controls/form_controls_example.js b/src-docs/src/views/form_controls/form_controls_example.js index 065915b6e22..4c0059ffc31 100644 --- a/src-docs/src/views/form_controls/form_controls_example.js +++ b/src-docs/src/views/form_controls/form_controls_example.js @@ -189,7 +189,7 @@ export const FormControlsExample = {

- Placeholders should never replace a label but used as + Placeholders should never replace a label but used as a hint in addition to the label. Use the{' '} placeholder prop to describe the expected value of the input. @@ -467,7 +467,7 @@ export const FormControlsExample = { title: 'Form control layout', source: [ { - type: GuideSectionTypes.JS, + type: GuideSectionTypes.TSX, code: formControlLayoutSource, }, ], @@ -481,7 +481,9 @@ export const FormControlsExample = { EuiFormControlLayout is generally used internally to consistently style form controls, but it’s published in case you want to create your own form control which matches those of - EUI. The examples below demonstrate its various states. + EUI. The examples below demonstrate its various states and utilize + the controlOnly and type props + of EuiFieldText as the wrapped control.

@@ -504,7 +506,7 @@ export const FormControlsExample = { title: 'Form control layout delimited', source: [ { - type: GuideSectionTypes.JS, + type: GuideSectionTypes.TSX, code: formControlLayoutRangeSource, }, ], diff --git a/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap b/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap index dc13c8023fb..7ee550b7c40 100644 --- a/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap +++ b/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap @@ -13,38 +13,27 @@ exports[`renders EuiColorPicker 1`] = `
+
-
-
- -
- - -
-
-
+
+
-
-
- -
- - -
-
-
+
+
-
-
- -
- - -
-
-
+
-
-
+
+ -
- -
- - -
-
-
+
-
-
+
+ -
- -
- - -
-
-
+
-
-
+
+ -
- -
- - -
-
-
+
+
-
-
- -
- - -
-
-
+
+
-
-
- -
- - -
-
-
+
+
-
-
- -
- - -
-
-
+
+
-
-
- -
- - -
-
-
-
-
@@ -681,38 +564,27 @@ exports[`renders fullWidth EuiColorPicker 1`] = `
+
-
-
- -
- - -
-
-
+
+
-
-
- -
- - -
-
-
+
diff --git a/src/components/color_picker/color_palette_picker/__snapshots__/color_palette_picker.test.tsx.snap b/src/components/color_picker/color_palette_picker/__snapshots__/color_palette_picker.test.tsx.snap index 78cdc4adfa9..8f2644c6708 100644 --- a/src/components/color_picker/color_palette_picker/__snapshots__/color_palette_picker.test.tsx.snap +++ b/src/components/color_picker/color_palette_picker/__snapshots__/color_palette_picker.test.tsx.snap @@ -27,7 +27,7 @@ exports[`EuiColorPalettePicker is rendered 1`] = `
+
+ +
`; @@ -234,7 +242,7 @@ exports[`EuiFieldPassword props isLoading is rendered 1`] = ` > diff --git a/src/components/form/field_password/_field_password.scss b/src/components/form/field_password/_field_password.scss index 364f4f8217c..67e3b890f90 100644 --- a/src/components/form/field_password/_field_password.scss +++ b/src/components/form/field_password/_field_password.scss @@ -1,7 +1,6 @@ .euiFieldPassword { @include euiFormControlStyle; - @include euiFormControlWithIcon($isIconOptional: false); - @include euiFormControlIsLoading; + @include euiFormControlWithIcon($isIconOptional: false, $side: 'left'); &.euiFieldPassword--compressed { @include euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: true); diff --git a/src/components/form/field_password/field_password.tsx b/src/components/form/field_password/field_password.tsx index a604eb0a2c9..14ec5c65d8e 100644 --- a/src/components/form/field_password/field_password.tsx +++ b/src/components/form/field_password/field_password.tsx @@ -24,6 +24,7 @@ import { EuiValidatableControl } from '../validatable_control'; import { EuiButtonIcon, EuiButtonIconPropsForButton } from '../../button'; import { useEuiI18n } from '../../i18n'; import { useCombinedRefs } from '../../../services'; +import { getFormControlClassNameForIconCount } from '../form_control_layout/_num_icons'; export type EuiFieldPasswordProps = Omit< InputHTMLAttributes, @@ -135,14 +136,20 @@ export const EuiFieldPassword: FunctionComponent = ({ const finalAppend = appends.length ? appends : undefined; + const numIconsClass = getFormControlClassNameForIconCount({ + isInvalid, + isLoading, + }); + const classes = classNames( 'euiFieldPassword', + numIconsClass, { 'euiFieldPassword--fullWidth': fullWidth, 'euiFieldPassword--compressed': compressed, - 'euiFieldPassword-isLoading': isLoading, 'euiFieldPassword--inGroup': prepend || finalAppend, 'euiFieldPassword--withToggle': type === 'dual', + 'euiFieldPassword-isLoading': isLoading, }, className ); @@ -152,6 +159,7 @@ export const EuiFieldPassword: FunctionComponent = ({ icon="lock" fullWidth={fullWidth} isLoading={isLoading} + isInvalid={isInvalid} compressed={compressed} prepend={prepend} append={finalAppend} diff --git a/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap b/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap index 96d015e18bd..2a13740b707 100644 --- a/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap +++ b/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap @@ -11,7 +11,7 @@ exports[`EuiFieldSearch is rendered 1`] = ` @@ -95,13 +95,14 @@ exports[`EuiFieldSearch props isInvalid is rendered 1`] = ` compressed="false" fullwidth="false" icon="search" + isinvalid="true" isloading="false" > @@ -117,7 +118,7 @@ exports[`EuiFieldSearch props isLoading is rendered 1`] = ` > diff --git a/src/components/form/field_search/_field_search.scss b/src/components/form/field_search/_field_search.scss index 951e30e158f..d2d49fb9bbd 100644 --- a/src/components/form/field_search/_field_search.scss +++ b/src/components/form/field_search/_field_search.scss @@ -20,32 +20,8 @@ &::-ms-clear { display: none; /* 2 */ } +} - &.euiFieldSearch-isClearable { - @include euiFormControlLayoutPadding(1) - } - - &.euiFieldSearch-isLoading { - @include euiFormControlLayoutPadding(1); - } - - &.euiFieldSearch-isLoading.euiFieldSearch-isClearable { - @include euiFormControlLayoutPadding(2); - } - - &.euiFieldSearch--compressed { - @include euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: true); - - &.euiFieldSearch-isClearable { - @include euiFormControlLayoutPadding(1, $compressed: true); - } - - &.euiFieldSearch-isLoading { - @include euiFormControlLayoutPadding(1, $compressed: true); - } - - &.euiFieldSearch-isLoading.euiFieldSearch-isClearable { - @include euiFormControlLayoutPadding(2, $compressed: true); - } - } +.euiFieldSearch--compressed { + @include euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: true); } diff --git a/src/components/form/field_search/field_search.tsx b/src/components/form/field_search/field_search.tsx index a1fe2710a2e..1cea8d73b2f 100644 --- a/src/components/form/field_search/field_search.tsx +++ b/src/components/form/field_search/field_search.tsx @@ -18,6 +18,7 @@ import { } from '../form_control_layout'; import { EuiValidatableControl } from '../validatable_control'; +import { getFormControlClassNameForIconCount } from '../form_control_layout/_num_icons'; export interface EuiFieldSearchProps extends CommonProps, @@ -217,7 +218,7 @@ export class EuiFieldSearch extends Component< incremental, compressed, onSearch, - isClearable, + isClearable: _isClearable, append, prepend, ...rest @@ -226,14 +227,27 @@ export class EuiFieldSearch extends Component< let value = this.props.value; if (typeof this.props.value !== 'string') value = this.state.value; + // Set actual value of isClearable if value exists as well + const isClearable = Boolean( + _isClearable && value && !rest.readOnly && !rest.disabled + ); + + const numIconsClass = getFormControlClassNameForIconCount({ + clear: isClearable, + isInvalid, + isLoading, + }); + const classes = classNames( 'euiFieldSearch', + numIconsClass, { 'euiFieldSearch--fullWidth': fullWidth, 'euiFieldSearch--compressed': compressed, 'euiFieldSearch--inGroup': prepend || append, 'euiFieldSearch-isLoading': isLoading, - 'euiFieldSearch-isClearable': isClearable && value, + 'euiFieldSearch-isClearable': isClearable, + 'euiFieldSearch-isInvalid': isInvalid, }, className ); @@ -243,8 +257,9 @@ export class EuiFieldSearch extends Component< icon="search" fullWidth={fullWidth} isLoading={isLoading} + isInvalid={isInvalid} clear={ - isClearable && value && !rest.readOnly && !rest.disabled + isClearable ? { onClick: this.onClear, 'data-test-subj': 'clearSearchButton' } : undefined } diff --git a/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap b/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap index 22f2efdaf39..44584d0e90c 100644 --- a/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap +++ b/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap @@ -46,12 +46,13 @@ exports[`EuiFieldText props fullWidth is rendered 1`] = ` exports[`EuiFieldText props isInvalid is rendered 1`] = ` @@ -65,7 +66,7 @@ exports[`EuiFieldText props isLoading is rendered 1`] = ` > diff --git a/src/components/form/field_text/_field_text.scss b/src/components/form/field_text/_field_text.scss index ce5df6c5a69..297a0f6a4ba 100644 --- a/src/components/form/field_text/_field_text.scss +++ b/src/components/form/field_text/_field_text.scss @@ -1,7 +1,6 @@ .euiFieldText { @include euiFormControlStyle; - @include euiFormControlWithIcon($isIconOptional: true); - @include euiFormControlIsLoading; + @include euiFormControlWithIcon($isIconOptional: true, $side: 'left'); /* Invalid state normally comes from :invalid, but several components /* like EuiDatePicker need it toggled through an extra class. @@ -14,11 +13,3 @@ .euiFieldText--withIcon.euiFieldText--compressed { @include euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: true); } - -.euiFieldText--isClearable { - @include euiFormControlWithIcon($isIconOptional: false, $side: 'right', $compressed: false); -} - -.euiFieldText--isClearable.euiFieldText--compressed { - @include euiFormControlWithIcon($isIconOptional: false, $side: 'right', $compressed: true); -} diff --git a/src/components/form/field_text/field_text.tsx b/src/components/form/field_text/field_text.tsx index 25c9f529131..2b38745560e 100644 --- a/src/components/form/field_text/field_text.tsx +++ b/src/components/form/field_text/field_text.tsx @@ -16,6 +16,7 @@ import { } from '../form_control_layout'; import { EuiValidatableControl } from '../validatable_control'; +import { getFormControlClassNameForIconCount } from '../form_control_layout/_num_icons'; export type EuiFieldTextProps = InputHTMLAttributes & CommonProps & { @@ -68,7 +69,12 @@ export const EuiFieldText: FunctionComponent = ({ controlOnly, ...rest }) => { - const classes = classNames('euiFieldText', className, { + const numIconsClass = getFormControlClassNameForIconCount({ + isInvalid, + isLoading, + }); + + const classes = classNames('euiFieldText', className, numIconsClass, { 'euiFieldText--withIcon': icon, 'euiFieldText--fullWidth': fullWidth, 'euiFieldText--compressed': compressed, @@ -99,6 +105,7 @@ export const EuiFieldText: FunctionComponent = ({ icon={icon} fullWidth={fullWidth} isLoading={isLoading} + isInvalid={isInvalid} compressed={compressed} readOnly={readOnly} prepend={prepend} diff --git a/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap b/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap index 4c31a6c9bdd..f810f84acf3 100644 --- a/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap +++ b/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap @@ -188,6 +188,49 @@ exports[`EuiFormControlLayout props icon side right is rendered 1`] = `
`; +exports[`EuiFormControlLayout props isDropdown is rendered 1`] = ` +
+
+
+ + +
+
+
+`; + +exports[`EuiFormControlLayout props isInvalid is rendered 1`] = ` +
+
+
+ +
+
+
+`; + exports[`EuiFormControlLayout props isLoading is rendered 1`] = ` {appendNodes} diff --git a/src/components/form/form_control_layout/form_control_layout_custom_icon.test.tsx b/src/components/form/form_control_layout/form_control_layout_custom_icon.test.tsx index 289f0d7321b..e3c76dbb47b 100644 --- a/src/components/form/form_control_layout/form_control_layout_custom_icon.test.tsx +++ b/src/components/form/form_control_layout/form_control_layout_custom_icon.test.tsx @@ -20,6 +20,7 @@ describe('EuiFormControlLayoutCustomIcon', () => { onClick: () => null, type: 'alert', iconRef: 'icon', + color: 'danger', }; const component = render( diff --git a/src/components/form/form_control_layout/form_control_layout_custom_icon.tsx b/src/components/form/form_control_layout/form_control_layout_custom_icon.tsx index 7e4bb5fd864..227c2334822 100644 --- a/src/components/form/form_control_layout/form_control_layout_custom_icon.tsx +++ b/src/components/form/form_control_layout/form_control_layout_custom_icon.tsx @@ -34,6 +34,7 @@ export const EuiFormControlLayoutCustomIcon: FunctionComponent { const classes = classNames('euiFormControlLayoutCustomIcon', className, { @@ -54,6 +55,7 @@ export const EuiFormControlLayoutCustomIcon: FunctionComponent
@@ -993,7 +998,7 @@ Array [ aria-haspopup="listbox" aria-label="aria-label" autocomplete="off" - class="euiFieldSearch euiFieldSearch--fullWidth euiFieldSearch-isLoading euiSelectableSearch testClass1 testClass2" + class="euiFieldSearch euiFormControlLayout--1icons euiFieldSearch--fullWidth euiFieldSearch-isLoading euiSelectableSearch testClass1 testClass2" data-test-subj="test subject string" placeholder="Filter options" type="search" @@ -1246,7 +1251,7 @@ Array [ aria-haspopup="listbox" aria-label="aria-label" autocomplete="off" - class="euiFieldSearch euiFieldSearch--fullWidth euiFieldSearch-isLoading euiSelectableSearch testClass1 testClass2" + class="euiFieldSearch euiFormControlLayout--1icons euiFieldSearch--fullWidth euiFieldSearch-isLoading euiSelectableSearch testClass1 testClass2" data-test-subj="test subject string" placeholder="Filter options" type="search" diff --git a/src/global_styling/mixins/_form.scss b/src/global_styling/mixins/_form.scss index de91b7ed803..4cede2b498e 100644 --- a/src/global_styling/mixins/_form.scss +++ b/src/global_styling/mixins/_form.scss @@ -214,7 +214,10 @@ } @mixin euiFormControlReadOnlyStyle { + // sass-lint:disable-block no-vendor-prefixes cursor: default; + color: $euiTextColor; + -webkit-text-fill-color: $euiTextColor; // Required for Safari // Use transparency since there is no border and in case form is on a non-white background background: $euiFormBackgroundReadOnlyColor; border-color: transparent; diff --git a/src/themes/amsterdam/overrides/_form_control_layout.scss b/src/themes/amsterdam/overrides/_form_control_layout.scss index 982f36789d9..cb9aa0d6ae9 100644 --- a/src/themes/amsterdam/overrides/_form_control_layout.scss +++ b/src/themes/amsterdam/overrides/_form_control_layout.scss @@ -43,11 +43,13 @@ } .euiFormControlLayout__childrenWrapper:first-child .euiSelect, + .euiFormControlLayout__childrenWrapper:first-child .euiSuperSelectControl, .euiFormControlLayout__childrenWrapper:first-child [class*='euiField'] { @include euiFormControlSideBorderRadius($euiFormControlBorderRadius, $side: 'left'); } .euiFormControlLayout__childrenWrapper:last-child .euiSelect, + .euiFormControlLayout__childrenWrapper:last-child .euiSuperSelectControl, .euiFormControlLayout__childrenWrapper:last-child [class*='euiField'] { @include euiFormControlSideBorderRadius($euiFormControlBorderRadius, $side: 'right'); } diff --git a/upcoming_changelogs/5738.md b/upcoming_changelogs/5738.md new file mode 100644 index 00000000000..0232d4fffdf --- /dev/null +++ b/upcoming_changelogs/5738.md @@ -0,0 +1,9 @@ +- Added `alert` icon indicator and `aria-invalid` when the following form controls are `isInvalid`: `EuiFieldNumber`, `EuiFieldPassword`, `EuiFieldText`, `EuiSelect`, `EuiSuperSelect`, `EuiFieldSearch`, `EuiColorPicker` +- Added `isInvalid` prop to `EuiFormControlLayout` to render the `alert` icon +- Added `isDropdown` prop to `EuiFormControlLayout` to create and control an `arrowDown` icon +- Added `color` as to `EuiFormControlLayout`'s `icon` object + +**Bug fixes** + +- Fixed `EuiSuperSelect` border-radius with `append` or `prepend` +- Fixed `EuiSuperSelect` not respecting `readOnly`