From fd997c968770b09ee844296ad02f51b9cd09b087 Mon Sep 17 00:00:00 2001 From: Lene Gadewoll Date: Mon, 15 Sep 2025 11:34:06 +0200 Subject: [PATCH 01/12] feat: add EuiFormAppend and EuiFormPrepend - updates EuiFormControlLayout by adding a context to share props - updates EuiFormLabel to support visual-only renders as span element --- .../form_append_prepend.test.tsx.snap | 43 +++ .../append_prepend/form_append.stories.tsx | 76 +++++ .../form_append_prepend.styles.ts | 112 ++++++++ .../form_append_prepend.test.tsx | 266 ++++++++++++++++++ .../append_prepend/form_append_prepend.tsx | 180 ++++++++++++ .../append_prepend/form_prepend.stories.tsx | 78 +++++ .../append_prepend/index.ts | 16 ++ .../form_control_layout.tsx | 97 ++++--- .../form_control_layout_context.tsx | 27 ++ .../form/form_control_layout/index.ts | 1 + .../components/form/form_label/form_label.tsx | 22 +- 11 files changed, 869 insertions(+), 49 deletions(-) create mode 100644 packages/eui/src/components/form/form_control_layout/append_prepend/__snapshots__/form_append_prepend.test.tsx.snap create mode 100644 packages/eui/src/components/form/form_control_layout/append_prepend/form_append.stories.tsx create mode 100644 packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.styles.ts create mode 100644 packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.test.tsx create mode 100644 packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.tsx create mode 100644 packages/eui/src/components/form/form_control_layout/append_prepend/form_prepend.stories.tsx create mode 100644 packages/eui/src/components/form/form_control_layout/append_prepend/index.ts create mode 100644 packages/eui/src/components/form/form_control_layout/form_control_layout_context.tsx diff --git a/packages/eui/src/components/form/form_control_layout/append_prepend/__snapshots__/form_append_prepend.test.tsx.snap b/packages/eui/src/components/form/form_control_layout/append_prepend/__snapshots__/form_append_prepend.test.tsx.snap new file mode 100644 index 00000000000..5c9d242ec9c --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/__snapshots__/form_append_prepend.test.tsx.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiFormAppend is rendered 1`] = ` +
+ +
+`; + +exports[`EuiFormAppendPrepend is rendered 1`] = ` +
+ +
+`; + +exports[`EuiFormPrepend is rendered 1`] = ` +
+ +
+`; 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..2f7094c90ba --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.styles.ts @@ -0,0 +1,112 @@ +/* + * 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 { isEuiThemeRefreshVariant } from '../../../../services'; +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 isRefreshVariant = isEuiThemeRefreshVariant( + euiThemeContext, + 'formVariant' + ); + 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', + isRefreshVariant ? euiTheme.size.m : euiTheme.size.s + )} + } + + &:not(:has(> ${buttons}:last-child, > *:last-child ${buttons})) { + ${logicalCSS( + 'padding-right', + isRefreshVariant ? euiTheme.size.m : euiTheme.size.s + )} + } + `, + 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, + &: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..37d1dbca083 --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.test.tsx @@ -0,0 +1,266 @@ +/* + * 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[0]).toBe('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[0]).toBe('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', () => { + const { getByText } = render( + + ); + + const element = getByText(defaultProps.label); + + expect(element).toBeInTheDocument(); + expect(element.tagName).toBe('LABEL'); + }); + + 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..2453b55dbe5 --- /dev/null +++ b/packages/eui/src/components/form/form_control_layout/append_prepend/form_append_prepend.tsx @@ -0,0 +1,180 @@ +/* + * 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', + 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', + className: 'eui-textTruncate', + } as _EuiFormLabelSpanProps) + : ({ + type: 'label', + 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.tsx b/packages/eui/src/components/form/form_control_layout/form_control_layout.tsx index 1c7438f648e..e85c45b1305 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,7 @@ import { euiFormControlLayoutStyles, euiFormControlLayoutSideNodeStyles, } from './form_control_layout.styles'; +import { EuiFormControlLayoutContextProvider } from './form_control_layout_context'; type StringOrReactElement = string | ReactElement; type PrependAppendType = StringOrReactElement | StringOrReactElement[]; @@ -162,53 +163,57 @@ export const EuiFormControlLayout: FunctionComponent< return (
- -
- {hasLeftIcon && ( - - )} - - {children} - - {hasRightIcons && ( - - )} -
- + +
+ {hasLeftIcon && ( + + )} + + {children} + + {hasRightIcons && ( + + )} +
+ +
); }; 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/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/form_label.tsx b/packages/eui/src/components/form/form_label/form_label.tsx index dff8e7bd401..1df491da3f2 100644 --- a/packages/eui/src/components/form/form_label/form_label.tsx +++ b/packages/eui/src/components/form/form_label/form_label.tsx @@ -29,7 +29,7 @@ interface EuiFormLabelCommonProps { * Default type is a `label` but can be changed to a `legend` * if using inside a `fieldset`. */ - type?: 'label' | 'legend'; + type?: 'label' | 'legend' | 'span'; } export type _EuiFormLabelProps = { @@ -44,9 +44,15 @@ export type _EuiFormLegendProps = { CommonProps & HTMLAttributes; +export type _EuiFormLabelSpanProps = { + type: 'span'; +} & EuiFormLabelCommonProps & + CommonProps & + HTMLAttributes; + export type EuiFormLabelProps = ExclusiveUnion< - _EuiFormLabelProps, - _EuiFormLegendProps + ExclusiveUnion<_EuiFormLabelProps, _EuiFormLegendProps>, + _EuiFormLabelSpanProps >; export const EuiFormLabel: FunctionComponent = ({ @@ -82,6 +88,16 @@ export const EuiFormLabel: FunctionComponent = ({ {children} ); + } else if (type === 'span') { + return ( + )} + > + {children} + + ); } else { return (