diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Controlled_Component.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Controlled_Component.png
index 897272a25fc..76c83b3b9e5 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Controlled_Component.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Controlled_Component.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Icon_Shape.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Icon_Shape.png
index 76b42b78108..401121dcafd 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Icon_Shape.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Icon_Shape.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Playground.png
index c570b904487..9443dc4bf34 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldNumber_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldPassword_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldPassword_Playground.png
index f11c7d56bc7..e65ce7349ef 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldPassword_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldPassword_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldSearch_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldSearch_Playground.png
index 240d08b8add..1c1e0894a3f 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldSearch_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldSearch_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Icon_Shape.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Icon_Shape.png
index ce79e9cbd46..3c079f73a92 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Icon_Shape.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Icon_Shape.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Playground.png
index adb9412ebed..93fbde02a44 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFieldText_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSelect_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSelect_Playground.png
index a7706db03da..dd9937bbf7e 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSelect_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSelect_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperSelect_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperSelect_Playground.png
index 682f2f6532b..782e071a80c 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperSelect_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperSelect_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Icon_Shape.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Icon_Shape.png
index e3fb5d6648a..499c602733f 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Icon_Shape.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Icon_Shape.png differ
diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Playground.png
index 23f6400513b..a507d2bc7e0 100644
Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiTextArea_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Controlled_Component.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Controlled_Component.png
index f60a83766ba..e1782502e20 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Controlled_Component.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Controlled_Component.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Icon_Shape.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Icon_Shape.png
index b549f45fe90..0e7ddb8bc0a 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Icon_Shape.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Icon_Shape.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Playground.png
index 54a9d1b3047..745b3b35db4 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldNumber_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldPassword_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldPassword_Playground.png
index b8de798f53f..526e6d26a7b 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldPassword_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldPassword_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldSearch_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldSearch_Playground.png
index 899a5b6e612..5873d9f7345 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldSearch_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldSearch_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Icon_Shape.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Icon_Shape.png
index 41ba657c2fa..65de86c6887 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Icon_Shape.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Icon_Shape.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Playground.png
index 8a2c59b54ce..fe985d9875a 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFieldText_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSelect_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSelect_Playground.png
index 69842e4a721..d31afe8a659 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSelect_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSelect_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperSelect_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperSelect_Playground.png
index 056ab1e904a..2702b2c7b6d 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperSelect_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperSelect_Playground.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Icon_Shape.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Icon_Shape.png
index 1282ed9ea0f..dd1133e11c6 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Icon_Shape.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Icon_Shape.png differ
diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Playground.png
index ed1a199d0c5..ca732808831 100644
Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiTextArea_Playground.png differ
diff --git a/packages/eui/changelogs/upcoming/7770.md b/packages/eui/changelogs/upcoming/7770.md
new file mode 100644
index 00000000000..cbf02ef5538
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/7770.md
@@ -0,0 +1,7 @@
+**Bug fixes**
+
+- Fixed broken focus/invalid styling on compressed `EuiDatePickerRange`s
+
+**CSS-in-JS conversions**
+
+- Converted `EuiFieldText` to Emotion
diff --git a/packages/eui/changelogs/upcoming/7776.md b/packages/eui/changelogs/upcoming/7776.md
new file mode 100644
index 00000000000..ceaeea60bd3
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/7776.md
@@ -0,0 +1,3 @@
+**CSS-in-JS conversions**
+
+- Updated the autofill colors of Chrome (and other webkit browsers) to better match EUI's light and dark mode
diff --git a/packages/eui/changelogs/upcoming/7799.md b/packages/eui/changelogs/upcoming/7799.md
new file mode 100644
index 00000000000..77f4bb71e7b
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/7799.md
@@ -0,0 +1 @@
+- Updated `EuiFormControlLayout` to automatically pass icon padding affordance down to child `input`s
diff --git a/packages/eui/changelogs/upcoming/7802.md b/packages/eui/changelogs/upcoming/7802.md
new file mode 100644
index 00000000000..0bc6a6a183a
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/7802.md
@@ -0,0 +1,5 @@
+**CSS-in-JS conversions**
+
+- Converted `EuiFieldNumber` to Emotion
+- Converted `EuiFieldSearch` to Emotion
+- Converted `EuiFieldPassword` to Emotion
diff --git a/packages/eui/changelogs/upcoming/7812.md b/packages/eui/changelogs/upcoming/7812.md
new file mode 100644
index 00000000000..ef4dd542e4f
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/7812.md
@@ -0,0 +1,5 @@
+**CSS-in-JS conversions**
+
+- Converted `EuiTextArea` to Emotion
+- Converted `EuiSelect` to Emotion
+- Converted `EuiSuperSelect` to Emotion
diff --git a/packages/eui/src-docs/src/views/form_controls/form_control_layout.tsx b/packages/eui/src-docs/src/views/form_controls/form_control_layout.tsx
index 02821d6134e..7f2c4b6a086 100644
--- a/packages/eui/src-docs/src/views/form_controls/form_control_layout.tsx
+++ b/packages/eui/src-docs/src/views/form_controls/form_control_layout.tsx
@@ -100,12 +100,7 @@ export default () => {
Label}
>
-
+
{
}
append={Button }
>
-
+
{
>
@@ -150,7 +138,6 @@ export default () => {
>
diff --git a/packages/eui/src-docs/src/views/form_controls/form_controls_example.js b/packages/eui/src-docs/src/views/form_controls/form_controls_example.js
index 04e18077c66..35e8b168289 100644
--- a/packages/eui/src-docs/src/views/form_controls/form_controls_example.js
+++ b/packages/eui/src-docs/src/views/form_controls/form_controls_example.js
@@ -477,16 +477,6 @@ export const FormControlsExample = {
the controlOnly and type props
of EuiFieldText as the wrapped control.
-
-
-
- The padding on the input itself doesn’t
- take into account the presence of the various icons supported by{' '}
- EuiFormControlLayout . Any input component
- provided to EuiFormControlLayout is responsible
- for its own padding.
-
-
>
),
props: {
diff --git a/packages/eui/src-docs/src/views/form_controls/prepend_append.js b/packages/eui/src-docs/src/views/form_controls/prepend_append.js
index 4e5751a1222..3dad35c7594 100644
--- a/packages/eui/src-docs/src/views/form_controls/prepend_append.js
+++ b/packages/eui/src-docs/src/views/form_controls/prepend_append.js
@@ -1,6 +1,7 @@
-import React, { Fragment, useState } from 'react';
+import React, { useState } from 'react';
import {
+ EuiFlexGroup,
EuiButtonEmpty,
EuiButtonIcon,
EuiFieldText,
@@ -19,24 +20,24 @@ export default () => {
const [isReadOnly, setReadOnly] = useState(false);
return (
-
- setCompressed(e.target.checked)}
- />
-
- setDisabled(e.target.checked)}
- />
-
- setReadOnly(e.target.checked)}
- />
+ <>
+
+ setCompressed(e.target.checked)}
+ />
+ setDisabled(e.target.checked)}
+ />
+ setReadOnly(e.target.checked)}
+ />
+
{
readOnly={isReadOnly}
aria-label="Use aria labels when no actual label is in use"
/>
-
+ >
);
};
diff --git a/packages/eui/src-docs/src/views/super_date_picker/super_date_picker.tsx b/packages/eui/src-docs/src/views/super_date_picker/super_date_picker.tsx
index c2575fe0da0..71da95be5ac 100644
--- a/packages/eui/src-docs/src/views/super_date_picker/super_date_picker.tsx
+++ b/packages/eui/src-docs/src/views/super_date_picker/super_date_picker.tsx
@@ -5,6 +5,7 @@ import {
EuiSpacer,
EuiFormControlLayoutDelimited,
EuiFormLabel,
+ EuiFieldText,
EuiPanel,
EuiText,
OnRefreshProps,
@@ -58,21 +59,19 @@ export default () => {
Dates}
startControl={
-
}
endControl={
-
}
/>
diff --git a/packages/eui/src/components/basic_table/in_memory_table.test.tsx b/packages/eui/src/components/basic_table/in_memory_table.test.tsx
index 5aedbd0eddc..6e0371bedfe 100644
--- a/packages/eui/src/components/basic_table/in_memory_table.test.tsx
+++ b/packages/eui/src/components/basic_table/in_memory_table.test.tsx
@@ -947,7 +947,7 @@ describe('EuiInMemoryTable', () => {
// should render with all three results visible
expect(component.find('.testTable EuiTableRow').length).toBe(3);
- const searchField = component.find('EuiFieldSearch input[type="search"]');
+ const searchField = component.find('input.euiFieldSearch');
searchField.simulate('keyUp', {
target: {
diff --git a/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap b/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap
index 01a480b6704..371dea2b8a0 100644
--- a/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap
+++ b/packages/eui/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap
@@ -27,7 +27,7 @@ exports[`renders EuiColorPicker 1`] = `
@@ -56,7 +56,7 @@ exports[`EuiColorPalettePicker is rendered with a selected custom text 1`] = `
@@ -97,7 +97,7 @@ exports[`EuiColorPalettePicker is rendered with a selected fixed palette 1`] = `
@@ -166,7 +166,7 @@ exports[`EuiColorPalettePicker is rendered with a selected gradient palette 1`]
@@ -215,7 +215,7 @@ exports[`EuiColorPalettePicker is rendered with a selected gradient palette with
@@ -264,7 +264,7 @@ exports[`EuiColorPalettePicker is rendered with the prop selectionDisplay set as
@@ -304,7 +304,7 @@ exports[`EuiColorPalettePicker more props are propagated to each option 1`] = `
>
diff --git a/packages/eui/src/components/color_picker/color_picker.tsx b/packages/eui/src/components/color_picker/color_picker.tsx
index f5f54ee6386..6e41de4af9b 100644
--- a/packages/eui/src/components/color_picker/color_picker.tsx
+++ b/packages/eui/src/components/color_picker/color_picker.tsx
@@ -293,13 +293,7 @@ export const EuiColorPicker: FunctionComponent = ({
clear: isClearable,
isInvalid,
});
- const inputClasses = classNames(
- 'euiColorPicker__input',
- { 'euiColorPicker__input--inGroup': prepend || append },
- // Manually account for input padding, since `controlOnly` disables that logic
- 'euiFieldText--withIcon',
- numIconsClass
- );
+ const inputClasses = classNames('euiColorPicker__input', numIconsClass);
const handleOnChange = (text: string) => {
const output = getOutput(text, showAlpha);
diff --git a/packages/eui/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap b/packages/eui/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap
index c18c44fbb19..ef96b659268 100644
--- a/packages/eui/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap
+++ b/packages/eui/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap
@@ -69,7 +69,7 @@ exports[`useDataGridColumnSelector columnSelector [React 16] renders a toolbar b
>
diff --git a/packages/eui/src/components/date_picker/__snapshots__/date_picker_range.test.tsx.snap b/packages/eui/src/components/date_picker/__snapshots__/date_picker_range.test.tsx.snap
index cabad552f24..ab07d6ab76b 100644
--- a/packages/eui/src/components/date_picker/__snapshots__/date_picker_range.test.tsx.snap
+++ b/packages/eui/src/components/date_picker/__snapshots__/date_picker_range.test.tsx.snap
@@ -33,7 +33,7 @@ exports[`EuiDatePickerRange is rendered 1`] = `
>
@@ -56,7 +56,7 @@ exports[`EuiDatePickerRange is rendered 1`] = `
>
@@ -98,7 +98,7 @@ exports[`EuiDatePickerRange props compressed 1`] = `
>
@@ -121,7 +121,7 @@ exports[`EuiDatePickerRange props compressed 1`] = `
>
@@ -163,7 +163,7 @@ exports[`EuiDatePickerRange props disabled 1`] = `
>
@@ -253,7 +253,7 @@ exports[`EuiDatePickerRange props fullWidth 1`] = `
>
@@ -266,7 +266,7 @@ exports[`EuiDatePickerRange props fullWidth 1`] = `
exports[`EuiDatePickerRange props inline renders 1`] = `
@@ -1352,7 +1352,7 @@ exports[`EuiDatePickerRange props isInvalid 1`] = `
@@ -1402,7 +1402,7 @@ exports[`EuiDatePickerRange props isLoading 1`] = `
>
@@ -1425,7 +1425,7 @@ exports[`EuiDatePickerRange props isLoading 1`] = `
>
@@ -1476,7 +1476,7 @@ exports[`EuiDatePickerRange props readOnly 1`] = `
>
@@ -1568,7 +1568,7 @@ exports[`EuiDatePickerRange uses individual EuiDatePicker props 1`] = `
>
diff --git a/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap b/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap
index 5fc2252abd1..29e3652341e 100644
--- a/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap
+++ b/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap
@@ -34,7 +34,7 @@ exports[`EuiAutoRefresh is rendered 1`] = `
>
= ({
'euiDatePicker--shadow': inline && shadow,
});
+ const datePickerClasses = classNames('euiDatePicker', className);
+
// Check for whether the passed `selected` moment date is valid
const isInvalid =
_isInvalid || (selected?.isValid() === false ? true : undefined);
- const numIconsClass = controlOnly
- ? false
- : getFormControlClassNameForIconCount({
- isInvalid,
- isLoading,
- });
-
- const datePickerClasses = classNames(
- 'euiDatePicker',
- 'euiFieldText',
- numIconsClass,
- !inline && {
- 'euiFieldText--fullWidth': fullWidth,
- 'euiFieldText-isLoading': isLoading,
- 'euiFieldText--compressed': compressed,
- 'euiFieldText--withIcon': showIcon,
- 'euiFieldText--isClearable': selected && onClear,
- },
- className
- );
-
- let optionalIcon: EuiFormControlLayoutIconsProps['icon'];
- if (inline || customInput || !showIcon) {
- optionalIcon = undefined;
- } else if (iconType) {
- optionalIcon = iconType;
- } else if (showTimeSelectOnly) {
- optionalIcon = 'clock';
- } else {
- optionalIcon = 'calendar';
- }
+ // Passed to the default EuiFieldText input, not passed to custom inputs
+ const defaultInputProps =
+ !inline && !customInput ? { compressed, fullWidth } : undefined;
// In case the consumer did not alter the default date format but wants
// to add the time select, we append the default time format
@@ -255,6 +230,7 @@ export const EuiDatePicker: FunctionComponent = ({
adjustDateOnChange={adjustDateOnChange}
calendarClassName={calendarClassName}
className={datePickerClasses}
+ defaultInputProps={defaultInputProps}
customInput={customInput}
dateFormat={fullDateFormat}
dayClassName={dayClassName}
@@ -294,6 +270,17 @@ export const EuiDatePicker: FunctionComponent = ({
if (controlOnly) return control;
+ let optionalIcon: EuiFormControlLayoutIconsProps['icon'];
+ if (inline || customInput || !showIcon) {
+ optionalIcon = undefined;
+ } else if (iconType) {
+ optionalIcon = iconType;
+ } else if (showTimeSelectOnly) {
+ optionalIcon = 'clock';
+ } else {
+ optionalIcon = 'calendar';
+ }
+
return (
{
- const { controlLayoutGroupInputHeight } = euiFormVariables(euiThemeContext);
-
- return {
- euiDatePickerRange: css`
- .euiFieldText.euiDatePicker {
- /* Needed for correct focus/invalid box-shadow styles */
- ${logicalCSS('height', controlLayoutGroupInputHeight)}
- }
- `,
- };
+export const euiDatePickerRangeStyles = {
+ euiDatePickerRange: css`
+ /* Needed for correct focus/invalid underline/linear-gradient styles */
+ .euiPopover,
+ .react-datepicker__input-container,
+ .euiDatePicker {
+ ${logicalCSS('height', '100%')}
+ }
+ `,
};
export const euiDatePickerRangeInlineStyles = (
@@ -55,7 +52,7 @@ export const euiDatePickerRangeInlineStyles = (
}`;
return {
- inline: css`
+ euiDatePickerRangeInline: css`
.euiFormControlLayoutDelimited {
/* Reset form control styling */
${logicalCSS('height', 'auto')}
diff --git a/packages/eui/src/components/date_picker/date_picker_range.tsx b/packages/eui/src/components/date_picker/date_picker_range.tsx
index f246fb36905..c51804c7b89 100644
--- a/packages/eui/src/components/date_picker/date_picker_range.tsx
+++ b/packages/eui/src/components/date_picker/date_picker_range.tsx
@@ -24,9 +24,9 @@ import {
import { IconType } from '../icon';
import { CommonProps } from '../common';
-import { useEuiTheme } from '../../services';
+import { useEuiMemoizedStyles } from '../../services';
import {
- euiDatePickerRangeStyles,
+ euiDatePickerRangeStyles as styles,
euiDatePickerRangeInlineStyles,
} from './date_picker_range.styles';
@@ -124,25 +124,18 @@ export const EuiDatePickerRange: FunctionComponent = ({
const classes = classNames('euiDatePickerRange', className);
- const euiTheme = useEuiTheme();
- const styles = euiDatePickerRangeStyles(euiTheme);
- const cssStyles = [styles.euiDatePickerRange];
-
- if (inline) {
- // Determine the inline container query to use based on the width of the react-datepicker
- const hasTimeSelect =
- startDateControl.props.showTimeSelect ||
- endDateControl.props.showTimeSelect;
-
- const inlineStyles = euiDatePickerRangeInlineStyles(euiTheme);
- cssStyles.push(inlineStyles.inline);
- cssStyles.push(
- hasTimeSelect
- ? inlineStyles.responsiveWithTimeSelect
- : inlineStyles.responsive
- );
- if (shadow) cssStyles.push(inlineStyles.shadow);
- }
+ const inlineStyles = useEuiMemoizedStyles(euiDatePickerRangeInlineStyles);
+ const cssStyles = !inline
+ ? styles.euiDatePickerRange
+ : [
+ inlineStyles.euiDatePickerRangeInline,
+ // Determine the inline container query to use based on the width of the react-datepicker
+ startDateControl.props.showTimeSelect ||
+ endDateControl.props.showTimeSelect
+ ? inlineStyles.responsiveWithTimeSelect
+ : inlineStyles.responsive,
+ shadow && inlineStyles.shadow,
+ ];
let startControl = startDateControl;
let endControl = endDateControl;
@@ -154,6 +147,7 @@ export const EuiDatePickerRange: FunctionComponent = ({
controlOnly: true,
showIcon: false,
inline,
+ compressed,
fullWidth,
readOnly,
disabled: disabled || startDateControl.props.disabled,
@@ -179,6 +173,7 @@ export const EuiDatePickerRange: FunctionComponent = ({
controlOnly: true,
showIcon: false,
inline,
+ compressed,
fullWidth,
readOnly,
disabled: disabled || endDateControl.props.disabled,
diff --git a/packages/eui/src/components/date_picker/react-datepicker/src/index.d.ts b/packages/eui/src/components/date_picker/react-datepicker/src/index.d.ts
index c89db3e28e6..8af0ebe458b 100644
--- a/packages/eui/src/components/date_picker/react-datepicker/src/index.d.ts
+++ b/packages/eui/src/components/date_picker/react-datepicker/src/index.d.ts
@@ -22,7 +22,8 @@
import * as React from 'react';
import * as moment from 'moment';
-import { EuiPopoverProps, PopoverAnchorPosition } from '../../../popover';
+import type { EuiPopoverProps, PopoverAnchorPosition } from '../../../popover';
+import type { EuiFieldTextProps } from '../../../form/field_text';
export interface ReactDatePickerProps {
/**
@@ -44,6 +45,7 @@ export interface ReactDatePickerProps {
* Added to the actual input of the calendar
*/
className?: string;
+ defaultInputProps?: Partial;
/**
* Replaces the input with any node, like a button
diff --git a/packages/eui/src/components/date_picker/react-datepicker/src/index.js b/packages/eui/src/components/date_picker/react-datepicker/src/index.js
index cc30a496103..efd9aae78ac 100644
--- a/packages/eui/src/components/date_picker/react-datepicker/src/index.js
+++ b/packages/eui/src/components/date_picker/react-datepicker/src/index.js
@@ -64,7 +64,8 @@ import {
getMonth
} from "./date_utils";
-import {EuiPopover, popoverAnchorPosition} from '../../../popover/popover';
+import { EuiPopover, popoverAnchorPosition } from '../../../popover/popover';
+import { EuiFieldText } from '../../../form/field_text';
export { default as CalendarContainer } from "./calendar_container";
@@ -772,8 +773,12 @@ export default class DatePicker extends React.Component {
[outsideClickIgnoreClass]: this.state.open
});
- const customInput = this.props.customInput || ;
- const customInputRef = this.props.customInputRef || "ref";
+ const customInput = this.props.customInput || (
+
+ );
+ const customInputRef =
+ this.props.customInputRef ??
+ (this.props.customInput ? 'ref' : 'inputRef');
const inputValue =
typeof this.props.value === "string"
? this.props.value
diff --git a/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap b/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap
index 61d3d0abcea..15009f90921 100644
--- a/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap
+++ b/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap
@@ -176,7 +176,7 @@ exports[`EuiSuperDatePicker props isAutoRefreshOnly is rendered 1`] = `
>
diff --git a/packages/eui/src/components/form/_index.scss b/packages/eui/src/components/form/_index.scss
index 7305fff689f..83c850af420 100644
--- a/packages/eui/src/components/form/_index.scss
+++ b/packages/eui/src/components/form/_index.scss
@@ -1,16 +1,7 @@
@import 'checkbox/index';
@import 'described_form_group/index';
-@import 'field_number/index';
-@import 'field_password/index';
-@import 'field_search/index';
-@import 'field_text/index';
@import 'file_picker/index';
@import 'form';
-
-// Selects must come before form_control_layout for proper padding
-@import 'select/index';
-@import 'super_select/index';
-
@import 'form_control_layout/index';
@import 'form_error_text/index';
@import 'form_fieldset/index';
@@ -19,4 +10,3 @@
@import 'form_row/index';
@import 'radio/index';
@import 'switch/index';
-@import 'text_area/index';
diff --git a/packages/eui/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap b/packages/eui/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap
index e097f199fdf..a1540cdf2d1 100644
--- a/packages/eui/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap
+++ b/packages/eui/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap
@@ -11,7 +11,7 @@ exports[`EuiFieldNumber is rendered 1`] = `
{
+ const formStyles = euiFormControlStyles(euiThemeContext);
+
+ return {
+ euiFieldNumber: css`
+ ${formStyles.shared}
+
+ /* Account for native validity detection as well via [aria-invalid="true"] */
+ &:is(:invalid, [aria-invalid="true"]) {
+ ${formStyles.invalid}
+ }
+
+ &:focus {
+ ${formStyles.focus}
+ }
+
+ &:disabled {
+ ${formStyles.disabled}
+ }
+
+ &[readOnly] {
+ ${formStyles.readOnly}
+ }
+
+ &:autofill {
+ ${formStyles.autoFill}
+ }
+ `,
+
+ // Skip the css() on the default height to avoid generating a className
+ uncompressed: formStyles.uncompressed,
+ compressed: css(formStyles.compressed),
+
+ // Skip the css() on the default width to avoid generating a className
+ formWidth: formStyles.formWidth,
+ fullWidth: css(formStyles.fullWidth),
+
+ // Layout modifiers
+ inGroup: css(formStyles.inGroup),
+ controlOnly: css`
+ .euiFormControlLayout--group & {
+ ${formStyles.inGroup}
+ }
+ `,
+ };
+};
diff --git a/packages/eui/src/components/form/field_number/field_number.test.tsx b/packages/eui/src/components/form/field_number/field_number.test.tsx
index e5870d92c0e..8b9f679a516 100644
--- a/packages/eui/src/components/form/field_number/field_number.test.tsx
+++ b/packages/eui/src/components/form/field_number/field_number.test.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { render } from '../../../test/rtl';
+import { shouldRenderCustomStyles } from '../../../test/internal';
import { requiredProps } from '../../../test/required_props';
import { EuiForm } from '../form';
@@ -25,6 +26,8 @@ jest.mock('../validatable_control', () => ({
}));
describe('EuiFieldNumber', () => {
+ shouldRenderCustomStyles( );
+
test('is rendered', () => {
const { container } = render(
{
);
const control = container.querySelector('.euiFieldNumber')!;
- if (!control.classList.contains('euiFieldNumber--fullWidth')) {
+ if (!control.className.includes('fullWidth')) {
throw new Error(
'expected EuiFieldNumber to inherit fullWidth from EuiForm'
);
diff --git a/packages/eui/src/components/form/field_number/field_number.tsx b/packages/eui/src/components/form/field_number/field_number.tsx
index 9b8bc28569a..cdeae657218 100644
--- a/packages/eui/src/components/form/field_number/field_number.tsx
+++ b/packages/eui/src/components/form/field_number/field_number.tsx
@@ -17,7 +17,7 @@ import React, {
} from 'react';
import classNames from 'classnames';
-import { useCombinedRefs } from '../../../services';
+import { useCombinedRefs, useEuiMemoizedStyles } from '../../../services';
import { CommonProps } from '../../common';
import { EuiValidatableControl } from '../validatable_control';
@@ -25,12 +25,10 @@ import {
EuiFormControlLayout,
EuiFormControlLayoutProps,
} from '../form_control_layout';
-import {
- getFormControlClassNameForIconCount,
- isRightSideIcon,
-} from '../form_control_layout/_num_icons';
import { useFormContext } from '../eui_form_context';
+import { euiFieldNumberStyles } from './field_number.styles';
+
export type EuiFieldNumberProps = Omit<
InputHTMLAttributes,
'min' | 'max' | 'readOnly' | 'step'
@@ -136,25 +134,19 @@ export const EuiFieldNumber: FunctionComponent = (
}
}, [value, min, max, step, checkNativeValidity]);
- const hasRightSideIcon = isRightSideIcon(icon);
- const numIconsClass = controlOnly
- ? false
- : getFormControlClassNameForIconCount({
- isInvalid: isInvalid || isNativelyInvalid,
- isLoading,
- icon: hasRightSideIcon,
- });
-
- const classes = classNames('euiFieldNumber', className, numIconsClass, {
- 'euiFieldNumber--fullWidth': fullWidth,
- 'euiFieldNumber--compressed': compressed,
- ...(!controlOnly && {
- 'euiFieldNumber--inGroup': prepend || append,
- 'euiFieldNumber--withIcon': icon && !hasRightSideIcon,
- }),
+ const classes = classNames('euiFieldNumber', className, {
'euiFieldNumber-isLoading': isLoading,
});
+ const styles = useEuiMemoizedStyles(euiFieldNumberStyles);
+ const cssStyles = [
+ styles.euiFieldNumber,
+ compressed ? styles.compressed : styles.uncompressed,
+ fullWidth ? styles.fullWidth : styles.formWidth,
+ !controlOnly && (prepend || append) && styles.inGroup,
+ controlOnly && styles.controlOnly,
+ ];
+
const control = (
= (
placeholder={placeholder}
readOnly={readOnly}
className={classes}
+ css={cssStyles}
ref={combinedRefs}
aria-invalid={isInvalid || isNativelyInvalid}
onKeyUp={(e) => {
diff --git a/packages/eui/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap b/packages/eui/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap
index f09a8f2d420..a6a77aca98d 100644
--- a/packages/eui/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap
+++ b/packages/eui/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap
@@ -23,7 +23,7 @@ exports[`EuiFieldPassword is rendered 1`] = `
@@ -89,7 +89,7 @@ exports[`EuiFieldPassword props dual dual type also renders append 1`] = `
@@ -143,7 +143,7 @@ exports[`EuiFieldPassword props dual dualToggleProps is rendered 1`] = `
@@ -188,7 +188,7 @@ exports[`EuiFieldPassword props fullWidth is rendered 1`] = `
@@ -221,7 +221,7 @@ exports[`EuiFieldPassword props isInvalid is rendered 1`] = `
isinvalid="true"
>
@@ -260,7 +260,7 @@ exports[`EuiFieldPassword props isLoading is rendered 1`] = `
@@ -305,7 +305,7 @@ exports[`EuiFieldPassword props prepend and append is rendered 1`] = `
@@ -341,7 +341,7 @@ exports[`EuiFieldPassword props type dual is rendered 1`] = `
@@ -385,7 +385,7 @@ exports[`EuiFieldPassword props type password is rendered 1`] = `
@@ -416,7 +416,7 @@ exports[`EuiFieldPassword props type text is rendered 1`] = `
diff --git a/packages/eui/src/components/form/field_password/_field_password.scss b/packages/eui/src/components/form/field_password/_field_password.scss
deleted file mode 100644
index a14e7f09697..00000000000
--- a/packages/eui/src/components/form/field_password/_field_password.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-.euiFieldPassword {
- @include euiFormControlStyle;
- @include euiFormControlWithIcon($isIconOptional: false, $side: 'left');
-
- &.euiFieldPassword--compressed {
- @include euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: true);
- }
-}
-
-// stylelint-disable selector-no-vendor-prefix
-// Only remove Edge's internal reveal button if we're providing a custom one
-.euiFieldPassword--withToggle::-ms-reveal {
- display: none;
-}
diff --git a/packages/eui/src/components/form/field_password/_index.scss b/packages/eui/src/components/form/field_password/_index.scss
deleted file mode 100644
index aab5cd9d3b3..00000000000
--- a/packages/eui/src/components/form/field_password/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import 'field_password';
diff --git a/packages/eui/src/components/form/field_password/field_password.styles.ts b/packages/eui/src/components/form/field_password/field_password.styles.ts
new file mode 100644
index 00000000000..3ac02111790
--- /dev/null
+++ b/packages/eui/src/components/form/field_password/field_password.styles.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+
+import { UseEuiTheme } from '../../../services';
+import { euiFormControlStyles } from '../form.styles';
+
+export const euiFieldPasswordStyles = (euiThemeContext: UseEuiTheme) => {
+ const formStyles = euiFormControlStyles(euiThemeContext);
+
+ return {
+ euiFieldPassword: css`
+ ${formStyles.shared}
+
+ &:invalid {
+ ${formStyles.invalid}
+ }
+
+ &:focus {
+ ${formStyles.focus}
+ }
+
+ &:disabled {
+ ${formStyles.disabled}
+ }
+
+ &[readOnly] {
+ ${formStyles.readOnly}
+ }
+
+ &:autofill {
+ ${formStyles.autoFill}
+ }
+ `,
+ // Only remove Edge's internal reveal button if we're providing a custom one
+ withToggle: css`
+ &::-ms-reveal {
+ display: none;
+ }
+ `,
+
+ // Skip the css() on the default height to avoid generating a className
+ uncompressed: formStyles.uncompressed,
+ compressed: css(formStyles.compressed),
+
+ // Skip the css() on the default width to avoid generating a className
+ formWidth: formStyles.formWidth,
+ fullWidth: css(formStyles.fullWidth),
+
+ // Layout modifiers
+ inGroup: css(formStyles.inGroup),
+ };
+};
diff --git a/packages/eui/src/components/form/field_password/field_password.test.tsx b/packages/eui/src/components/form/field_password/field_password.test.tsx
index be36a90c7ea..971ccb08988 100644
--- a/packages/eui/src/components/form/field_password/field_password.test.tsx
+++ b/packages/eui/src/components/form/field_password/field_password.test.tsx
@@ -7,7 +7,7 @@
*/
import React from 'react';
-import { mount } from 'enzyme';
+import { fireEvent } from '@testing-library/react';
import { shouldRenderCustomStyles } from '../../../test/internal';
import { requiredProps } from '../../../test/required_props';
import { render } from '../../../test/rtl';
@@ -26,6 +26,7 @@ const TYPES: Array = [
];
describe('EuiFieldPassword', () => {
+ shouldRenderCustomStyles( );
shouldRenderCustomStyles( , {
childProps: ['dualToggleProps'],
});
@@ -108,37 +109,48 @@ describe('EuiFieldPassword', () => {
expect(container.firstChild).toMatchSnapshot();
});
- test('dual does not mutate the append array prop', () => {
+ test('toggles the password mask on click', () => {
const props: EuiFieldPasswordProps = {
type: 'dual',
- append: ['one', 'two'],
dualToggleProps: {
'data-test-subj': 'toggleButton',
},
};
- const component = mount( );
-
- expect(
- component.find('button[data-test-subj="toggleButton"]').length
- ).toBe(1);
- expect(
- component
- .find('button[data-test-subj="toggleButton"] EuiIcon')
- .props().type
- ).toBe('eye');
-
- component
- .find('button[data-test-subj="toggleButton"]')
- .simulate('click');
-
- expect(
- component.find('button[data-test-subj="toggleButton"]').length
- ).toBe(1);
- expect(
- component
- .find('button[data-test-subj="toggleButton"] EuiIcon')
- .props().type
- ).toBe('eyeClosed');
+ const { getByTestSubject } = render( );
+ expect(getByTestSubject('toggleButton')).toMatchInlineSnapshot(`
+
+
+
+ `);
+
+ fireEvent.click(getByTestSubject('toggleButton'));
+ expect(getByTestSubject('toggleButton')).toMatchInlineSnapshot(`
+
+
+
+ `);
});
});
});
@@ -152,7 +164,7 @@ describe('EuiFieldPassword', () => {
);
const input = container.querySelector('.euiFieldPassword');
- expect(input).toHaveClass('euiFieldPassword--fullWidth');
+ expect(input!.className).toContain('fullWidth');
});
});
});
diff --git a/packages/eui/src/components/form/field_password/field_password.tsx b/packages/eui/src/components/form/field_password/field_password.tsx
index cc09af063b4..9b90dc4cf47 100644
--- a/packages/eui/src/components/form/field_password/field_password.tsx
+++ b/packages/eui/src/components/form/field_password/field_password.tsx
@@ -10,23 +10,26 @@ import React, {
InputHTMLAttributes,
FunctionComponent,
useState,
+ useMemo,
+ useCallback,
Ref,
} from 'react';
-import { CommonProps } from '../../common';
import classNames from 'classnames';
+import { useCombinedRefs, useEuiMemoizedStyles } from '../../../services';
+import { CommonProps } from '../../common';
+import { useEuiI18n } from '../../i18n';
+import { EuiButtonIcon, EuiButtonIconPropsForButton } from '../../button';
+
import {
EuiFormControlLayout,
EuiFormControlLayoutProps,
} from '../form_control_layout';
-
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';
import { useFormContext } from '../eui_form_context';
+import { euiFieldPasswordStyles } from './field_password.styles';
+
export type EuiFieldPasswordProps = Omit<
InputHTMLAttributes,
'type' | 'value'
@@ -110,62 +113,73 @@ export const EuiFieldPassword: FunctionComponent = (
const [inputRef, _setInputRef] = useState(null);
const setInputRef = useCombinedRefs([_setInputRef, _inputRef]);
- const handleToggle = (
- event: React.MouseEvent,
- isVisible: boolean
- ) => {
- setInputType(isVisible ? 'password' : 'text');
- if (inputRef) {
- inputRef.focus();
- }
+ const handleToggle = useCallback(
+ (event: React.MouseEvent, isVisible: boolean) => {
+ setInputType(isVisible ? 'password' : 'text');
+ inputRef?.focus();
- if (dualToggleProps && dualToggleProps.onClick) {
- dualToggleProps.onClick(event);
- }
- };
+ dualToggleProps?.onClick?.(event);
+ },
+ [inputRef, dualToggleProps]
+ );
- // Convert any `append` elements to an array so the visibility
- // toggle can be added to it
- let appends = Array.isArray(append) ? append : [];
- if (append && !Array.isArray(append)) appends.push(append);
// Add a toggling button to switch between `password` and `input` if consumer wants `dual`
// https://www.w3schools.com/howto/howto_js_toggle_password.asp
- if (type === 'dual') {
- const isVisible = inputType === 'text';
-
- const visibilityToggle = (
- handleToggle(e, isVisible)}
- />
- );
- appends = [...appends, visibilityToggle];
- }
-
- const finalAppend = appends.length ? appends : undefined;
-
- const numIconsClass = getFormControlClassNameForIconCount({
- isInvalid,
- isLoading,
- });
+ const visibilityToggle = useMemo(() => {
+ if (type === 'dual') {
+ const isVisible = inputType === 'text';
+
+ return (
+ handleToggle(e, isVisible)}
+ />
+ );
+ }
+ }, [
+ type,
+ inputType,
+ maskPasswordLabel,
+ showPasswordLabel,
+ dualToggleProps,
+ handleToggle,
+ rest.disabled,
+ ]);
+
+ const finalAppend = useMemo(() => {
+ if (!visibilityToggle) return append;
+ if (!append) return visibilityToggle;
+
+ // Convert any `append` elements to an array so the visibility
+ // toggle can be added to it
+ const appendAsArray = append
+ ? Array.isArray(append)
+ ? append
+ : [append]
+ : [];
+
+ return [...appendAsArray, visibilityToggle];
+ }, [append, visibilityToggle]);
const classes = classNames(
'euiFieldPassword',
- numIconsClass,
- {
- 'euiFieldPassword--fullWidth': fullWidth,
- 'euiFieldPassword--compressed': compressed,
- 'euiFieldPassword--inGroup': prepend || finalAppend,
- 'euiFieldPassword--withToggle': type === 'dual',
- 'euiFieldPassword-isLoading': isLoading,
- },
+ { 'euiFieldPassword-isLoading': isLoading },
className
);
+ const styles = useEuiMemoizedStyles(euiFieldPasswordStyles);
+ const cssStyles = [
+ styles.euiFieldPassword,
+ compressed ? styles.compressed : styles.uncompressed,
+ fullWidth ? styles.fullWidth : styles.formWidth,
+ (finalAppend || prepend) && styles.inGroup,
+ type === 'dual' && styles.withToggle,
+ ];
+
return (
= (
name={name}
placeholder={placeholder}
className={classes}
+ css={cssStyles}
value={value}
ref={setInputRef}
{...rest}
diff --git a/packages/eui/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap b/packages/eui/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap
index ef18f1ed02a..15aeba22095 100644
--- a/packages/eui/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap
+++ b/packages/eui/src/components/form/field_search/__snapshots__/field_search.test.tsx.snap
@@ -11,7 +11,7 @@ exports[`EuiFieldSearch is rendered 1`] = `
@@ -49,7 +49,7 @@ exports[`EuiFieldSearch props fullWidth is rendered 1`] = `
>
@@ -65,7 +65,7 @@ exports[`EuiFieldSearch props isClearable is accepted 1`] = `
>
@@ -82,7 +82,7 @@ exports[`EuiFieldSearch props isClearable is rendered when a value exists 1`] =
>
@@ -102,7 +102,7 @@ exports[`EuiFieldSearch props isInvalid is rendered 1`] = `
isinvalid="true"
>
@@ -118,7 +118,7 @@ exports[`EuiFieldSearch props isLoading is rendered 1`] = `
>
@@ -135,7 +135,7 @@ exports[`EuiFieldSearch props prepend is rendered 1`] = `
>
diff --git a/packages/eui/src/components/form/field_search/_field_search.scss b/packages/eui/src/components/form/field_search/_field_search.scss
deleted file mode 100644
index ea3b602e2fc..00000000000
--- a/packages/eui/src/components/form/field_search/_field_search.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 1. Fix for Safari to ensure that it renders like a normal text input
- * and doesn't add extra spacing around text
-*/
-// stylelint-disable property-no-vendor-prefix, selector-no-vendor-prefix
-
-.euiFieldSearch {
- @include euiFormControlStyle;
- @include euiFormControlWithIcon($isIconOptional: false);
- @include euiFormControlIsLoading;
-
- -webkit-appearance: textfield; /* 1 */
-
- &::-webkit-search-decoration,
- &::-webkit-search-cancel-button {
- -webkit-appearance: none; /* 1, 2 */
- }
-}
-
-.euiFieldSearch--compressed {
- @include euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: true);
-}
diff --git a/packages/eui/src/components/form/field_search/_index.scss b/packages/eui/src/components/form/field_search/_index.scss
deleted file mode 100644
index b9652e5dde1..00000000000
--- a/packages/eui/src/components/form/field_search/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import 'field_search';
diff --git a/packages/eui/src/components/form/field_search/field_search.styles.ts b/packages/eui/src/components/form/field_search/field_search.styles.ts
new file mode 100644
index 00000000000..417fa27b643
--- /dev/null
+++ b/packages/eui/src/components/form/field_search/field_search.styles.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+
+import { UseEuiTheme } from '../../../services';
+import { euiFormControlStyles } from '../form.styles';
+
+export const euiFieldSearchStyles = (euiThemeContext: UseEuiTheme) => {
+ const formStyles = euiFormControlStyles(euiThemeContext);
+
+ return {
+ euiFieldSearch: css`
+ /* Fix for Safari to ensure that it renders like a normal text input
+ * and doesn't add extra spacing around text */
+ /* stylelint-disable property-no-vendor-prefix */
+ -webkit-appearance: textfield;
+
+ &::-webkit-search-decoration,
+ &::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ }
+
+ ${formStyles.shared}
+
+ &:invalid {
+ ${formStyles.invalid}
+ }
+
+ &:focus {
+ ${formStyles.focus}
+ }
+
+ &:disabled {
+ ${formStyles.disabled}
+ }
+
+ &[readOnly] {
+ ${formStyles.readOnly}
+ }
+
+ &:autofill {
+ ${formStyles.autoFill}
+ }
+ `,
+
+ // Skip the css() on the default height to avoid generating a className
+ uncompressed: formStyles.uncompressed,
+ compressed: css(formStyles.compressed),
+
+ // Skip the css() on the default width to avoid generating a className
+ formWidth: formStyles.formWidth,
+ fullWidth: css(formStyles.fullWidth),
+
+ // Layout modifiers
+ inGroup: css(formStyles.inGroup),
+ };
+};
diff --git a/packages/eui/src/components/form/field_search/field_search.test.tsx b/packages/eui/src/components/form/field_search/field_search.test.tsx
index 53e6541a35b..010fc17bcb0 100644
--- a/packages/eui/src/components/form/field_search/field_search.test.tsx
+++ b/packages/eui/src/components/form/field_search/field_search.test.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { requiredProps } from '../../../test/required_props';
+import { shouldRenderCustomStyles } from '../../../test/internal';
import { render } from '../../../test/rtl';
import { EuiForm } from '../form';
@@ -21,6 +22,8 @@ jest.mock('../validatable_control', () => ({
}));
describe('EuiFieldSearch', () => {
+ shouldRenderCustomStyles( );
+
test('is rendered', () => {
const { container } = render(
{
);
const input = container.querySelector('.euiFieldSearch');
- expect(input).toHaveClass('euiFieldSearch--fullWidth');
+ expect(input?.className).toContain('fullWidth');
});
});
});
diff --git a/packages/eui/src/components/form/field_search/field_search.tsx b/packages/eui/src/components/form/field_search/field_search.tsx
index b3c9bc595ef..902d33fa932 100644
--- a/packages/eui/src/components/form/field_search/field_search.tsx
+++ b/packages/eui/src/components/form/field_search/field_search.tsx
@@ -8,19 +8,24 @@
import React, { Component, InputHTMLAttributes, KeyboardEvent } from 'react';
import classNames from 'classnames';
+
import { Browser } from '../../../services/browser';
import { CommonProps } from '../../common';
-import { keys } from '../../../services';
+import {
+ keys,
+ withEuiStylesMemoizer,
+ WithEuiStylesMemoizerProps,
+} from '../../../services';
import {
EuiFormControlLayout,
EuiFormControlLayoutProps,
} from '../form_control_layout';
-
import { EuiValidatableControl } from '../validatable_control';
-import { getFormControlClassNameForIconCount } from '../form_control_layout/_num_icons';
import { FormContext, FormContextValue } from '../eui_form_context';
+import { euiFieldSearchStyles } from './field_search.styles';
+
export interface EuiFieldSearchProps
extends CommonProps,
InputHTMLAttributes {
@@ -74,8 +79,8 @@ interface EuiFieldSearchState {
let isSearchSupported: boolean = false;
-export class EuiFieldSearch extends Component<
- EuiFieldSearchProps,
+export class EuiFieldSearchClass extends Component<
+ EuiFieldSearchProps & WithEuiStylesMemoizerProps,
EuiFieldSearchState
> {
static contextType = FormContext;
@@ -205,6 +210,7 @@ export class EuiFieldSearch extends Component<
render() {
const { defaultFullWidth } = this.context as FormContextValue;
const {
+ stylesMemoizer,
className,
id,
name,
@@ -230,19 +236,9 @@ export class EuiFieldSearch extends Component<
_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,
'euiFieldSearch-isInvalid': isInvalid,
@@ -250,6 +246,14 @@ export class EuiFieldSearch extends Component<
className
);
+ const styles = stylesMemoizer(euiFieldSearchStyles);
+ const cssStyles = [
+ styles.euiFieldSearch,
+ compressed ? styles.compressed : styles.uncompressed,
+ fullWidth ? styles.fullWidth : styles.formWidth,
+ (prepend || append) && styles.inGroup,
+ ];
+
return (
this.onKeyUp(e, incremental, onSearch)}
ref={this.setRef}
{...rest}
@@ -281,3 +286,6 @@ export class EuiFieldSearch extends Component<
);
}
}
+
+export const EuiFieldSearch =
+ withEuiStylesMemoizer(EuiFieldSearchClass);
diff --git a/packages/eui/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap b/packages/eui/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap
index df2492e7a8a..68c5fee6a55 100644
--- a/packages/eui/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap
+++ b/packages/eui/src/components/form/field_text/__snapshots__/field_text.test.tsx.snap
@@ -9,7 +9,7 @@ exports[`EuiFieldText is rendered 1`] = `
@@ -37,7 +37,7 @@ exports[`EuiFieldText props fullWidth is rendered 1`] = `
>
@@ -54,7 +54,7 @@ exports[`EuiFieldText props isInvalid is rendered 1`] = `
isinvalid="true"
>
@@ -69,7 +69,7 @@ exports[`EuiFieldText props isLoading is rendered 1`] = `
>
@@ -84,7 +84,7 @@ exports[`EuiFieldText props readOnly is rendered 1`] = `
>
(
+
+ ),
+ ],
+ args: {
+ name: 'autofill-test',
+ },
+};
diff --git a/packages/eui/src/components/form/field_text/field_text.styles.ts b/packages/eui/src/components/form/field_text/field_text.styles.ts
new file mode 100644
index 00000000000..dd1b194e8ca
--- /dev/null
+++ b/packages/eui/src/components/form/field_text/field_text.styles.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+
+import { UseEuiTheme } from '../../../services';
+import { euiFormControlStyles } from '../form.styles';
+
+export const euiFieldTextStyles = (euiThemeContext: UseEuiTheme) => {
+ const formStyles = euiFormControlStyles(euiThemeContext);
+
+ return {
+ euiFieldText: css`
+ ${formStyles.shared}
+
+ &:invalid {
+ ${formStyles.invalid}
+ }
+
+ &:focus {
+ ${formStyles.focus}
+ }
+
+ &:disabled {
+ ${formStyles.disabled}
+ }
+
+ &[readOnly] {
+ ${formStyles.readOnly}
+ }
+
+ &:autofill {
+ ${formStyles.autoFill}
+ }
+ `,
+
+ // Skip the css() on the default height to avoid generating a className
+ uncompressed: formStyles.uncompressed,
+ compressed: css(formStyles.compressed),
+
+ // Skip the css() on the default width to avoid generating a className
+ formWidth: formStyles.formWidth,
+ fullWidth: css(formStyles.fullWidth),
+
+ // Layout modifiers
+ inGroup: css(formStyles.inGroup),
+ controlOnly: css`
+ .euiFormControlLayout--group & {
+ ${formStyles.inGroup}
+ }
+ `,
+ };
+};
diff --git a/packages/eui/src/components/form/field_text/field_text.test.tsx b/packages/eui/src/components/form/field_text/field_text.test.tsx
index cd811d10c6a..4cd427d2b93 100644
--- a/packages/eui/src/components/form/field_text/field_text.test.tsx
+++ b/packages/eui/src/components/form/field_text/field_text.test.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { requiredProps } from '../../../test/required_props';
+import { shouldRenderCustomStyles } from '../../../test/internal';
import { render } from '../../../test/rtl';
import { EuiForm } from '../form';
@@ -25,6 +26,8 @@ jest.mock('../validatable_control', () => ({
}));
describe('EuiFieldText', () => {
+ shouldRenderCustomStyles( );
+
test('is rendered', () => {
const { container } = render(
{
);
const input = getByRole('textbox');
- expect(input).toHaveClass('euiFieldText--fullWidth');
+ expect(input.className).toContain('fullWidth');
});
});
});
diff --git a/packages/eui/src/components/form/field_text/field_text.tsx b/packages/eui/src/components/form/field_text/field_text.tsx
index fb89e2263b4..3f99adc1762 100644
--- a/packages/eui/src/components/form/field_text/field_text.tsx
+++ b/packages/eui/src/components/form/field_text/field_text.tsx
@@ -7,21 +7,19 @@
*/
import React, { InputHTMLAttributes, Ref, FunctionComponent } from 'react';
-import { CommonProps } from '../../common';
import classNames from 'classnames';
+import { useEuiMemoizedStyles } from '../../../services';
+import { CommonProps } from '../../common';
import {
EuiFormControlLayout,
EuiFormControlLayoutProps,
} from '../form_control_layout';
-
import { EuiValidatableControl } from '../validatable_control';
-import {
- isRightSideIcon,
- getFormControlClassNameForIconCount,
-} from '../form_control_layout/_num_icons';
import { useFormContext } from '../eui_form_context';
+import { euiFieldTextStyles } from './field_text.styles';
+
export type EuiFieldTextProps = InputHTMLAttributes &
CommonProps & {
icon?: EuiFormControlLayoutProps['icon'];
@@ -81,26 +79,19 @@ export const EuiFieldText: FunctionComponent = (props) => {
...rest
} = props;
- const hasRightSideIcon = isRightSideIcon(icon);
-
- const numIconsClass = controlOnly
- ? false
- : getFormControlClassNameForIconCount({
- isInvalid,
- isLoading,
- icon: hasRightSideIcon,
- });
-
- const classes = classNames('euiFieldText', className, numIconsClass, {
- 'euiFieldText--fullWidth': fullWidth,
- 'euiFieldText--compressed': compressed,
- ...(!controlOnly && {
- 'euiFieldText--withIcon': icon && !hasRightSideIcon,
- 'euiFieldText--inGroup': prepend || append,
- }),
+ const classes = classNames('euiFieldText', className, {
'euiFieldText-isLoading': isLoading,
});
+ const styles = useEuiMemoizedStyles(euiFieldTextStyles);
+ const cssStyles = [
+ styles.euiFieldText,
+ compressed ? styles.compressed : styles.uncompressed,
+ fullWidth ? styles.fullWidth : styles.formWidth,
+ !controlOnly && (prepend || append) && styles.inGroup,
+ controlOnly && styles.controlOnly,
+ ];
+
const control = (
= (props) => {
name={name}
placeholder={placeholder}
className={classes}
+ css={cssStyles}
value={value}
ref={inputRef}
readOnly={readOnly}
diff --git a/packages/eui/src/components/form/form.styles.test.tsx b/packages/eui/src/components/form/form.styles.test.tsx
index dcf015f0142..80ff5357c83 100644
--- a/packages/eui/src/components/form/form.styles.test.tsx
+++ b/packages/eui/src/components/form/form.styles.test.tsx
@@ -13,7 +13,7 @@ import { EuiProvider } from '../provider';
import {
euiFormVariables,
- euiFormControlSize,
+ euiFormControlStyles,
euiCustomControl,
} from './form.styles';
@@ -28,10 +28,9 @@ describe('euiFormVariables', () => {
Object {
"animationTiming": "150ms ease-in",
"backgroundColor": "#f9fbfd",
- "backgroundDisabledColor": "#eceff5",
+ "backgroundDisabledColor": "#eef1f7",
"backgroundReadOnlyColor": "#FFF",
- "borderColor": "rgba(211,218,230,0.9)",
- "borderDisabledColor": "rgba(211,218,230,0.9)",
+ "borderColor": "rgba(32,38,47,0.1)",
"controlBorderRadius": "6px",
"controlBoxShadow": "0 0 transparent",
"controlCompressedBorderRadius": "4px",
@@ -53,9 +52,12 @@ describe('euiFormVariables', () => {
"controlPlaceholderText": "#646a77",
"customControlBorderColor": "#f5f7fc",
"customControlDisabledIconColor": "#cacfd8",
+ "iconAffordance": "24px",
+ "iconCompressedAffordance": "18px",
"inputGroupBorder": "none",
"inputGroupLabelBackground": "#e9edf3",
"maxWidth": "400px",
+ "textColor": "#343741",
}
`);
});
@@ -73,105 +75,150 @@ describe('euiFormVariables', () => {
});
});
-describe('euiFormControlSize', () => {
- it('outputs the logical properties for height, width, and max-width', () => {
- const { result } = renderHook(() => euiFormControlSize(useEuiTheme()));
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 400px;
- inline-size: 100%;
- block-size: 40px;"
- `);
- });
+describe('euiFormControlStyles', () => {
+ it('outputs an object of control states and modifiers', () => {
+ const { result } = renderHook(() => euiFormControlStyles(useEuiTheme()));
+ expect(result.current).toMatchInlineSnapshot(`
+ Object {
+ "autoFill": "
+ &:-webkit-autofill {
+ -webkit-text-fill-color: #343741;
+ -webkit-box-shadow: inset 0 0 0 1px rgba(0,107,184,0.2), inset 0 0 0 100vw #f0f7fc;
- it('allows passing in a custom height', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), { height: '100px' })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 400px;
- inline-size: 100%;
- block-size: 100px;"
- `);
- });
+ &:invalid {
+ -webkit-box-shadow: inset 0 0 0 1px #BD271E, inset 0 0 0 100vw #f0f7fc;
+ }
+ }
+ ",
+ "compressed": "
+ block-size: 32px;
+ padding-block: 8px;
+ padding-inline-start: calc(8px + (18px * var(--euiFormControlLeftIconsCount, 0)));
+ padding-inline-end: calc(8px + (18px * var(--euiFormControlRightIconsCount, 0)));
+ border-radius: 4px;
+ ",
+ "disabled": "
+ color: #98A2B3;
+ /* Required for Safari */
+ -webkit-text-fill-color: #98A2B3;
+ background-color: #eef1f7;
+ cursor: not-allowed;
- test('fullWidth', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), { fullWidth: true })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 100%;
- inline-size: 100%;
- block-size: 40px;"
- `);
- });
+
+ &::-webkit-input-placeholder {
+ color: #98A2B3;
+ opacity: 1;
+ }
+ &::-moz-placeholder {
+ color: #98A2B3;
+ opacity: 1;
+ }
+ &:-ms-input-placeholder {
+ color: #98A2B3;
+ opacity: 1;
+ }
+ &:-moz-placeholder {
+ color: #98A2B3;
+ opacity: 1;
+ }
+ &::placeholder {
+ color: #98A2B3;
+ opacity: 1;
+ }
- test('compressed', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), { compressed: true })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 400px;
- inline-size: 100%;
- block-size: 32px;"
- `);
- });
+ ",
+ "focus": "
+ --euiFormControlStateColor: #07C;
+ background-color: #FFF;
+ background-size: 100% 100%;
+ outline: none; /* Remove all outlines and rely on our own bottom border gradient */
+ ",
+ "formWidth": "
+ max-inline-size: 400px;
+ inline-size: 100%;
+ ",
+ "fullWidth": "
+ max-inline-size: 100%;
+ inline-size: 100%;
+ ",
+ "inGroup": "
+ block-size: 100%;
+ box-shadow: none;
+ border-radius: 0;
+ ",
+ "invalid": "
+ --euiFormControlStateColor: #BD271E;
+ background-size: 100% 100%;
+ ",
+ "readOnly": "
+ cursor: default;
+ color: #343741;
+ -webkit-text-fill-color: #343741; /* Required for Safari */
- test('compressed & fullWidth', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), { compressed: true, fullWidth: true })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 100%;
- inline-size: 100%;
- block-size: 32px;"
- `);
- });
+ background-color: #FFF;
+ --euiFormControlStateColor: transparent;
+ ",
+ "shared": "
+
+ font-family: 'Inter', BlinkMacSystemFont, Helvetica, Arial, sans-serif;
+ font-size: 1.0000rem;
+ color: #343741;
- test('inGroup', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), { inGroup: true })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 400px;
- inline-size: 100%;
- block-size: 100%;"
- `);
- });
+
+ &::-webkit-input-placeholder {
+ color: #646a77;
+ opacity: 1;
+ }
+ &::-moz-placeholder {
+ color: #646a77;
+ opacity: 1;
+ }
+ &:-ms-input-placeholder {
+ color: #646a77;
+ opacity: 1;
+ }
+ &:-moz-placeholder {
+ color: #646a77;
+ opacity: 1;
+ }
+ &::placeholder {
+ color: #646a77;
+ opacity: 1;
+ }
- test('inGroup & fullWidth', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), { inGroup: true, fullWidth: true })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 100%;
- inline-size: 100%;
- block-size: 100%;"
- `);
- });
+
+
+ /* We use inset box-shadow instead of border to skip extra hight calculations */
+ border: none;
+ box-shadow: inset 0 0 0 1px rgba(32,38,47,0.1);
+ background-color: #f9fbfd;
- test('compressed overrides custom height', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), { height: '500px', compressed: true })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 400px;
- inline-size: 100%;
- block-size: 32px;"
- `);
- });
+ background-repeat: no-repeat;
+ background-size: 0% 100%;
+ background-image: linear-gradient(to top,
+ var(--euiFormControlStateColor),
+ var(--euiFormControlStateColor) 2px,
+ transparent 2px,
+ transparent 100%
+ );
- test('inGroup overrides compressed and custom height', () => {
- const { result } = renderHook(() =>
- euiFormControlSize(useEuiTheme(), {
- height: '500px',
- compressed: true,
- inGroup: true,
- })
- );
- expect(result.current.trim()).toMatchInlineSnapshot(`
- "max-inline-size: 400px;
- inline-size: 100%;
- block-size: 100%;"
+ @media screen and (prefers-reduced-motion: no-preference) {
+ transition:
+ box-shadow 150ms ease-in,
+ background-image 150ms ease-in,
+ background-size 150ms ease-in,
+ background-color 150ms ease-in;
+ }
+
+ ",
+ "uncompressed": "
+ block-size: 40px;
+ padding-block: 12px;
+ padding-inline-start: calc(12px + (24px * var(--euiFormControlLeftIconsCount, 0)));
+ padding-inline-end: calc(12px + (24px * var(--euiFormControlRightIconsCount, 0)));
+ border-radius: 6px;
+ ",
+ }
`);
});
});
diff --git a/packages/eui/src/components/form/form.styles.ts b/packages/eui/src/components/form/form.styles.ts
index 57834bafb09..d47b4940fcc 100644
--- a/packages/eui/src/components/form/form.styles.ts
+++ b/packages/eui/src/components/form/form.styles.ts
@@ -15,10 +15,12 @@ import {
makeHighContrastColor,
} from '../../services';
import {
+ logicalCSS,
mathWithUnits,
euiCanAnimate,
euiFontSize,
} from '../../global_styling';
+import { euiButtonColor } from '../../themes/amsterdam/global_styling/mixins';
export const euiFormVariables = (euiThemeContext: UseEuiTheme) => {
const { euiTheme, colorMode } = euiThemeContext;
@@ -38,14 +40,21 @@ export const euiFormVariables = (euiThemeContext: UseEuiTheme) => {
controlCompressedPadding: euiTheme.size.s,
controlBorderRadius: euiTheme.border.radius.medium,
controlCompressedBorderRadius: euiTheme.border.radius.small,
+ iconAffordance: mathWithUnits(euiTheme.size.base, (x) => x * 1.5),
+ iconCompressedAffordance: mathWithUnits(euiTheme.size.m, (x) => x * 1.5),
};
const colors = {
+ textColor: euiTheme.colors.text,
backgroundColor: backgroundColor,
- backgroundDisabledColor: darken(euiTheme.colors.lightestShade, 0.1),
+ backgroundDisabledColor: darken(euiTheme.colors.lightestShade, 0.05),
backgroundReadOnlyColor: euiTheme.colors.emptyShade,
- borderColor: transparentize(euiTheme.border.color, 0.9),
- borderDisabledColor: transparentize(euiTheme.border.color, 0.9),
+ borderColor: transparentize(
+ colorMode === 'DARK'
+ ? euiTheme.colors.ghost
+ : darken(euiTheme.border.color, 4),
+ 0.1
+ ),
controlDisabledColor: euiTheme.colors.mediumShade,
controlBoxShadow: '0 0 transparent',
controlPlaceholderText: makeHighContrastColor(euiTheme.colors.subduedText)(
@@ -96,31 +105,67 @@ export const euiFormVariables = (euiThemeContext: UseEuiTheme) => {
};
};
-export const euiFormControlSize = (
- euiThemeContext: UseEuiTheme,
- options: {
- height?: string;
- fullWidth?: boolean;
- compressed?: boolean;
- inGroup?: boolean;
- } = {}
-) => {
+export const euiFormControlStyles = (euiThemeContext: UseEuiTheme) => {
const form = euiFormVariables(euiThemeContext);
- const width = '100%';
+ return {
+ shared: `
+ ${euiFormControlText(euiThemeContext)}
+ ${euiFormControlDefaultShadow(euiThemeContext)}
+ `,
- let maxWidth = form.maxWidth;
- if (options.fullWidth) maxWidth = '100%';
+ // Sizes
+ uncompressed: `
+ ${logicalCSS('height', form.controlHeight)}
+ ${logicalCSS('padding-vertical', form.controlPadding)}
+ ${logicalCSS(
+ 'padding-left',
+ `calc(${form.controlPadding} + (${form.iconAffordance} * var(--euiFormControlLeftIconsCount, 0)))`
+ )}
+ ${logicalCSS(
+ 'padding-right',
+ `calc(${form.controlPadding} + (${form.iconAffordance} * var(--euiFormControlRightIconsCount, 0)))`
+ )}
+ border-radius: ${form.controlBorderRadius};
+ `,
+ compressed: `
+ ${logicalCSS('height', form.controlCompressedHeight)}
+ ${logicalCSS('padding-vertical', form.controlCompressedPadding)}
+ ${logicalCSS(
+ 'padding-left',
+ `calc(${form.controlCompressedPadding} + (${form.iconCompressedAffordance} * var(--euiFormControlLeftIconsCount, 0)))`
+ )}
+ ${logicalCSS(
+ 'padding-right',
+ `calc(${form.controlCompressedPadding} + (${form.iconCompressedAffordance} * var(--euiFormControlRightIconsCount, 0)))`
+ )}
+ border-radius: ${form.controlCompressedBorderRadius};
+ `,
- let height = options.height || form.controlHeight;
- if (options.compressed) height = form.controlCompressedHeight;
- if (options.inGroup) height = '100%';
+ // In group
+ inGroup: `
+ ${logicalCSS('height', '100%')}
+ box-shadow: none;
+ border-radius: 0;
+ `,
- return `
- max-inline-size: ${maxWidth};
- inline-size: ${width};
- block-size: ${height};
- `;
+ // Widths
+ formWidth: `
+ ${logicalCSS('max-width', form.maxWidth)}
+ ${logicalCSS('width', '100%')}
+ `,
+ fullWidth: `
+ ${logicalCSS('max-width', '100%')}
+ ${logicalCSS('width', '100%')}
+ `,
+
+ // States
+ invalid: euiFormControlInvalidStyles(euiThemeContext),
+ focus: euiFormControlFocusStyles(euiThemeContext),
+ disabled: euiFormControlDisabledStyles(euiThemeContext),
+ readOnly: euiFormControlReadOnlyStyles(euiThemeContext),
+ autoFill: euiFormControlAutoFillStyles(euiThemeContext),
+ };
};
export const euiCustomControl = (
@@ -165,14 +210,17 @@ export const euiCustomControl = (
export const euiFormControlText = (euiThemeContext: UseEuiTheme) => {
const { euiTheme } = euiThemeContext;
const { fontSize } = euiFontSize(euiThemeContext, 's');
- const { controlPlaceholderText } = euiFormVariables(euiThemeContext);
+ const form = euiFormVariables(euiThemeContext);
return `
font-family: ${euiTheme.font.family};
font-size: ${fontSize};
- color: ${euiTheme.colors.text};
+ color: ${form.textColor};
- ${euiPlaceholderPerBrowser(`color: ${controlPlaceholderText}`)}
+ ${euiPlaceholderPerBrowser(`
+ color: ${form.controlPlaceholderText};
+ opacity: 1;
+ `)}
`;
};
@@ -181,14 +229,16 @@ export const euiFormControlDefaultShadow = (euiThemeContext: UseEuiTheme) => {
const form = euiFormVariables(euiThemeContext);
return `
- box-shadow: inset 0 0 0 1px ${form.borderColor};
+ /* We use inset box-shadow instead of border to skip extra hight calculations */
+ border: none;
+ box-shadow: inset 0 0 0 ${euiTheme.border.width.thin} ${form.borderColor};
background-color: ${form.backgroundColor};
background-repeat: no-repeat;
background-size: 0% 100%;
background-image: linear-gradient(to top,
- var(--euiFormStateColor),
- var(--euiFormStateColor) ${euiTheme.border.width.thick},
+ var(--euiFormControlStateColor),
+ var(--euiFormControlStateColor) ${euiTheme.border.width.thick},
transparent ${euiTheme.border.width.thick},
transparent 100%
);
@@ -207,7 +257,7 @@ export const euiFormControlFocusStyles = ({
euiTheme,
colorMode,
}: UseEuiTheme) => `
- --euiFormStateColor: ${euiTheme.colors.primary};
+ --euiFormControlStateColor: ${euiTheme.colors.primary};
background-color: ${
colorMode === 'DARK'
? shade(euiTheme.colors.emptyShade, 0.4)
@@ -217,10 +267,80 @@ export const euiFormControlFocusStyles = ({
outline: none; /* Remove all outlines and rely on our own bottom border gradient */
`;
+export const euiFormControlInvalidStyles = ({ euiTheme }: UseEuiTheme) => `
+ --euiFormControlStateColor: ${euiTheme.colors.danger};
+ background-size: 100% 100%;
+`;
+
+export const euiFormControlDisabledStyles = (euiThemeContext: UseEuiTheme) => {
+ const form = euiFormVariables(euiThemeContext);
+
+ return `
+ color: ${form.controlDisabledColor};
+ /* Required for Safari */
+ -webkit-text-fill-color: ${form.controlDisabledColor};
+ background-color: ${form.backgroundDisabledColor};
+ cursor: not-allowed;
+
+ ${euiPlaceholderPerBrowser(`
+ color: ${form.controlDisabledColor};
+ opacity: 1;
+ `)}
+ `;
+};
+
+export const euiFormControlReadOnlyStyles = (euiThemeContext: UseEuiTheme) => {
+ const form = euiFormVariables(euiThemeContext);
+
+ return `
+ cursor: default;
+ color: ${form.textColor};
+ -webkit-text-fill-color: ${form.textColor}; /* Required for Safari */
+
+ background-color: ${form.backgroundReadOnlyColor};
+ --euiFormControlStateColor: transparent;
+ `;
+};
+
+export const euiFormControlAutoFillStyles = (euiThemeContext: UseEuiTheme) => {
+ const { euiTheme, colorMode } = euiThemeContext;
+
+ // Make the text color slightly less prominent than the default colors.text
+ const textColor = euiTheme.colors.darkestShade;
+
+ const { backgroundColor } = euiButtonColor(euiThemeContext, 'primary');
+ const tintedBackgroundColor =
+ colorMode === 'DARK'
+ ? shade(backgroundColor, 0.5)
+ : tint(backgroundColor, 0.7);
+ // Hacky workaround to background-color, since Chrome doesn't normally allow overriding its styles
+ // @see https://developer.mozilla.org/en-US/docs/Web/CSS/:autofill#sect1
+ const backgroundShadow = `inset 0 0 0 100vw ${tintedBackgroundColor}`;
+
+ // Re-create the border, since the above webkit box shadow overrides the default border box-shadow
+ // + change the border color to match states, since the underline background gradient no longer works
+ const borderColor = transparentize(euiTheme.colors.primaryText, 0.2);
+ const invalidBorder = euiTheme.colors.danger;
+ const borderShadow = (color: string) =>
+ `inset 0 0 0 ${euiTheme.border.width.thin} ${color}`;
+
+ // These styles only apply/override Chrome/webkit browsers - Firefox does not set autofill styles
+ return `
+ &:-webkit-autofill {
+ -webkit-text-fill-color: ${textColor};
+ -webkit-box-shadow: ${borderShadow(borderColor)}, ${backgroundShadow};
+
+ &:invalid {
+ -webkit-box-shadow: ${borderShadow(invalidBorder)}, ${backgroundShadow};
+ }
+ }
+ `;
+};
+
const euiPlaceholderPerBrowser = (content: string) => `
- &::-webkit-input-placeholder { ${content}; opacity: 1; }
- &::-moz-placeholder { ${content}; opacity: 1; }
- &:-ms-input-placeholder { ${content}; opacity: 1; }
- &:-moz-placeholder { ${content}; opacity: 1; }
- &::placeholder { ${content}; opacity: 1; }
+ &::-webkit-input-placeholder { ${content} }
+ &::-moz-placeholder { ${content} }
+ &:-ms-input-placeholder { ${content} }
+ &:-moz-placeholder { ${content} }
+ &::placeholder { ${content} }
`;
diff --git a/packages/eui/src/components/form/form_control_layout/_form_control_layout.scss b/packages/eui/src/components/form/form_control_layout/_form_control_layout.scss
index b0b41443cf1..d94c5af5e36 100644
--- a/packages/eui/src/components/form/form_control_layout/_form_control_layout.scss
+++ b/packages/eui/src/components/form/form_control_layout/_form_control_layout.scss
@@ -5,6 +5,7 @@
// Let the height expand as needed
@include euiFormControlSize($includeAlternates: true);
+ // TODO: Remove this once all form controls are on Emotion/setting padding via CSS variables
$iconSize: map-get($euiFormControlIconSizes, 'medium');
$iconPadding: $euiFormControlPadding;
$marginBetweenIcons: $euiFormControlPadding / 2;
diff --git a/packages/eui/src/components/form/form_control_layout/_num_icons.test.ts b/packages/eui/src/components/form/form_control_layout/_num_icons.test.ts
index 4b3f26693e7..2ac6beb228e 100644
--- a/packages/eui/src/components/form/form_control_layout/_num_icons.test.ts
+++ b/packages/eui/src/components/form/form_control_layout/_num_icons.test.ts
@@ -9,8 +9,68 @@
import {
getFormControlClassNameForIconCount,
isRightSideIcon,
+ getIconAffordanceStyles,
} from './_num_icons';
+describe('getIconAffordanceStyles', () => {
+ const noIcons = {
+ icon: undefined,
+ clear: false,
+ isLoading: false,
+ isInvalid: false,
+ isDropdown: false,
+ };
+ const allIcons = {
+ icon: { type: 'search', side: 'right' as const },
+ clear: true,
+ isLoading: true,
+ isInvalid: true,
+ isDropdown: true,
+ };
+
+ test('empty object', () => {
+ const styles = getIconAffordanceStyles({});
+ expect(styles).toEqual(undefined);
+ });
+
+ test('false values', () => {
+ const styles = getIconAffordanceStyles(noIcons);
+ expect(styles).toEqual(undefined);
+ });
+
+ test('all icons', () => {
+ const styles = getIconAffordanceStyles(allIcons);
+ expect(styles).toMatchInlineSnapshot(`
+ Object {
+ "--euiFormControlRightIconsCount": 5,
+ }
+ `);
+ });
+
+ test('some icons', () => {
+ const styles = getIconAffordanceStyles({
+ isLoading: true,
+ isInvalid: true,
+ });
+ expect(styles).toMatchInlineSnapshot(`
+ Object {
+ "--euiFormControlRightIconsCount": 2,
+ }
+ `);
+ });
+
+ test('left icon', () => {
+ const styles = getIconAffordanceStyles({
+ icon: 'search',
+ });
+ expect(styles).toMatchInlineSnapshot(`
+ Object {
+ "--euiFormControlLeftIconsCount": 1,
+ }
+ `);
+ });
+});
+
describe('getFormControlClassNameForIconCount', () => {
it('should return undefined if object is empty', () => {
const numberClass = getFormControlClassNameForIconCount({});
diff --git a/packages/eui/src/components/form/form_control_layout/_num_icons.ts b/packages/eui/src/components/form/form_control_layout/_num_icons.ts
index 95c792c4cf2..1dad9f03c73 100644
--- a/packages/eui/src/components/form/form_control_layout/_num_icons.ts
+++ b/packages/eui/src/components/form/form_control_layout/_num_icons.ts
@@ -51,3 +51,40 @@ export const isRightSideIcon = (
): boolean => {
return !!icon && isIconShape(icon) && icon.side === 'right';
};
+
+export const getIconAffordanceStyles = ({
+ icon,
+ clear,
+ isLoading,
+ isInvalid,
+ isDropdown,
+}: {
+ icon?: EuiFormControlLayoutIconsProps['icon'];
+ clear?: EuiFormControlLayoutIconsProps['clear'] | boolean;
+ isLoading?: boolean;
+ isInvalid?: boolean;
+ isDropdown?: boolean;
+}) => {
+ const cssVariables = {
+ '--euiFormControlLeftIconsCount': 0,
+ '--euiFormControlRightIconsCount': 0,
+ };
+
+ if (icon) {
+ if (isRightSideIcon(icon)) {
+ cssVariables['--euiFormControlRightIconsCount']++;
+ } else {
+ cssVariables['--euiFormControlLeftIconsCount']++;
+ }
+ }
+
+ if (clear) cssVariables['--euiFormControlRightIconsCount']++;
+ if (isLoading) cssVariables['--euiFormControlRightIconsCount']++;
+ if (isInvalid) cssVariables['--euiFormControlRightIconsCount']++;
+ if (isDropdown) cssVariables['--euiFormControlRightIconsCount']++;
+
+ const filtered = Object.entries(cssVariables).filter(
+ ([, count]) => count > 0
+ );
+ return filtered.length ? Object.fromEntries(filtered) : undefined;
+};
diff --git a/packages/eui/src/components/form/form_control_layout/form_control_layout.tsx b/packages/eui/src/components/form/form_control_layout/form_control_layout.tsx
index 5039efa0268..00b20ab9cca 100644
--- a/packages/eui/src/components/form/form_control_layout/form_control_layout.tsx
+++ b/packages/eui/src/components/form/form_control_layout/form_control_layout.tsx
@@ -8,21 +8,23 @@
import React, {
cloneElement,
- Component,
+ FunctionComponent,
HTMLAttributes,
ReactElement,
ReactNode,
+ useCallback,
+ useMemo,
} from 'react';
import classNames from 'classnames';
+import { getIconAffordanceStyles, isRightSideIcon } from './_num_icons';
import {
EuiFormControlLayoutIcons,
EuiFormControlLayoutIconsProps,
- IconShape,
} from './form_control_layout_icons';
import { CommonProps } from '../../common';
import { EuiFormLabel } from '../form_label';
-import { FormContext, FormContextValue } from '../eui_form_context';
+import { useFormContext } from '../eui_form_context';
type StringOrReactElement = string | ReactElement;
type PrependAppendType = StringOrReactElement | StringOrReactElement[];
@@ -41,6 +43,11 @@ export type EuiFormControlLayoutProps = CommonProps &
append?: PrependAppendType;
children?: ReactNode;
icon?: EuiFormControlLayoutIconsProps['icon'];
+ /**
+ * Determines whether icons are absolutely or statically rendered. For single inputs,
+ * absolute rendering is typically preferred.
+ * @default absolute
+ */
iconsPosition?: EuiFormControlLayoutIconsProps['iconsPosition'];
clear?: EuiFormControlLayoutIconsProps['clear'];
/**
@@ -65,156 +72,134 @@ export type EuiFormControlLayoutProps = CommonProps &
inputId?: string;
};
-export class EuiFormControlLayout extends Component {
- static contextType = FormContext;
-
- render() {
- const { defaultFullWidth } = this.context as FormContextValue;
- const {
- children,
+export const EuiFormControlLayout: FunctionComponent<
+ EuiFormControlLayoutProps
+> = (props) => {
+ const { defaultFullWidth } = useFormContext();
+ const {
+ inputId,
+ className,
+ children,
+ icon,
+ iconsPosition = 'absolute',
+ clear,
+ isDropdown,
+ isLoading,
+ isInvalid,
+ isDisabled,
+ readOnly,
+ compressed,
+ prepend,
+ append,
+ fullWidth = defaultFullWidth,
+ ...rest
+ } = props;
+
+ const classes = classNames(
+ 'euiFormControlLayout',
+ {
+ 'euiFormControlLayout--fullWidth': fullWidth,
+ 'euiFormControlLayout--compressed': compressed,
+ 'euiFormControlLayout--readOnly': readOnly,
+ 'euiFormControlLayout--group': prepend || append,
+ 'euiFormControlLayout-isDisabled': isDisabled,
+ },
+ className
+ );
+
+ const hasDropdownIcon = !readOnly && !isDisabled && isDropdown;
+ const hasRightIcon = isRightSideIcon(icon);
+ const hasLeftIcon = icon && !hasRightIcon;
+ const hasRightIcons =
+ hasRightIcon || clear || isLoading || isInvalid || hasDropdownIcon;
+
+ const iconAffordanceStyles = useMemo(() => {
+ if (iconsPosition === 'static') return; // Static icons don't need padding affordance
+
+ return getIconAffordanceStyles({
icon,
- iconsPosition,
clear,
- fullWidth = defaultFullWidth,
- isLoading,
- isDisabled,
- compressed,
- className,
- prepend,
- append,
- readOnly,
isInvalid,
- isDropdown,
- inputId,
- ...rest
- } = this.props;
-
- const classes = classNames(
- 'euiFormControlLayout',
- {
- 'euiFormControlLayout--fullWidth': fullWidth,
- 'euiFormControlLayout--compressed': compressed,
- 'euiFormControlLayout--readOnly': readOnly,
- 'euiFormControlLayout--group': prepend || append,
- 'euiFormControlLayout-isDisabled': isDisabled,
- },
- className
- );
-
- const prependNodes = this.renderSideNode('prepend', prepend, inputId);
- const appendNodes = this.renderSideNode('append', append, inputId);
-
- return (
-
- {prependNodes}
-
- {this.renderLeftIcons()}
- {children}
- {this.renderRightIcons()}
-
- {appendNodes}
-
- );
- }
-
- renderLeftIcons = () => {
- const { icon, iconsPosition, compressed } = this.props;
-
- const leftCustomIcon =
- icon && (icon as IconShape)?.side !== 'right' ? icon : undefined;
-
- return leftCustomIcon ? (
-
- ) : null;
- };
-
- renderRightIcons = () => {
- const {
- icon,
- iconsPosition,
- clear,
- compressed,
isLoading,
- isInvalid,
- isDisabled,
- readOnly,
- isDropdown,
- } = this.props;
- const hasDropdownIcon = !readOnly && !isDisabled && isDropdown;
-
- const rightCustomIcon =
- icon && (icon as IconShape)?.side === 'right' ? icon : undefined;
-
- const hasRightIcons =
- rightCustomIcon || clear || isLoading || isInvalid || hasDropdownIcon;
-
- return hasRightIcons ? (
-
+
- ) : null;
- };
-
- renderSideNode(
- side: 'append' | 'prepend',
- nodes?: PrependAppendType,
- inputId?: string
- ) {
- if (!nodes) {
- return;
- }
-
- if (typeof nodes === 'string') {
- return this.createFormLabel(side, nodes, inputId);
- }
-
- const appendNodes = React.Children.map(nodes, (item, index) =>
- typeof item === 'string'
- ? this.createFormLabel(side, item, inputId)
- : this.createSideNode(side, item, index)
- );
-
- return appendNodes;
- }
-
- createFormLabel(
- side: 'append' | 'prepend',
- string: string,
- inputId?: string
- ) {
- return (
-
- {string}
+ {hasLeftIcon && (
+
+ )}
+
+ {children}
+
+ {hasRightIcons && (
+
+ )}
+
+
+
+ );
+};
+
+/**
+ * Internal subcomponent utility for prepend/append nodes
+ */
+const EuiFormControlLayoutSideNodes: FunctionComponent<{
+ side: 'append' | 'prepend';
+ nodes?: PrependAppendType; // For some bizarre reason if you make this the `children` prop instead, React doesn't properly override cloned keys :|
+ inputId?: string;
+}> = ({ side, nodes, inputId }) => {
+ const className = `euiFormControlLayout__${side}`;
+
+ const renderFormLabel = useCallback(
+ (label: string) => (
+
+ {label}
- );
- }
-
- createSideNode(
- side: 'append' | 'prepend',
- node: ReactElement,
- key: React.Key
- ) {
- return cloneElement(node, {
- className: classNames(
- `euiFormControlLayout__${side}`,
- node.props.className
- ),
- key: key,
- });
- }
-}
+ ),
+ [inputId, className]
+ );
+
+ if (!nodes) return null;
+
+ return (
+ <>
+ {React.Children.map(nodes, (node, index) =>
+ typeof node === 'string'
+ ? renderFormLabel(node)
+ : cloneElement(node, {
+ className: classNames(className, node.props.className),
+ key: index,
+ })
+ )}
+ >
+ );
+};
diff --git a/packages/eui/src/components/form/range/__snapshots__/dual_range.test.tsx.snap b/packages/eui/src/components/form/range/__snapshots__/dual_range.test.tsx.snap
index 341ba7373e8..7b28bd5627b 100644
--- a/packages/eui/src/components/form/range/__snapshots__/dual_range.test.tsx.snap
+++ b/packages/eui/src/components/form/range/__snapshots__/dual_range.test.tsx.snap
@@ -2,7 +2,7 @@
exports[`EuiDualRange props isDraggable renders draggable track when isDraggable=true 1`] = `