diff --git a/packages/eui-theme-common/src/global_styling/variables/typography.ts b/packages/eui-theme-common/src/global_styling/variables/typography.ts index 721db617a59..78b0bd02f80 100644 --- a/packages/eui-theme-common/src/global_styling/variables/typography.ts +++ b/packages/eui-theme-common/src/global_styling/variables/typography.ts @@ -97,11 +97,11 @@ export type _EuiThemeFontWeights = { light: CSSProperties['fontWeight']; /** - Default value: 400 */ regular: CSSProperties['fontWeight']; - /** - Default value: 500 */ + /** - Default value: 450 */ medium: CSSProperties['fontWeight']; - /** - Default value: 600 */ + /** - Default value: 500 */ semiBold: CSSProperties['fontWeight']; - /** - Default value: 700 */ + /** - Default value: 600 */ bold: CSSProperties['fontWeight']; }; diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png index 0c1a0c9c9b1..23c7846ef80 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png index ead0ba552e3..03d2635e3e1 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png index a457b614927..4880b636020 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png index 45881158d0d..8f13125589b 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png index 52305ead205..5623b169cd0 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png index db5c638321d..fb1e5447080 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormAppend_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormAppend_Playground.png new file mode 100644 index 00000000000..6c8c24c1efd Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormAppend_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormPrepend_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormPrepend_Playground.png new file mode 100644 index 00000000000..c75673cb213 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormPrepend_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png index 934c10caef6..f48ca9ca90d 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png index d691fac33df..74a3a37bfcb 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png index e57b22afce3..e6178e22908 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png index 5265605f79a..ae52b3e3741 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png index b9474d0ed93..b919048e123 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png index 58ef2bdb79c..ad5d714bdf0 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png index b9474d0ed93..b919048e123 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png index f107ed3d3e0..d081d81a7f6 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png index 794ab275e0b..1118848852a 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png index ea5d99d8241..80efb8dbc19 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiAutoRefresh_EuiAutoRefresh_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png index a7790794937..2016395e05b 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Append_Prepend.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png index 54b9753c480..20757616b06 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png index 0ff5718a97d..5bd985130b4 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_High_Contrast_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormAppend_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormAppend_Playground.png new file mode 100644 index 00000000000..c57d23eb560 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormAppend_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormPrepend_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormPrepend_Playground.png new file mode 100644 index 00000000000..7df807b02dd Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Subcomponents_EuiFormPrepend_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png index 16d2a95aca9..98b3da2a6ce 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Collapsed_Quick_Select_Only.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png index 5a7515ad730..14fdad1d9cf 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png index 925c79da2ac..4fd0770a524 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Time_Zone_Display.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png index 7310e8d1a55..4e373243bf1 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Overflowing_Children.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png index cf5e753247b..fe34be8de62 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png index ef9e69670f6..c17f448f2be 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png index cf5e753247b..fe34be8de62 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png index a9dd1b2b639..391886fd90d 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png index c4289c9b640..7688d48aa1d 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Time_Window_Buttons_Compressed.png differ diff --git a/packages/eui/changelogs/upcoming/9014.md b/packages/eui/changelogs/upcoming/9014.md new file mode 100644 index 00000000000..a123cc959ee --- /dev/null +++ b/packages/eui/changelogs/upcoming/9014.md @@ -0,0 +1,13 @@ +- Added `EuiFormAppend` and `EuiFormPrepend` components +- Added support for `type="span"` on `EuiFormLabel` to support using visual-only form labels +- Updated `EuiFormLabel` to render a `span` if no `htmlFor` is passed +- Updated `EuiFormControlLayout` to use `EuiFormAppend` and `EuiFormPrepend` +- Updated `EuiAutoRefresh` and `EuiColorPicker` to use `EuiFormPrepend` + +**Breaking changes** + +- Updated `EuiQuickSelectPopover` in `EuiSuperDatePicker` to use `EuiFormPrepend`. This results in more restricted `buttonProps` as they reflect `EuiFormPrepend` instead of generic `EuiButtonEmpty` props. + +**Bug fixes** + +- Updated `EuiColorPicker` to ensure `id` is correctly passed onto the internal `EuiFormControlLayout` 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 c4101d93c6b..9a276485928 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 @@ -628,11 +628,15 @@ exports[`EuiColorPicker prepend and append 1`] = `
- + + prepend + +
- + + append + +
diff --git a/packages/eui/src/components/color_picker/color_picker.styles.ts b/packages/eui/src/components/color_picker/color_picker.styles.ts index 25d86472346..b8881cfdeff 100644 --- a/packages/eui/src/components/color_picker/color_picker.styles.ts +++ b/packages/eui/src/components/color_picker/color_picker.styles.ts @@ -46,7 +46,7 @@ export const euiColorPickerStyles = (euiThemeContext: UseEuiTheme) => { padding-inline: ${euiTheme.size.xs}; } - .euiFormControlLayout__append { + .euiFormAppend { padding-inline: ${euiTheme.size.xxs} !important; } diff --git a/packages/eui/src/components/color_picker/color_picker.tsx b/packages/eui/src/components/color_picker/color_picker.tsx index 2521673beb7..e5babd1c12d 100644 --- a/packages/eui/src/components/color_picker/color_picker.tsx +++ b/packages/eui/src/components/color_picker/color_picker.tsx @@ -602,6 +602,7 @@ export const EuiColorPicker: FunctionComponent = ({ isInvalid={isInvalid} isDisabled={disabled} isDropdown + inputId={id} > @@ -60,25 +52,17 @@ exports[`EuiAutoRefresh isPaused is false 1`] = ` class="euiFormControlLayout__prepend emotion-euiFormControlLayout__side-prepend" > @@ -108,25 +92,17 @@ exports[`EuiAutoRefresh refreshInterval is rendered 1`] = ` class="euiFormControlLayout__prepend emotion-euiFormControlLayout__side-prepend" > diff --git a/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx b/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx index d8b08dd5366..227900d8d88 100644 --- a/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx +++ b/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx @@ -8,7 +8,7 @@ import React, { FunctionComponent, useState } from 'react'; import classNames from 'classnames'; -import { EuiFieldText, EuiFieldTextProps } from '../../form'; +import { EuiFieldText, EuiFieldTextProps, EuiFormPrepend } from '../../form'; import { EuiButtonEmpty, CommonEuiButtonEmptyProps, @@ -61,18 +61,14 @@ export const EuiAutoRefresh: FunctionComponent = ({ aria-label={autoRefeshLabel} onClick={() => setIsPopoverOpen((isOpen) => !isOpen)} prepend={ - setIsPopoverOpen((isOpen) => !isOpen)} - size="s" - color="text" - iconType="timeRefresh" + element="button" + label={{autoRefeshLabel}} + iconLeft="timeRefresh" isDisabled={isDisabled} - > - - {autoRefeshLabel} - - + onClick={() => setIsPopoverOpen((isOpen) => !isOpen)} + /> } readOnly={readOnly} disabled={isDisabled} 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 f92b1c01f1a..ad03158d798 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 @@ -16,26 +16,16 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo > @@ -103,26 +93,16 @@ exports[`EuiSuperDatePicker props compressed is rendered 1`] = ` > @@ -189,25 +169,17 @@ exports[`EuiSuperDatePicker props isAutoRefreshOnly is rendered 1`] = ` class="euiFormControlLayout__prepend emotion-euiFormControlLayout__side-prepend" > @@ -242,26 +214,18 @@ exports[`EuiSuperDatePicker props isAutoRefreshOnly passes required props 1`] = class="euiFormControlLayout__prepend emotion-euiFormControlLayout__side-prepend" > @@ -316,27 +280,17 @@ exports[`EuiSuperDatePicker props isDisabled true 1`] = ` > @@ -406,26 +360,16 @@ exports[`EuiSuperDatePicker props isQuickSelectOnly is rendered 1`] = ` > @@ -472,26 +416,16 @@ exports[`EuiSuperDatePicker props showUpdateButton can be false 1`] = ` > @@ -538,26 +472,16 @@ exports[`EuiSuperDatePicker props showUpdateButton can be iconOnly 1`] = ` > @@ -628,26 +552,16 @@ exports[`EuiSuperDatePicker props width can be auto 1`] = ` > @@ -715,26 +629,16 @@ exports[`EuiSuperDatePicker props width can be full 1`] = ` > @@ -803,26 +707,16 @@ exports[`EuiSuperDatePicker renders 1`] = ` > @@ -894,26 +788,16 @@ exports[`EuiSuperDatePicker renders an EuiDatePickerRange 1`] = ` > diff --git a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap index 42af0e09ff1..5dee6906419 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap +++ b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap @@ -313,11 +313,11 @@ exports[`EuiQuickSelectPanels customQuickSelectPanels should render custom panel class="euiSwitch__label emotion-euiSwitch__label-compressed" id="generated-id" > - + @@ -418,26 +418,16 @@ exports[`EuiQuickSelectPopover is rendered 1`] = ` > `; diff --git a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx index e1469d7d5af..7848881ac46 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx +++ b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx @@ -18,11 +18,8 @@ import React, { import { useEuiMemoizedStyles } from '../../../../services'; import { useEuiI18n } from '../../../i18n'; -import { EuiButtonEmpty } from '../../../button'; -import { EuiButtonEmptyPropsForButton } from '../../../button/button_empty/button_empty'; -import { EuiIcon } from '../../../icon'; import { EuiPopover } from '../../../popover'; - +import { EuiFormAppendPrependButtonProps, EuiFormPrepend } from '../../../form'; import { euiQuickSelectPopoverStyles } from './quick_select_popover.styles'; import { EuiQuickSelectPanel } from './quick_select_panel'; import { EuiQuickSelect } from './quick_select'; @@ -41,7 +38,7 @@ import { } from '../../types'; export type EuiQuickSelectButtonProps = Partial< - Omit + Omit >; export type CustomQuickSelectRenderOptions = { @@ -76,11 +73,7 @@ export interface EuiQuickSelectPopoverProps { export const EuiQuickSelectPopover: FunctionComponent< EuiQuickSelectPopoverProps > = ({ applyTime: _applyTime, buttonProps = {}, ...props }) => { - const { - contentProps: buttonContentProps, - onClick: buttonOnClick, - ...quickSelectButtonProps - } = buttonProps; + const { onClick: buttonOnClick, ...quickSelectButtonProps } = buttonProps; const [prevQuickSelect, setQuickSelect] = useState(); const [isOpen, setIsOpen] = useState(false); @@ -105,8 +98,6 @@ export const EuiQuickSelectPopover: FunctionComponent< 'Date quick select' ); - const styles = useEuiMemoizedStyles(euiQuickSelectPopoverStyles); - const quickSelectButtonOnClick = ( e: MouseEvent & MouseEvent ) => { @@ -115,24 +106,17 @@ export const EuiQuickSelectPopover: FunctionComponent< }; const quickSelectButton = ( - - - + /> ); return ( diff --git a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx index a272bc261a5..c21603d6193 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx +++ b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx @@ -158,7 +158,7 @@ describe('EuiSuperDatePicker', () => { const quickSelectButtonProps: EuiSuperDatePickerProps['quickSelectButtonProps'] = { onMouseDown, - color: 'danger', + 'aria-label': 'Quick Select', }; const { getByTestSubject } = render( @@ -170,7 +170,10 @@ describe('EuiSuperDatePicker', () => { const quickSelectButton = getByTestSubject( 'superDatePickerToggleQuickMenuButton' )!; - expect(quickSelectButton.className).toContain('danger'); + expect(quickSelectButton).toHaveAttribute( + 'aria-label', + quickSelectButtonProps['aria-label'] + ); fireEvent.mouseDown(quickSelectButton); expect(onMouseDown).toHaveBeenCalledTimes(1); 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 260aeb8580b..7e4d4d2a393 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 @@ -101,11 +101,11 @@ exports[`EuiFieldPassword props dual dual type also renders append 1`] = `
- + Span @@ -298,11 +298,15 @@ exports[`EuiFieldPassword props prepend and append is rendered 1`] = `
- + + String + +
- + + String + +
`; diff --git a/packages/eui/src/components/form/field_password/field_password.stories.tsx b/packages/eui/src/components/form/field_password/field_password.stories.tsx index e0ffabd8293..a63950576e9 100644 --- a/packages/eui/src/components/form/field_password/field_password.stories.tsx +++ b/packages/eui/src/components/form/field_password/field_password.stories.tsx @@ -13,8 +13,8 @@ import { disableStorybookControls, enableFunctionToggleControls, } from '../../../../.storybook/utils'; -import { EuiIcon } from '../../icon'; import { EuiFieldPassword, EuiFieldPasswordProps } from './field_password'; +import { EuiFormAppend, EuiFormPrepend } from '../form_control_layout'; const meta: Meta = { title: 'Forms/EuiFieldPassword', @@ -24,7 +24,7 @@ const meta: Meta = { control: 'radio', options: [undefined, 'icon', 'text'], mapping: { - icon: , + icon: , text: 'Appended', undefined: undefined, }, @@ -33,7 +33,7 @@ const meta: Meta = { control: 'radio', options: [undefined, 'icon', 'text'], mapping: { - icon: , + icon: , text: 'Prepended', undefined: undefined, }, diff --git a/packages/eui/src/components/form/field_search/field_search.stories.tsx b/packages/eui/src/components/form/field_search/field_search.stories.tsx index 40f95f854ba..d07e782e551 100644 --- a/packages/eui/src/components/form/field_search/field_search.stories.tsx +++ b/packages/eui/src/components/form/field_search/field_search.stories.tsx @@ -13,8 +13,8 @@ import { disableStorybookControls, enableFunctionToggleControls, } from '../../../../.storybook/utils'; -import { EuiIcon } from '../../icon'; import { EuiFieldSearch, EuiFieldSearchProps } from './field_search'; +import { EuiFormAppend, EuiFormPrepend } from '../form_control_layout'; const meta: Meta = { title: 'Forms/EuiFieldSearch', @@ -24,7 +24,7 @@ const meta: Meta = { control: 'radio', options: [undefined, 'icon', 'text'], mapping: { - icon: , + icon: , text: 'Appended', undefined: undefined, }, @@ -33,7 +33,7 @@ const meta: Meta = { control: 'radio', options: [undefined, 'icon', 'text'], mapping: { - icon: , + icon: , text: 'Prepended', undefined: undefined, }, diff --git a/packages/eui/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap b/packages/eui/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap index 2440cca85fa..26bb7faaa93 100644 --- a/packages/eui/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap +++ b/packages/eui/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap @@ -368,11 +368,15 @@ exports[`EuiFormControlLayout props one append string is rendered 1`] = `
- + + 1 + +
`; @@ -420,11 +424,15 @@ exports[`EuiFormControlLayout props one prepend string is rendered 1`] = `
- + + 1 + +
+ + Label + +
+`; + +exports[`EuiFormAppendPrepend is rendered 1`] = ` +
+ + Label + +
+`; + +exports[`EuiFormPrepend is rendered 1`] = ` +
+ + Label + +
+`; diff --git a/packages/eui/src/components/form/form_control_layout/append_prepend/form_append.stories.tsx b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append.stories.tsx new file mode 100644 index 00000000000..5c170d3c180 --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append.stories.tsx @@ -0,0 +1,76 @@ +/* + * 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 React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { + hideStorybookControls, + enableFunctionToggleControls, +} from '../../../../../.storybook/utils'; + +import { EuiFieldText } from '../../field_text'; +import { EuiNotificationBadge } from '../../../badge'; +import { EuiFormAppend, type EuiFormAppendProps } from './form_append_prepend'; + +const meta: Meta = { + title: 'Forms/EuiForm/EuiFormControlLayout/Subcomponents/EuiFormAppend', + component: EuiFormAppend, + argTypes: { + label: { control: 'text' }, + iconLeft: { control: 'text' }, + iconRight: { control: 'text' }, + children: { + control: 'radio', + options: [undefined, 'badge', 'text'], + mapping: { + badge: 1, + text: 'Content', + undefined: undefined, + }, + }, + isDisabled: { control: 'boolean' }, + }, + args: { + inputId: '', + element: 'div', + compressed: false, + label: '', + iconLeft: '', + iconRight: '', + children: undefined, + // @ts-expect-error - ignore exclusive union + isDisabled: false, + }, +}; +hideStorybookControls(meta, ['aria-label']); +enableFunctionToggleControls(meta, ['onClick']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + label: 'Append', + // @ts-expect-error - onClick is optional but the toggle is enabled + onClick: false, + }, + render: ({ compressed, inputId, ...args }: EuiFormAppendProps) => { + const textFieldProps = { + compressed, + id: inputId, + }; + + return ( + } + /> + ); + }, +}; diff --git a/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.styles.ts b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.styles.ts new file mode 100644 index 00000000000..380710945e3 --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.styles.ts @@ -0,0 +1,100 @@ +/* + * 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 '@elastic/eui-theme-common'; + +import { logicalCSS } from '../../../../global_styling'; +import { buttonSelectors } from '../form_control_layout.styles'; +import { euiFormVariables } from '../../form.styles'; + +export const euiFormAppendPrependStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + const form = euiFormVariables(euiThemeContext); + + const buttons = buttonSelectors; + + return { + side: css` + position: relative; + display: flex; + align-items: center; + gap: ${euiTheme.size.xs}; + block-size: 100%; + max-inline-size: 100%; + `, + uncompressed: css` + &:not(:has(> ${buttons}:first-child, > *:first-child ${buttons})) { + ${logicalCSS('padding-left', euiTheme.size.m)} + } + + &:not(:has(> ${buttons}:last-child, > *:last-child ${buttons})) { + ${logicalCSS('padding-right', euiTheme.size.m)} + } + `, + compressed: css` + &:not(:has(> ${buttons}:first-child, > *:first-child ${buttons})) { + ${logicalCSS('padding-left', euiTheme.size.s)} + } + + &:not(:has(> ${buttons}:last-child, > *:last-child ${buttons})) { + ${logicalCSS('padding-right', euiTheme.size.s)} + } + `, + append: css` + border-radius: 0; + border-start-end-radius: ${euiTheme.border.radius.small}; + border-end-end-radius: ${euiTheme.border.radius.small}; + `, + prepend: css` + border-radius: 0; + border-start-start-radius: ${euiTheme.border.radius.small}; + border-end-start-radius: ${euiTheme.border.radius.small}; + `, + isInteractive: css` + color: ${euiTheme.colors.textPrimary}; + + &:hover { + background-color: ${euiTheme.colors.backgroundBaseInteractiveHover}; + } + + &:focus-visible { + outline: none; + + /* apply a focus style that matches input focus more closely */ + &::after { + content: ''; + position: absolute; + inset: 0; + border: ${euiTheme.border.width.thick} solid + ${euiTheme.components.forms.borderFocused}; + /* ensure it stays on top of hovered borders */ + z-index: 2; + pointer-events: none; + border-radius: inherit; + } + } + + .euiFormLabel { + color: currentColor; + cursor: pointer; + } + + * { + cursor: pointer; + } + `, + disabled: css` + color: ${form.textColorDisabled}; + + .euiFormLabel { + color: ${form.textColorDisabled}; + } + `, + }; +}; diff --git a/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.test.tsx b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.test.tsx new file mode 100644 index 00000000000..448a3a692b7 --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.test.tsx @@ -0,0 +1,277 @@ +/* + * 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 React from 'react'; +import { fireEvent } from '@testing-library/react'; + +import { shouldRenderCustomStyles } from '../../../../test/internal'; +import { + EuiFormAppend, + EuiFormAppendPrepend, + EuiFormAppendPrependProps, + EuiFormPrepend, +} from './form_append_prepend'; +import { requiredProps } from '../../../../test'; +import { render } from '../../../../test/rtl'; +import { EuiFieldText } from '../../field_text'; + +const sharedProps = { + label: 'Label', + 'data-test-subj': 'euiFormAppendPrepend', +}; + +const defaultProps = { + ...sharedProps, + side: 'append' as EuiFormAppendPrependProps['side'], +}; + +describe('EuiFormAppend', () => { + shouldRenderCustomStyles(); + + it('is rendered', () => { + const { container, getByTestSubject } = render( + + ); + + const classes = Object.values( + getByTestSubject(requiredProps['data-test-subj']).classList + ); + + expect(container.firstChild).toMatchSnapshot(); + expect(classes).toContain('euiFormAppend'); + }); +}); + +describe('EuiFormPrepend', () => { + shouldRenderCustomStyles(); + + it('is rendered', () => { + const { container, getByTestSubject } = render( + + ); + + const classes = Object.values( + getByTestSubject(requiredProps['data-test-subj']).classList + ); + + expect(container.firstChild).toMatchSnapshot(); + expect(classes).toContain('euiFormPrepend'); + }); +}); + +describe('EuiFormAppendPrepend', () => { + shouldRenderCustomStyles(); + + it('is rendered', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + describe('props', () => { + describe('element', () => { + it('renders a div', () => { + const { getByTestSubject } = render( + + ); + + const element = getByTestSubject(defaultProps['data-test-subj']); + + expect(element).toBeInTheDocument(); + expect(element.tagName).toBe('DIV'); + }); + + it('renders a button', () => { + const { getByTestSubject } = render( + + ); + + const element = getByTestSubject(defaultProps['data-test-subj']); + + expect(element).toBeInTheDocument(); + expect(element.tagName).toBe('BUTTON'); + }); + }); + + describe('label', () => { + it('renders a label element when an `id` is provided', () => { + const { getByText } = render( + + ); + + const element = getByText(defaultProps.label); + + expect(element).toBeInTheDocument(); + expect(element.tagName).toBe('LABEL'); + }); + + it('renders a span element when no `id` is provided', () => { + const { getByText } = render( + + ); + + const element = getByText(defaultProps.label); + + expect(element).toBeInTheDocument(); + expect(element.tagName).toBe('SPAN'); + }); + + it('renders a span element for buttons', () => { + const { getByText } = render( + + ); + + const element = getByText(defaultProps.label); + + expect(element).toBeInTheDocument(); + expect(element.tagName).toBe('SPAN'); + }); + }); + + describe('iconLeft', () => { + it('renders an icon on the left side', () => { + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']).firstChild + ).toHaveAttribute('data-euiicon-type', 'faceHappy'); + }); + }); + + describe('iconRight', () => { + it('renders an icon on the left side', () => { + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']).lastChild + ).toHaveAttribute('data-euiicon-type', 'faceHappy'); + }); + }); + + describe('children', () => { + it('renders', () => { + const { side, 'data-test-subj': dataTestSubj } = defaultProps; + + const { getByTestSubject } = render( + + Content + + ); + + const element = getByTestSubject(dataTestSubj); + + expect(element.children.length).toBe(1); + expect(element.firstChild).toHaveTextContent('Content'); + }); + + it('renders `children` as last child', () => { + const { getByTestSubject } = render( + + Content + + ); + + const element = getByTestSubject(defaultProps['data-test-subj']); + + expect(element.firstChild).toHaveTextContent(defaultProps.label); + expect(element.lastChild).toHaveTextContent('Content'); + }); + }); + + describe('inputId', () => { + it('renders `for` attribute when `inputId` is passed', () => { + const { getByText } = render( + + ); + + expect(getByText(defaultProps.label)).toHaveAttribute('for', 'testId'); + }); + + it('does not render `for` attribute for buttons when `inputId` is passed', () => { + const { getByText } = render( + + ); + + expect(getByText(defaultProps.label)).not.toHaveAttribute('for'); + }); + + it('renders `for` attribute when `id` is set on the parent form element', () => { + const { getByText } = render( + } + /> + ); + + expect(getByText(defaultProps.label)).toHaveAttribute('for', 'testId'); + }); + }); + + describe('compressed', () => { + it('renders compressed styles', () => { + const { getByTestSubject } = render( + + ); + + const classes = Object.values( + getByTestSubject(defaultProps['data-test-subj']).classList + ); + + expect(classes.some((clx) => clx.includes('compressed'))).toBe(true); + }); + + it('renders compressed styles when the parent form element is compressed', () => { + const { getByTestSubject } = render( + } + /> + ); + + const classes = Object.values( + getByTestSubject(defaultProps['data-test-subj']).classList + ); + + expect(classes.some((clx) => clx.includes('compressed'))).toBe(true); + }); + }); + + describe('onClick', () => { + it('renders a button with click handler', () => { + const onClick = jest.fn(); + + const { getByTestSubject } = render( + + ); + + const element = getByTestSubject(defaultProps['data-test-subj']); + + expect(element).toBeInTheDocument(); + expect(element.tagName).toBe('BUTTON'); + + fireEvent.click(element); + + expect(onClick).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.tsx b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.tsx new file mode 100644 index 00000000000..dec9b1198ee --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.tsx @@ -0,0 +1,183 @@ +/* + * 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 React, { + ButtonHTMLAttributes, + FunctionComponent, + HTMLAttributes, + ReactNode, + useContext, +} from 'react'; +import classNames from 'classnames'; + +import { useEuiMemoizedStyles } from '../../../../services'; +import { CommonProps, ExclusiveUnion } from '../../../common'; +import { EuiIcon, IconType } from '../../../icon'; +import { EuiFormLabel } from '../../form_label'; +import { + _EuiFormLabelProps, + _EuiFormLabelSpanProps, +} from '../../form_label/form_label'; +import { euiFormAppendPrependStyles } from './form_append_prepend.styles'; +import { EuiFormControlLayoutContext } from '../form_control_layout_context'; + +export type EuiFormAppendProps = EuiFormAppendPrependBaseProps; + +export const EuiFormAppend = ({ className, ...rest }: EuiFormAppendProps) => { + const classes = classNames('euiFormAppend', className); + + return ; +}; + +export type EuiFormPrependProps = EuiFormAppendPrependBaseProps; + +export const EuiFormPrepend = ({ className, ...rest }: EuiFormPrependProps) => { + const classes = classNames('euiFormPrepend', className); + + return ; +}; + +export type EuiFormAppendPrependCommonProps = CommonProps & { + /** + * Main content label + */ + label?: ReactNode; + /** + * Left side icon + */ + iconLeft?: IconType; + /** + * Right side icon + */ + iconRight?: IconType; + /** + * Optional content that will be appended to `label` and icons + */ + children?: ReactNode; + /** + * id of the input element that the form label is linked to via `htmlFor` attribute + */ + inputId?: string; + /** + * Renders the element with smaller height and padding + */ + compressed?: boolean; +}; + +export type EuiFormAppendPrependButtonProps = + EuiFormAppendPrependCommonProps & { + /** + * Defines the rendered HTML element + */ + element?: 'button'; + isDisabled?: boolean; + } & ButtonHTMLAttributes; + +export type EuiFormAppendPrependDivProps = EuiFormAppendPrependCommonProps & { + /** + * Defines the rendered HTML element + */ + element?: 'div'; +} & HTMLAttributes; + +export type EuiFormAppendPrependBaseProps = ExclusiveUnion< + EuiFormAppendPrependButtonProps, + EuiFormAppendPrependDivProps +>; + +export type EuiFormAppendPrependProps = { + side: 'append' | 'prepend'; +} & EuiFormAppendPrependBaseProps; + +/* Internal component */ + +export const EuiFormAppendPrepend: FunctionComponent< + EuiFormAppendPrependProps +> = ({ + element = 'div', + id, + side, + children, + className, + inputId: _inputId, + compressed: _compressed, + iconLeft: _iconLeft, + iconRight: _iconRight, + label: _label, + isDisabled: _isDisabled, + disabled, + ...rest +}) => { + const styles = useEuiMemoizedStyles(euiFormAppendPrependStyles); + + const { compressed: formLayoutCompressed, inputId: formLayoutInputId } = + useContext(EuiFormControlLayoutContext); + const compressed = _compressed ?? formLayoutCompressed; + const inputId = _inputId ?? formLayoutInputId; + + // Adding automatic check on onClick for DevX convinience, this doesn't replace defining `element` + const isButton = element === 'button' || typeof rest.onClick === 'function'; + const isDisabled = _isDisabled || disabled; + + const iconLeft = _iconLeft && ; + const iconRight = _iconRight && ; + + const cssStyles = [ + styles.side, + compressed ? styles.compressed : styles.uncompressed, + isButton && !isDisabled && styles.isInteractive, + isDisabled && styles.disabled, + isButton && styles[side], + ]; + + const labelProps = isButton + ? ({ + type: 'span', + id, + className: 'eui-textTruncate', + } as _EuiFormLabelSpanProps) + : ({ + type: 'label', + id, + htmlFor: inputId || undefined, + } as _EuiFormLabelProps); + + const label = _label && {_label}; + + const content = ( + <> + {iconLeft} + {label} + {iconRight} + {children} + + ); + + if (isButton) { + return ( + + ); + } + + return ( +
)} + > + {content} +
+ ); +}; diff --git a/packages/eui/src/components/form/form_control_layout/append_prepend/form_prepend.stories.tsx b/packages/eui/src/components/form/form_control_layout/append_prepend/form_prepend.stories.tsx new file mode 100644 index 00000000000..b0df65c5860 --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_prepend.stories.tsx @@ -0,0 +1,78 @@ +/* + * 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 React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { + hideStorybookControls, + enableFunctionToggleControls, +} from '../../../../../.storybook/utils'; + +import { EuiFieldText } from '../../field_text'; +import { EuiNotificationBadge } from '../../../badge'; +import { + EuiFormPrepend, + type EuiFormPrependProps, +} from './form_append_prepend'; + +const meta: Meta = { + title: 'Forms/EuiForm/EuiFormControlLayout/Subcomponents/EuiFormPrepend', + component: EuiFormPrepend, + argTypes: { + label: { control: 'text' }, + iconLeft: { control: 'text' }, + iconRight: { control: 'text' }, + children: { + control: 'radio', + options: [undefined, 'badge', 'text'], + mapping: { + badge: 1, + text: 'Content', + undefined: undefined, + }, + }, + }, + args: { + inputId: '', + element: 'div', + compressed: false, + label: '', + iconLeft: '', + iconRight: '', + children: undefined, + // @ts-expect-error - ignore exclusive union + isDisabled: false, + }, +}; +hideStorybookControls(meta, ['aria-label']); +enableFunctionToggleControls(meta, ['onClick']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + label: 'Prepend', + // @ts-expect-error - onClick is optional but the toggle is enabled + onClick: false, + }, + render: ({ compressed, inputId, ...args }: EuiFormPrependProps) => { + const textFieldProps = { + compressed, + id: inputId, + }; + + return ( + } + /> + ); + }, +}; diff --git a/packages/eui/src/components/form/form_control_layout/append_prepend/index.ts b/packages/eui/src/components/form/form_control_layout/append_prepend/index.ts new file mode 100644 index 00000000000..0d43a856e6a --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ + +export { + EuiFormAppend, + EuiFormPrepend, + type EuiFormAppendProps, + type EuiFormPrependProps, + type EuiFormAppendPrependButtonProps, + type EuiFormAppendPrependDivProps, +} from './form_append_prepend'; diff --git a/packages/eui/src/components/form/form_control_layout/form_control_layout.stories.tsx b/packages/eui/src/components/form/form_control_layout/form_control_layout.stories.tsx index 71afc2763d1..f106a7511c5 100644 --- a/packages/eui/src/components/form/form_control_layout/form_control_layout.stories.tsx +++ b/packages/eui/src/components/form/form_control_layout/form_control_layout.stories.tsx @@ -7,6 +7,7 @@ */ import React, { ChangeEvent, useState } from 'react'; +import { css } from '@emotion/react'; import type { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; @@ -22,6 +23,15 @@ import { EuiText } from '../../text'; import { EuiFormRow } from '../form_row'; import { EuiSelectable, EuiSelectableOption } from '../../selectable'; import { EuiNotificationBadge } from '../../badge'; +import { EuiCopy } from '../../copy'; +import { EuiFlexGroup } from '../../flex'; +import { + EuiDragDropContext, + EuiDraggable, + EuiDroppable, +} from '../../drag_and_drop'; +import { EuiFormLabel } from '../form_label'; +import { EuiFilterButton } from '../../filter_group'; import { EuiFieldSearch } from '../field_search'; import { @@ -32,6 +42,8 @@ import { EuiFormControlLayout, EuiFormControlLayoutProps, } from './form_control_layout'; +import { EuiFormAppend, EuiFormPrepend } from './append_prepend'; +import { UseEuiTheme } from '@elastic/eui-theme-common'; const meta: Meta = { title: 'Forms/EuiForm/EuiFormControlLayout', @@ -81,7 +93,7 @@ export const Playground: Story = { const { readOnly, isDisabled, fullWidth, compressed } = args; const childProps = { readOnly, - isDisabled, + disabled: isDisabled, fullWidth, compressed, isInvalid: args.isInvalid, @@ -167,8 +179,11 @@ export const FormControlButton: Story = { }, }; -export const AppendPrepend: Story = { - tags: ['vrt-only'], +/* TODO: remove testing story */ +export const AppendPrepend_REVIEW_EXAMPLE: Story = { + parameters: { + loki: { skip: true }, + }, render: function Render() { const isDesktop = useIsWithinMinBreakpoint('xl'); return ( @@ -329,6 +344,785 @@ export const AppendPrepend: Story = { }, }; +export const AppendPrepend: Story = { + tags: ['vrt-only'], + render: function Render(args) { + const isDesktop = useIsWithinMinBreakpoint('xl'); + + const renderContent = (compressed: boolean = false) => { + const idBase = `input-${compressed ? 'compressed' : ''}`; + + return ( + + } + autoFocus + /> + + } + append={ + + } + /> + + + } + append={ + + } + /> + + + 1 +
+ } + append={ + + 1 + + } + /> + + + + + } + append={ + + + + } + /> + + + } + append={ + + } + /> + } + /> + + } + append={ + + } + /> + + + } + /> + + } + append="String" + /> + + ); + }; + + return ( + + {renderContent(false)} + {renderContent(true)} + + ); + }, +}; + +/* TODO: remove testing story */ +export const Kitchensink: Story = { + parameters: { + codeSnippet: { + skip: true, + }, + loki: { skip: true }, + }, + render: function Render(args) { + const { readOnly, isDisabled, fullWidth, compressed } = args; + const isDesktop = useIsWithinMinBreakpoint('xl'); + + const [isPopoverOpenA, setPopoverOpenA] = useState(false); + const [isPopoverOpenB, setPopoverOpenB] = useState(false); + + const formStyles = ({ euiTheme }: UseEuiTheme) => css` + display: flex; + flex-direction: column; + gap: ${euiTheme.size.m}; + `; + + const childProps = { + readOnly, + disabled: isDisabled, + fullWidth, + compressed, + isInvalid: args.isInvalid, + }; + + return ( + + + +

Styled wrapper API

+
+ + + + + 1 +
+ } + /> + + } + append={ + + + + } + /> + + + 1 +
+ } + /> + + + {(copy) => ( + + )} + + } + /> + + setPopoverOpenA(false)} + button={ + setPopoverOpenA(!isPopoverOpenA)} + /> + } + > + Popover content + + } + append={ + + + + } + /> + + + + 1 + + + } + closePopover={() => setPopoverOpenA(false)} + > + Popover content + + } + /> + + + } + /> + + + } + append={ + + } + /> + + {/* Drag examples */} + + +

Drag examples

+
+ + {}}> + + + {(provided) => ( + + } + /> + )} + + + + + +

With custom styles (reduce spacing and hover styles)

+
+ + {}}> + + + {(provided) => ( +
css` + .euiFormControlLayout__prepend { + &:is(:hover, :active) { + &::before { + content: ''; + position: absolute; + inset: 0; + background-color: ${euiTheme.colors + .backgroundBaseInteractiveHover}; + } + + .euiFormAppendPrepend__dragHandle { + color: ${euiTheme.colors.textParagraph}; + } + } + } + `} + > + + + + + + + + } + /> +
+ )} +
+
+
+ + + {/* split here */} + + + +

Custom content API

+
+ + + + , + 'Prepended', + , + 1, + ]} + /> + + } + append={ + + Tooltip + + } + /> + + + + Appended + + 1 + + + } + /> + + + {(copy) => ( + + Copy + + )} + + } + /> + + setPopoverOpenB(!isPopoverOpenB)} + > + + + } + closePopover={() => setPopoverOpenB(false)} + > + Popover content + + } + append={ + + Tooltip + + } + /> + + setPopoverOpenB(!isPopoverOpenB)} + > + + + } + closePopover={() => setPopoverOpenB(false)} + > + Popover content + + } + /> + + } + /> + + + + , + ]} + append={[ + + } + closePopover={() => {}} + > + Popover content + , + 'String', + ]} + /> + + {/* Drag examples */} + + +

Drag examples

+
+ + {}}> + + + {(provided) => ( + + + + String + + + } + /> + )} + + + + + +

With custom styles (reduce spacing and hover styles)

+
+ + {}}> + + + {(provided) => ( +
css` + .euiFormControlLayout__prepend { + &:is(:hover, :active) { + &::before { + content: ''; + position: absolute; + inset: 0; + background-color: ${euiTheme.colors + .backgroundBaseInteractiveHover}; + } + + .euiFormAppendPrepend__dragHandle { + color: ${euiTheme.colors.textParagraph}; + } + } + } + `} + > + + + + + String + + + + } + /> +
+ )} +
+
+
+
+ + ); + }, +}; + export const HighContrast: Story = { ...AppendPrepend, tags: ['vrt-only'], diff --git a/packages/eui/src/components/form/form_control_layout/form_control_layout.styles.ts b/packages/eui/src/components/form/form_control_layout/form_control_layout.styles.ts index 91ac288ce62..3d41ca9093e 100644 --- a/packages/eui/src/components/form/form_control_layout/form_control_layout.styles.ts +++ b/packages/eui/src/components/form/form_control_layout/form_control_layout.styles.ts @@ -19,6 +19,9 @@ import { type EuiButtonDisplaySizes } from '../../button/button_display/_button_ import { euiFormVariables } from '../form.styles'; +export const buttonSelectors = + '*:is(.euiButton, .euiButtonEmpty, .euiButtonIcon, .euiFormAppend, .euiFormPrepend)'; + export const euiFormControlLayoutStyles = (euiThemeContext: UseEuiTheme) => { const { euiTheme } = euiThemeContext; const form = euiFormVariables(euiThemeContext); @@ -154,7 +157,8 @@ export const euiFormControlLayoutSideNodeStyles = ( const uncompressedHeight = form.controlHeight; const compressedHeight = form.controlCompressedHeight; - const buttons = '*:is(.euiButton, .euiButtonEmpty, .euiButtonIcon)'; + const appendPrepend = '*:is(.euiFormAppend, .euiFormPrepend)'; + const buttons = buttonSelectors; const text = '*:is(.euiFormLabel, .euiText)'; const _buttonPadding = (size: EuiButtonDisplaySizes) => @@ -184,7 +188,13 @@ export const euiFormControlLayoutSideNodeStyles = ( /* Overrides */ + :has(${appendPrepend}) > *, + ${appendPrepend} > ${buttons} { + block-size: 100%; + } + ${buttons} { + block-size: 100%; /* Override button hover/active transform */ transform: none !important; /* stylelint-disable-line declaration-no-important */ @@ -200,16 +210,6 @@ export const euiFormControlLayoutSideNodeStyles = ( overflow: hidden; text-overflow: ellipsis; } - - /* Account for button padding when spacing children */ - /* Second > * selector accounts for buttons inside popover & tooltip wrappers */ - &:not(:has(> ${buttons}:first-child, > *:first-child > ${buttons})) { - ${logicalCSS('padding-left', euiTheme.size.s)} - } - - &:not(:has(> ${buttons}:last-child, > *:last-child > ${buttons})) { - ${logicalCSS('padding-right', euiTheme.size.s)} - } `, append: css( highContrastModeStyles(euiThemeContext, { @@ -249,16 +249,16 @@ export const euiFormControlLayoutSideNodeStyles = ( }) ), uncompressed: ` - &:not(:has(> ${buttons}:first-child, > *:first-child > ${buttons})) { + &:not(:has(> ${buttons}:first-child, > *:first-child ${buttons})) { ${logicalCSS('padding-left', euiTheme.size.m)} } - &:not(:has(> ${buttons}:last-child, > *:last-child > ${buttons})) { + &:not(:has(> ${buttons}:last-child, > *:last-child ${buttons})) { ${logicalCSS('padding-right', euiTheme.size.m)} } ${text} { - ${logicalCSS('padding-horizontal', euiTheme.size.xs)} + ${logicalCSS('padding-horizontal', euiTheme.size.xs)} line-height: ${uncompressedHeight}; } @@ -275,6 +275,19 @@ export const euiFormControlLayoutSideNodeStyles = ( } `, compressed: css` + /* Legacy padding styles to handle content without */ + &:not(:has(${appendPrepend})):not( + :has(> ${buttons}:first-child, > *:first-child ${buttons}) + ) { + ${logicalCSS('padding-left', euiTheme.size.s)} + } + + &:not(:has(${appendPrepend})):not( + :has(> ${buttons}:last-child, > *:last-child ${buttons}) + ) { + ${logicalCSS('padding-right', euiTheme.size.s)} + } + ${text} { ${logicalCSS('padding-horizontal', euiTheme.size.xxs)} line-height: ${compressedHeight}; 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 1c7438f648e..c175995cc9e 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 @@ -29,6 +29,8 @@ import { euiFormControlLayoutStyles, euiFormControlLayoutSideNodeStyles, } from './form_control_layout.styles'; +import { EuiFormAppend, EuiFormPrepend } from './append_prepend'; +import { EuiFormControlLayoutContextProvider } from './form_control_layout_context'; type StringOrReactElement = string | ReactElement; type PrependAppendType = StringOrReactElement | StringOrReactElement[]; @@ -162,53 +164,57 @@ export const EuiFormControlLayout: FunctionComponent< return (
- -
- {hasLeftIcon && ( - - )} - - {children} - - {hasRightIcons && ( - - )} -
- + +
+ {hasLeftIcon && ( + + )} + + {children} + + {hasRightIcons && ( + + )} +
+ +
); }; @@ -221,7 +227,8 @@ const EuiFormControlLayoutSideNodes: FunctionComponent<{ nodes?: PrependAppendType; // For some bizarre reason if you make this the `children` prop instead, React doesn't properly override cloned keys :| inputId?: string; compressed?: boolean; -}> = ({ side, nodes, inputId, compressed }) => { +}> = (props) => { + const { side, nodes, inputId, compressed } = props; const className = `euiFormControlLayout__${side}`; const styles = useEuiMemoizedStyles(euiFormControlLayoutSideNodeStyles); const cssStyles = [ @@ -232,15 +239,33 @@ const EuiFormControlLayoutSideNodes: FunctionComponent<{ if (!nodes) return null; + let content; + + const AppendOrPrepend = side === 'append' ? EuiFormAppend : EuiFormPrepend; + + if (Array.isArray(nodes)) { + content = React.Children.map(nodes, (node) => + typeof node === 'string' ? ( + {node} + ) : ( + node + ) + ); + } else if (typeof nodes === 'string') { + content = ( + + ); + } else { + content = nodes; + } + return (
- {React.Children.map(nodes, (node) => - typeof node === 'string' ? ( - {node} - ) : ( - node - ) - )} + {content}
); }; diff --git a/packages/eui/src/components/form/form_control_layout/form_control_layout_context.tsx b/packages/eui/src/components/form/form_control_layout/form_control_layout_context.tsx new file mode 100644 index 00000000000..25d831e47d6 --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/form_control_layout_context.tsx @@ -0,0 +1,27 @@ +/* + * 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 { createContext } from 'react'; +import { EuiFormControlLayoutProps } from './form_control_layout'; + +type FormControlLayoutContext = Pick< + EuiFormControlLayoutProps, + 'compressed' | 'inputId' +>; + +/** + * Context to share props between `EuiFormControlLayout` and passed children like e.g. EuiFormAppend/Prepend + */ +export const EuiFormControlLayoutContext = + createContext({ + compressed: false, + inputId: '', + }); + +export const EuiFormControlLayoutContextProvider = + EuiFormControlLayoutContext.Provider; diff --git a/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx b/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx index c7d392cefe5..244c0d41708 100644 --- a/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx +++ b/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx @@ -23,6 +23,7 @@ import { EuiFormControlLayoutDelimitedProps, } from './form_control_layout_delimited'; import { EuiFormControlLayoutProps } from './form_control_layout'; +import { EuiFormAppend, EuiFormPrepend } from './append_prepend'; // re-declaring the component with props as the Partial // of EuiFormControlLayoutDelimitedProps is otherwise not resolved @@ -46,7 +47,7 @@ const meta: Meta = { control: 'radio', options: [undefined, 'icon', 'text'], mapping: { - icon: , + icon: , text: 'Appended', undefined: undefined, }, @@ -55,7 +56,7 @@ const meta: Meta = { control: 'radio', options: [undefined, 'icon', 'text'], mapping: { - icon: , + icon: , text: 'Prepended', undefined: undefined, }, @@ -106,6 +107,7 @@ export const Playground: Story = { controlOnly placeholder="0" aria-label="EuiFormControlLayoutDelimited demo - start control" + id="foo" /> ), endControl: ( diff --git a/packages/eui/src/components/form/form_control_layout/index.ts b/packages/eui/src/components/form/form_control_layout/index.ts index 243fa6fb296..d25bb63fcbf 100644 --- a/packages/eui/src/components/form/form_control_layout/index.ts +++ b/packages/eui/src/components/form/form_control_layout/index.ts @@ -14,3 +14,4 @@ export { EuiFormControlLayoutIcons, type EuiFormControlLayoutIconsProps, } from './form_control_layout_icons'; +export * from './append_prepend'; diff --git a/packages/eui/src/components/form/form_label/__snapshots__/form_label.test.tsx.snap b/packages/eui/src/components/form/form_label/__snapshots__/form_label.test.tsx.snap index de31e2b67d4..e2080d2d523 100644 --- a/packages/eui/src/components/form/form_label/__snapshots__/form_label.test.tsx.snap +++ b/packages/eui/src/components/form/form_label/__snapshots__/form_label.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiFormLabel is rendered 1`] = ` -