diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_High_Contrast.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_High_Contrast.png new file mode 100644 index 00000000000..4270f85a7c9 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_High_Contrast.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_High_Contrast_Dark_Mode.png new file mode 100644 index 00000000000..47d01577acf Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_High_Contrast_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Kitchensink.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Kitchensink.png new file mode 100644 index 00000000000..019f97246c7 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Kitchensink.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Kitchensink_Dark_Mode.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Kitchensink_Dark_Mode.png new file mode 100644 index 00000000000..6980c52beb9 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Kitchensink_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_Playground.png new file mode 100644 index 00000000000..bdf7eb376d7 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlButton_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 63e56ceb491..3cae57f1078 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 4a046cf405a..04936313133 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_EuiFormControlLayoutDelimited_Kitchen_Sink.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_Kitchen_Sink.png index 1515b4966ba..093d71f3506 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_Kitchen_Sink.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayoutDelimited_Kitchen_Sink.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 2e375ab34f3..472ada7a0d3 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_Form_Control_Button.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Form_Control_Button.png new file mode 100644 index 00000000000..5055dbdf6b0 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiForm_EuiFormControlLayout_Form_Control_Button.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 f1af88cbd30..b4a65da2315 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 255d9cf5ef8..bb6c399d2d8 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_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png index d208b4a7cdd..ca48b5b11c6 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_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png index 1254e429e3e..afe39534d99 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_Restricted_Range.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png index 1254e429e3e..afe39534d99 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_mobile_Forms_EuiForm_EuiFormControlButton_High_Contrast.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_High_Contrast.png new file mode 100644 index 00000000000..19f020a66f1 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_High_Contrast.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_High_Contrast_Dark_Mode.png new file mode 100644 index 00000000000..fb949297304 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_High_Contrast_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Kitchensink.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Kitchensink.png new file mode 100644 index 00000000000..7053491d123 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Kitchensink.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Kitchensink_Dark_Mode.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Kitchensink_Dark_Mode.png new file mode 100644 index 00000000000..7ecc5f1e483 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Kitchensink_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Playground.png new file mode 100644 index 00000000000..462c23eb573 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlButton_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png index 060e239d7e9..a25fb6c9ec3 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png index 0bde46b4509..6dd39da1038 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_High_Contrast_Dark_Mode.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_Kitchen_Sink.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_Kitchen_Sink.png index 5124052a047..e0b145b1969 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_Kitchen_Sink.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayoutDelimited_Kitchen_Sink.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 66e45ee1601..446c0033d3e 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_Form_Control_Button.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Form_Control_Button.png new file mode 100644 index 00000000000..1891458e7d7 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiForm_EuiFormControlLayout_Form_Control_Button.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 8565af97f95..e380ff4096a 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 5516e41b801..b5bc6ebe12b 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_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Custom_Quick_Select_Panel.png index b498a092454..f62b096b43a 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_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Playground.png index 467c160d0f1..410f6780bf7 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_Restricted_Range.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Restricted_Range.png index 467c160d0f1..410f6780bf7 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/changelogs/upcoming/9006.md b/packages/eui/changelogs/upcoming/9006.md new file mode 100644 index 00000000000..2493020694d --- /dev/null +++ b/packages/eui/changelogs/upcoming/9006.md @@ -0,0 +1,3 @@ +- Added `EuiFormControlButton` component for usage as input-styled button within `EuiFormControlLayout` +- Updated `EuiSuperDatePicker` to use `EuiFormControlButton` when a prettified duration button is rendered + diff --git a/packages/eui/src/components/button/button_empty/button_empty.tsx b/packages/eui/src/components/button/button_empty/button_empty.tsx index 251ce26bf02..6e5a71d5dad 100644 --- a/packages/eui/src/components/button/button_empty/button_empty.tsx +++ b/packages/eui/src/components/button/button_empty/button_empty.tsx @@ -84,7 +84,8 @@ export interface CommonEuiButtonEmptyProps contentProps?: CommonProps & EuiButtonDisplayContentType; } -type EuiButtonEmptyPropsForAnchor = PropsForAnchor; +export type EuiButtonEmptyPropsForAnchor = + PropsForAnchor; export type EuiButtonEmptyPropsForButton = PropsForButton; 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 f76ca298a77..7b4500c996d 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 @@ -43,11 +43,15 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > @@ -116,11 +120,15 @@ exports[`EuiSuperDatePicker props compressed is rendered 1`] = ` class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > @@ -256,15 +264,19 @@ exports[`EuiSuperDatePicker props isAutoRefreshOnly passes required props 1`] = exports[`EuiSuperDatePicker props isDisabled config object 1`] = ` `; @@ -312,12 +324,16 @@ exports[`EuiSuperDatePicker props isDisabled true 1`] = ` class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > @@ -452,11 +468,15 @@ exports[`EuiSuperDatePicker props showUpdateButton can be false 1`] = ` class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > @@ -505,11 +525,15 @@ exports[`EuiSuperDatePicker props showUpdateButton can be iconOnly 1`] = ` class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > @@ -581,11 +605,15 @@ exports[`EuiSuperDatePicker props width can be auto 1`] = ` class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > @@ -654,11 +682,15 @@ exports[`EuiSuperDatePicker props width can be full 1`] = ` class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > @@ -728,11 +760,15 @@ exports[`EuiSuperDatePicker renders 1`] = ` class="euiFormControlLayout__childrenWrapper emotion-euiFormControlLayout__childrenWrapper-inGroup-prependOnly" > diff --git a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx index cb3652cbcba..46d20c15cf8 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -21,7 +21,11 @@ import { isObject } from '../../../services/predicate'; import { EuiI18nConsumer } from '../../context'; import { CommonProps } from '../../common'; import { EuiDatePickerRange } from '../date_picker_range'; -import { EuiFormControlLayout, EuiFormControlLayoutProps } from '../../form'; +import { + EuiFormControlButton, + EuiFormControlLayout, + EuiFormControlLayoutProps, +} from '../../form'; import { ShortDate, @@ -604,7 +608,7 @@ export class EuiSuperDatePickerInternal extends Component< return ( {!isQuickSelectOnly && ( - + )} ); diff --git a/packages/eui/src/components/form/form.styles.test.tsx b/packages/eui/src/components/form/form.styles.test.tsx index 491f1e49e39..68b2bd70d67 100644 --- a/packages/eui/src/components/form/form.styles.test.tsx +++ b/packages/eui/src/components/form/form.styles.test.tsx @@ -123,21 +123,21 @@ describe('euiFormControlStyles', () => { &::-webkit-input-placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } &::-moz-placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } &:-moz-placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } &::placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } ", "focus": " @@ -210,21 +210,21 @@ describe('euiFormControlStyles', () => { &::-webkit-input-placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } &::-moz-placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } &:-moz-placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } &::placeholder { - color: #798EAF; - opacity: 1; - } + color: #798EAF; + opacity: 1; + } diff --git a/packages/eui/src/components/form/form.styles.ts b/packages/eui/src/components/form/form.styles.ts index aaaa13f71ff..dcb04f0149a 100644 --- a/packages/eui/src/components/form/form.styles.ts +++ b/packages/eui/src/components/form/form.styles.ts @@ -25,6 +25,19 @@ import { highContrastModeStyles } from '../../global_styling/functions/high_cont export const euiFormMaxWidth = ({ euiTheme }: UseEuiTheme) => euiTheme.components.forms.maxWidth; +export const euiFormPlaceholderStyles = ( + euiThemeContext: UseEuiTheme, + color?: string +) => { + const form = euiFormVariables(euiThemeContext); + const _color = color ?? form.textColorDisabled; + + return ` + color: ${_color}; + opacity: 1; + `; +}; + export const euiFormVariables = (euiThemeContext: UseEuiTheme) => { const { euiTheme, highContrastMode } = euiThemeContext; const isRefreshVariant = isEuiThemeRefreshVariant( @@ -195,10 +208,9 @@ export const euiFormControlText = (euiThemeContext: UseEuiTheme) => { font-size: ${fontSize}; color: ${form.textColor}; - ${euiPlaceholderPerBrowser(` - color: ${form.controlPlaceholderText}; - opacity: 1; - `)} + ${euiPlaceholderPerBrowser( + euiFormPlaceholderStyles(euiThemeContext, form.controlPlaceholderText) + )} `; }; @@ -422,10 +434,7 @@ export const euiFormControlDisabledStyles = (euiThemeContext: UseEuiTheme) => { ${isRefreshVariant && refreshVariantStyles} - ${euiPlaceholderPerBrowser(` - color: ${form.textColorDisabled}; - opacity: 1; - `)} + ${euiPlaceholderPerBrowser(euiFormPlaceholderStyles(euiThemeContext))} `; }; diff --git a/packages/eui/src/components/form/form_control_button/__snapshots__/form_control_button.test.tsx.snap b/packages/eui/src/components/form/form_control_button/__snapshots__/form_control_button.test.tsx.snap new file mode 100644 index 00000000000..96c28fc2e11 --- /dev/null +++ b/packages/eui/src/components/form/form_control_button/__snapshots__/form_control_button.test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiButtonEmpty is rendered 1`] = ` + +`; diff --git a/packages/eui/src/components/form/form_control_button/form_control_button.stories.tsx b/packages/eui/src/components/form/form_control_button/form_control_button.stories.tsx new file mode 100644 index 00000000000..2e153c4412d --- /dev/null +++ b/packages/eui/src/components/form/form_control_button/form_control_button.stories.tsx @@ -0,0 +1,134 @@ +/* + * 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 { + enableFunctionToggleControls, + hideStorybookControls, +} from '../../../../.storybook/utils'; +import { EuiFlexGroup } from '../../flex'; +import { EuiSpacer } from '../../spacer'; +import { + EuiFormControlButton, + EuiFormControlButtonProps, +} from './form_control_button'; + +const meta: Meta = { + title: 'Forms/EuiForm/EuiFormControlButton', + component: EuiFormControlButton, + argTypes: { + value: { control: 'text' }, + iconType: { control: 'text' }, + target: { control: 'text' }, + }, + args: { + type: 'button', + iconSize: 'm', + iconSide: 'left', + iconType: '', + placeholder: '', + value: '', + isDisabled: false, + isInvalid: false, + compressed: false, + fullWidth: true, + }, +}; +hideStorybookControls(meta, ['aria-label', 'buttonRef']); +enableFunctionToggleControls(meta, ['onClick']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + value: 'Button value', + }, +}; + +export const Kitchensink: Story = { + tags: ['vrt-only'], + render: () => { + return ( + + + + Button content + + Button content + + + + + + + Button content + + + + + {/* compressed */} + + + Button content + + Button content + + + + + + + Button content + + + ); + }, +}; + +export const KitchensinkDarkMode: Story = { + tags: ['vrt-only'], + ...Kitchensink, + globals: { colorMode: 'DARK' }, +}; + +export const HighContrast: Story = { + ...Kitchensink, + tags: ['vrt-only'], + globals: { highContrastMode: true }, +}; + +export const HighContrastDarkMode: Story = { + ...Kitchensink, + tags: ['vrt-only'], + globals: { highContrastMode: true, colorMode: 'dark' }, +}; diff --git a/packages/eui/src/components/form/form_control_button/form_control_button.styles.ts b/packages/eui/src/components/form/form_control_button/form_control_button.styles.ts new file mode 100644 index 00000000000..1b738327275 --- /dev/null +++ b/packages/eui/src/components/form/form_control_button/form_control_button.styles.ts @@ -0,0 +1,64 @@ +/* + * 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 { euiFormControlStyles, euiFormPlaceholderStyles } from '../form.styles'; + +export const euiFormControlButtonStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + const formStyles = euiFormControlStyles(euiThemeContext); + + return { + euiFormControlButton: css` + ${formStyles.shared} + ${formStyles.uncompressed} + font-weight: ${euiTheme.font.weight.regular}; + transition: none; + + &:hover { + &::before { + display: none; + } + } + + &:focus { + ${formStyles.focus} + } + + &:disabled { + ${formStyles.disabled} + } + `, + isInvalid: css` + ${formStyles.invalid} + + &:disabled { + ${formStyles.invalid} + } + `, + compressed: css` + ${formStyles.compressed} + `, + formWidth: formStyles.formWidth, + fullWidth: css(formStyles.fullWidth), + euiFormControlButton__content: css` + justify-content: flex-start; + ${logicalCSS('width', '100%')} + `, + textContent: css` + flex: 1; + text-align: start; + `, + placeholder: css` + ${euiFormPlaceholderStyles(euiThemeContext)} + `, + }; +}; diff --git a/packages/eui/src/components/form/form_control_button/form_control_button.test.tsx b/packages/eui/src/components/form/form_control_button/form_control_button.test.tsx new file mode 100644 index 00000000000..a71447f539c --- /dev/null +++ b/packages/eui/src/components/form/form_control_button/form_control_button.test.tsx @@ -0,0 +1,223 @@ +/* + * 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 { render } from '../../../test/rtl'; +import { requiredProps } from '../../../test/required_props'; +import { shouldRenderCustomStyles } from '../../../test/internal'; + +import { EuiFormControlButton } from './form_control_button'; + +const defaultProps = { + value: 'Button value', + 'data-test-subj': 'euiFormControlButton', +}; + +describe('EuiButtonEmpty', () => { + shouldRenderCustomStyles(, { + childProps: ['contentProps', 'textProps'], + }); + + test('is rendered', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + describe('props', () => { + describe('placeholder', () => { + it('is rendered', () => { + const placeholder = 'Placeholder text'; + + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']) + ).toHaveTextContent(placeholder); + }); + + it('renders when only `children` is passed', () => { + const placeholder = 'Placeholder text'; + const children = 'Button content'; + + const { getByTestSubject } = render( + + {children} + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']) + ).toHaveTextContent(`${defaultProps.value} ${children}`); + }); + + it('does not render `placeholder` when `value` is passed', () => { + const placeholder = 'Placeholder text'; + + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']) + ).toHaveTextContent(defaultProps.value); + }); + }); + + describe('value', () => { + it('is rendered', () => { + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']) + ).toHaveTextContent(defaultProps.value); + }); + + it('renders `value` and ` children', () => { + const children = 'Button content'; + + const { getByTestSubject } = render( + + {children} + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']) + ).toHaveTextContent(`${defaultProps.value} ${children}`); + }); + }); + + describe('compressed', () => { + it('is rendered', () => { + const { getByTestSubject } = render( + + ); + + const classes = Object.values( + getByTestSubject('euiFormControlButton').classList + ); + + expect(classes.some((clx) => clx.includes('compressed'))).toBe(true); + }); + }); + + describe('isDisabled', () => { + it('is rendered', () => { + const { getByTestSubject } = render( + + ); + + expect(getByTestSubject('euiFormControlButton')).toBeDisabled(); + }); + }); + + describe('isInvalid', () => { + it('is rendered', () => { + const { getByTestSubject } = render( + + ); + + const classes = Object.values( + getByTestSubject('euiFormControlButton').classList + ); + + expect(classes.some((clx) => clx.includes('isInvalid'))).toBe(true); + }); + }); + + describe('iconType', () => { + it('is rendered', () => { + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']).firstChild + ?.firstChild + ).toHaveAttribute('data-euiicon-type', 'user'); + }); + }); + + describe('iconSide', () => { + it('is rendered', () => { + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']).firstChild?.lastChild + ).toHaveAttribute('data-euiicon-type', 'user'); + }); + }); + + describe('onClick', () => { + it('fires `onClick` on button click', () => { + const handler = jest.fn(); + const { container } = render( + + ); + fireEvent.click(container.querySelector('button')!); + expect(handler).toHaveBeenCalledTimes(1); + }); + }); + + describe('contentProps', () => { + it('applies and merges passed `contentProps` correctly', () => { + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(defaultProps['data-test-subj']).firstChild + ).toHaveClass('euiButtonEmpty__content'); + expect( + getByTestSubject(defaultProps['data-test-subj']).firstChild + ).toHaveClass('testClass1'); + expect( + getByTestSubject(defaultProps['data-test-subj']).firstChild + ).toHaveAttribute('data-test-subj', requiredProps['data-test-subj']); + }); + }); + + describe('textProps', () => { + it('applies and merges passed `textProps` correctly', () => { + const { getByTestSubject } = render( + + ); + + expect( + getByTestSubject(requiredProps['data-test-subj']) + ).toBeInTheDocument(); + expect( + getByTestSubject(requiredProps['data-test-subj']) + ).toHaveTextContent(defaultProps.value); + expect(getByTestSubject(requiredProps['data-test-subj'])).toHaveClass( + 'testClass1' + ); + }); + }); + }); +}); diff --git a/packages/eui/src/components/form/form_control_button/form_control_button.tsx b/packages/eui/src/components/form/form_control_button/form_control_button.tsx new file mode 100644 index 00000000000..853e13365de --- /dev/null +++ b/packages/eui/src/components/form/form_control_button/form_control_button.tsx @@ -0,0 +1,139 @@ +/* + * 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, { FunctionComponent, isValidElement, ReactNode } from 'react'; + +import classNames from 'classnames'; +import { useEuiMemoizedStyles } from '../../../services'; +import { EuiButtonEmpty, type EuiButtonEmptyProps } from '../../button'; +import { EuiFieldTextProps } from '../field_text'; +import { euiFormControlButtonStyles } from './form_control_button.styles'; +import { useInnerText } from '../../inner_text'; +import { + EuiButtonEmptyPropsForAnchor, + EuiButtonEmptyPropsForButton, +} from '../../button/button_empty/button_empty'; + +export type EuiFormControlButtonInputProps = { + /** + * Placeholder value to be shown when no `value` is passed + */ + placeholder?: EuiFieldTextProps['placeholder']; + /** + * When `true`, it renders a shorter height element + */ + compressed?: EuiFieldTextProps['compressed']; + /** + * Defines invalid state styling + */ + isInvalid?: EuiFieldTextProps['isInvalid']; + /** + * Expand to fill 100% of the parent. + * Defaults to `fullWidth` prop of ``. + * @default true + */ + fullWidth?: boolean; +}; +export type EuiFormControlButtonProps = EuiFormControlButtonInputProps & + Omit< + EuiButtonEmptyProps, + 'value' | 'color' | 'size' | 'flush' | 'isSelected' | 'isLoading' + > & { + /** + * Defines the button label when used like an input in combination with `placeholder` + */ + value?: ReactNode; + }; + +export const EuiFormControlButton: FunctionComponent< + EuiFormControlButtonProps +> = ({ + children, + value, + placeholder, + className, + contentProps: _contentProps, + textProps: _textProps, + compressed, + isInvalid = false, + fullWidth = true, + href, + rel, // required by our local href-with-rel eslint rule + ...rest +}) => { + const [buttonTextRef, innerText] = useInnerText(); + + const styles = useEuiMemoizedStyles(euiFormControlButtonStyles); + const classes = classNames('euiFormControlButton', className); + + const cssStyles = [ + styles.euiFormControlButton, + isInvalid && styles.isInvalid, + compressed && styles.compressed, + fullWidth ? styles.fullWidth : styles.formWidth, + ]; + + const contentProps = { + ..._contentProps, + css: [styles.euiFormControlButton__content, _contentProps?.css], + }; + + const customTextProps = { + ..._textProps, + className: classNames( + 'eui-textTruncate', + _textProps && _textProps?.className + ), + css: [ + styles.textContent, + !value && styles.placeholder, + _textProps && _textProps?.css, + ], + }; + + const content = children ? ( + isValidElement(children) ? ( + children + ) : ( + {children} + ) + ) : null; + const hasText = value || placeholder; + + const linkProps = { + href, + rel, + ...rest, + } as EuiButtonEmptyPropsForAnchor; + + const buttonProps = { + value: value ? innerText ?? '' : undefined, + ...rest, + } as EuiButtonEmptyPropsForButton; + + const restProps = href ? linkProps : buttonProps; + + return ( + + {hasText && ( + + {value || placeholder} + + )} + {hasText && content && ' '} + {content} + + ); +}; diff --git a/packages/eui/src/components/form/form_control_button/index.ts b/packages/eui/src/components/form/form_control_button/index.ts new file mode 100644 index 00000000000..1338e1b0b23 --- /dev/null +++ b/packages/eui/src/components/form/form_control_button/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { + EuiFormControlButton, + type EuiFormControlButtonInputProps, + type EuiFormControlButtonProps, +} from './form_control_button'; 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 a0917906521..71afc2763d1 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 @@ -6,20 +6,28 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { ChangeEvent, useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; import { hideStorybookControls } from '../../../../.storybook/utils'; - import { useIsWithinMinBreakpoint } from '../../../services'; import { EuiForm } from '../form'; import { EuiFieldText } from '../field_text'; import { EuiIcon } from '../../icon'; import { EuiIconTip, EuiToolTip } from '../../tool_tip'; -import { EuiPopover } from '../../popover'; +import { EuiInputPopover, EuiPopover } from '../../popover'; import { EuiButtonEmpty, EuiButtonIcon } from '../../button'; import { EuiText } from '../../text'; +import { EuiFormRow } from '../form_row'; +import { EuiSelectable, EuiSelectableOption } from '../../selectable'; +import { EuiNotificationBadge } from '../../badge'; +import { EuiFieldSearch } from '../field_search'; +import { + EuiFormControlButton, + EuiFormControlButtonProps, +} from '../form_control_button'; import { EuiFormControlLayout, EuiFormControlLayoutProps, @@ -121,6 +129,44 @@ export const IconShape: Story = { }, }; +export const FormControlButton: Story = { + parameters: { + controls: { + exclude: ['fullWidth', 'isDelimited', 'readOnly', 'wrapperProps'], + }, + }, + render: function Render(args) { + const { isInvalid, isDisabled, compressed } = args; + + return ( + + + + opt.label).join(', ')} + onClick={action('onClick')} + > + + {_options.length} + + + + + + + + + + ); + }, +}; + export const AppendPrepend: Story = { tags: ['vrt-only'], render: function Render() { @@ -267,6 +313,17 @@ export const AppendPrepend: Story = { } append="String" /> + + + + + + ); }, @@ -283,3 +340,148 @@ export const HighContrastDarkMode: Story = { tags: ['vrt-only'], globals: { highContrastMode: true, colorMode: 'dark' }, }; + +/* components */ + +const _options: EuiSelectableOption[] = [ + { + label: 'Titan', + }, + { + label: 'Enceladus', + }, + { + label: 'Mimas', + }, + { + label: 'Dione', + }, + { + label: 'Iapetus', + }, + { + label: 'Phoebe', + }, + { + label: 'Rhea', + }, +]; + +const FormControlButtonWithPopover = ( + args: EuiFormControlLayoutProps & EuiFormControlButtonProps +) => { + const { isInvalid, isDisabled, compressed, placeholder, fullWidth } = args; + + const formControlButtonProps = { + isInvalid, + isDisabled, + compressed, + } as EuiFormControlButtonProps; + + const [isPopoverOpen, setPopoverOpen] = useState(false); + + const [searchValue, setSearchValue] = useState(''); + const [options, setOptions] = useState(_options); + const [selectedOptions, setSelectedOptions] = useState( + [] + ); + const [buttonLabel, setButtonLabel] = useState(''); + + const panelTitle = 'Panel title'; + const popoverId = 'popover-id'; + + const getSelectedOptions = (options: EuiSelectableOption[]) => { + return options + .map((option) => (option.checked === 'on' ? option : null)) + .filter((option) => option !== null); + }; + + const handleOnSearch = (e: ChangeEvent) => { + setSearchValue(e.target.value); + + const filteredOptions = _options.filter((opt) => { + return opt.label.toLowerCase().includes(e.target.value); + }); + + setOptions(filteredOptions); + }; + + const handleOnChange = (options: EuiSelectableOption[]) => { + setOptions(options); + const _selectedOptions = getSelectedOptions(options); + setSelectedOptions(_selectedOptions); + setButtonLabel(_selectedOptions.map((opt) => opt.label).join(', ')); + }; + + const handleOnClear = () => { + setOptions(_options); + setSelectedOptions([]); + setButtonLabel(''); + }; + + const formControlButton = ( + setPopoverOpen(!isPopoverOpen)} + aria-expanded={isPopoverOpen} + aria-controls={popoverId} + > + {(selectedOptions.length > 0 || options.length > 0) && ( + 0 && !isDisabled ? 'success' : 'subdued' + } + > + {selectedOptions.length > 0 ? selectedOptions.length : options.length} + + )} + + ); + + return ( + + setPopoverOpen(false)} + panelProps={{ + title: panelTitle, + 'aria-label': panelTitle, + }} + fullWidth={fullWidth} + > + + + + + + {(list, search) => ( + <> + {search} + {list} + + )} + + + + ); +}; 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 0c8234772de..9d7cadd18ea 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 @@ -57,11 +57,13 @@ export const euiFormControlLayoutStyles = (euiThemeContext: UseEuiTheme) => { const wrapperGroupStyles = ` > :first-child { + border-radius: inherit; ${logicalCSS('border-top-left-radius', '0')} ${logicalCSS('border-bottom-left-radius', '0')} } > :last-child { + border-radius: inherit; ${logicalCSS('border-top-right-radius', '0')} ${logicalCSS('border-bottom-right-radius', '0')} } @@ -117,6 +119,11 @@ export const euiFormControlLayoutStyles = (euiThemeContext: UseEuiTheme) => { ${isRefreshVariant && groupStyles} + .euiFormControlButton { + border-radius: inherit; + box-shadow: none; + } + /* Force the stretch of any children so they expand the full height of the control */ > * { ${logicalCSS('height', '100%')} diff --git a/packages/eui/src/components/form/index.ts b/packages/eui/src/components/form/index.ts index 134df66aa95..3df1b60664a 100644 --- a/packages/eui/src/components/form/index.ts +++ b/packages/eui/src/components/form/index.ts @@ -14,6 +14,7 @@ export * from './field_search'; export * from './field_text'; export * from './file_picker'; export * from './form'; +export * from './form_control_button'; export * from './form_control_layout'; export * from './form_error_text'; export * from './form_fieldset'; diff --git a/packages/website/docs/components/forms/layouts/form_control_buttons.mdx b/packages/website/docs/components/forms/layouts/form_control_buttons.mdx new file mode 100644 index 00000000000..005c964f692 --- /dev/null +++ b/packages/website/docs/components/forms/layouts/form_control_buttons.mdx @@ -0,0 +1,268 @@ +--- +sidebar_label: Form control buttons +keywords: [EuiFormControlButton, EuiFormFilterButton] +--- + +# Form control buttons + +Form control buttons provide semantic button elements that look like form inputs. They are designed for scenarios where you need interactive buttons within form layouts while maintaining form control styling consistently. + +## EuiFormControlButton + +**EuiFormControlButton** renders a button that looks like a form input field. It can be used to trigger actions (e.g. opening popovers) from within a **EuiFormControlLayout** to ensure expected visual output and consistency with input controls. + +:::warning **EuiFormControlButton** is meant to only be used inside **EuiFormControlLayout**. +::: + +The component provides a mix of base [**EuiButtonEmpty**](../../navigation/buttons/button.mdx#empty) props and input specific props - e.g. use `value` and `placeholder` to provide content similar to an input element. You can customize the content by passing additional content via the `children` prop. + +:::tip Dropdown styling +Set `isDropdown` on **EuiFormControlLayout** to indicate a dropdown toggle that accounts for other form layout icons. +::: + +```tsx interactive +import React, { useState } from 'react'; +import { + EuiFieldSearch, + EuiForm, + EuiFormControlLayout, + EuiFormControlButton, + EuiInputPopover, + EuiNotificationBadge, + EuiFlexGroup, + EuiSelectable, + EuiSwitch, + EuiSpacer, + type EuiSelectableOption, +} from '@elastic/eui'; + +const _options: EuiSelectableOption[] = [ + { + label: 'Titan', + }, + { + label: 'Enceladus', + }, + { + label: 'Mimas', + }, + { + label: 'Dione', + }, + { + label: 'Iapetus', + }, + { + label: 'Phoebe', + }, + { + label: 'Rhea', + }, +]; + +export default () => { + const [isCompressed, setCompressed] = useState(false); + const [isDisabled, setDisabled] = useState(false); + const [isInvalid, setInvalid] = useState(false); + const [isDropdown, setDropdown] = useState(false); + const [isClearable, setClearable] = useState(false); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [searchValue, setSearchValue] = useState(''); + const [options, setOptions] = useState(_options); + const [selectedOptions, setSelectedOptions] = useState< + EuiSelectableOption[] + >([]); + const [buttonLabel, setButtonLabel] = useState(''); + + const formLayoutProps = { + compressed: isCompressed, + isDisabled, + isInvalid, + isDropdown, + clear: isClearable && { onClick: () => {} }, + } + + const formButtonProps = { + compressed: isCompressed, + isDisabled, + isInvalid, + } + + const popoverId = 'popover-id'; + const panelTitle = 'popover-panel-title'; + + const handleOnSearch = (e: ChangeEvent) => { + setSearchValue(e.target.value); + + const filteredOptions = _options.filter((opt) => { + return opt.label.toLowerCase().includes(e.target.value); + }); + + setOptions(filteredOptions); + }; + + const handleOnChange = (options: EuiSelectableOption[]) => { + setOptions(options); + const _selectedOptions = getSelectedOptions(options); + setSelectedOptions(_selectedOptions); + setButtonLabel(_selectedOptions.map((opt) => opt.label).join(', ')); + }; + + const handleOnClear = () => { + setOptions(_options); + setSelectedOptions([]); + setButtonLabel(''); + }; + + const getSelectedOptions = (options: EuiSelectableOption[]) => { + return options + .map((option) => (option.checked === 'on' ? option : null)) + .filter((option) => option !== null); + }; + + return ( + <> + + setCompressed(e.target.checked)} + /> + setDisabled(e.target.checked)} + /> + setInvalid(e.target.checked)} + /> + setDropdown(e.target.checked)} + /> + setClearable(e.target.checked)} + /> + + + + + + + + + + + + + + + + + + + + + Active + + + + + + + setIsPopoverOpen(!isPopoverOpen)} + aria-expanded={isPopoverOpen} + aria-controls={popoverId} + {...formButtonProps} + > + {(selectedOptions.length > 0 || options.length > 0) && ( + 0 && !isDisabled ? 'success' : 'subdued' + } + > + {selectedOptions.length > 0 + ? selectedOptions.length + : options.length} + + )} + + )} + hasArrow={false} + repositionOnScroll + isOpen={isPopoverOpen} + panelPaddingSize="none" + panelMinWidth={200} + initialFocus={'[data-test-subj=panel-search-input]'} + closePopover={() => setIsPopoverOpen(false)} + panelProps={{ + title: panelTitle, + 'aria-label': panelTitle, + }} + > + + + + {(list, search) => ( + <> + {search} + {list} + + )} + + + + + + + ); +}; +``` + +## Props + +import docgen from '@elastic/eui-docgen/dist/components/form'; + +