diff --git a/changelogs/upcoming/7576.md b/changelogs/upcoming/7576.md new file mode 100644 index 00000000000..e5f340ead1d --- /dev/null +++ b/changelogs/upcoming/7576.md @@ -0,0 +1 @@ +- Updated `EuiSuperUpdateButton` to support custom button text via an optional `children` prop diff --git a/src-docs/src/views/super_date_picker/playground.js b/src-docs/src/views/super_date_picker/playground.js index 610014ac214..8431a8f80fb 100644 --- a/src-docs/src/views/super_date_picker/playground.js +++ b/src-docs/src/views/super_date_picker/playground.js @@ -77,6 +77,11 @@ export const superUpdateButtonConfig = () => { propsToUse.onClick = simulateFunction(propsToUse.onClick, true); + propsToUse.children = { + ...propsToUse.children, + type: PropTypes.String, + }; + return { config: { componentName: 'EuiSuperUpdateButton', diff --git a/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap b/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap index fa792a8d48e..6fd392b27bd 100644 --- a/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap @@ -1,495 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiSuperUpdateButton iconOnly 1`] = ` - - - - - - - - - - - - -`; - exports[`EuiSuperUpdateButton is rendered 1`] = ` - - - - - - - - - - - - -`; - -exports[`EuiSuperUpdateButton isDisabled 1`] = ` - - } - delay="regular" - display="inlineBlock" - position="bottom" -> - - - - - - - - - - - -`; - -exports[`EuiSuperUpdateButton isLoading 1`] = ` - - - - - - - - - - - - -`; - -exports[`EuiSuperUpdateButton needsUpdate 1`] = ` - - } - delay="regular" - display="inlineBlock" - position="bottom" -> - - - - - - - - - - - -`; - -exports[`EuiSuperUpdateButton responsive can be all 1`] = ` - - - - - - - - - - - - -`; - -exports[`EuiSuperUpdateButton responsive can be false 1`] = ` - - - - - - - - - - - - -`; - -exports[`EuiSuperUpdateButton showTooltip 1`] = ` - - - - - - - - - - - - +
+ + + +
`; diff --git a/src/components/date_picker/super_date_picker/super_update_button.test.tsx b/src/components/date_picker/super_date_picker/super_update_button.test.tsx index 6a606c49b59..a2cf931a92a 100644 --- a/src/components/date_picker/super_date_picker/super_update_button.test.tsx +++ b/src/components/date_picker/super_date_picker/super_update_button.test.tsx @@ -7,13 +7,12 @@ */ import React from 'react'; -import { shallow, mount } from 'enzyme'; import { fireEvent } from '@testing-library/react'; -import { waitForEuiToolTipVisible } from '../../../test/rtl'; +import { render, waitForEuiToolTipVisible, within } from '../../../test/rtl'; import { shouldRenderCustomStyles } from '../../../test/internal'; import { EuiSuperUpdateButton } from './super_update_button'; -import { EuiButton, EuiButtonProps } from '../../button'; +import { EuiButtonProps } from '../../button'; const noop = () => {}; @@ -37,109 +36,120 @@ describe('EuiSuperUpdateButton', () => { } ); - test('is rendered', () => { - const component = shallow(); - - expect(component).toMatchSnapshot(); - }); - - test('needsUpdate', () => { - const component = shallow( - + it('is rendered', () => { + const { container, getByRole } = render( + ); - expect(component).toMatchSnapshot(); - }); + const tooltipWrapper = container.querySelector('.euiToolTipAnchor'); + expect(tooltipWrapper).toBeInTheDocument(); - test('isDisabled', () => { - const component = shallow( - - ); + const button = getByRole('button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass('euiSuperUpdateButton'); + expect(button).toHaveTextContent('Refresh'); - expect(component).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); - test('isLoading', () => { - const component = shallow( - - ); + describe('props', () => { + test('needsUpdate', () => { + const { getByRole } = render( + + ); - expect(component).toMatchSnapshot(); - }); + expect(getByRole('button')).toHaveTextContent('Update'); + }); - test('iconOnly', () => { - const component = shallow(); + test('isDisabled', () => { + const { getByRole } = render( + + ); - expect(component).toMatchSnapshot(); - }); + expect(getByRole('button')).toBeDisabled(); + }); - test('showTooltip', () => { - const component = shallow( - - ); + test('isLoading', () => { + const { getByRole } = render( + + ); - expect(component).toMatchSnapshot(); - }); + expect(getByRole('button')).toHaveTextContent('Updating'); - test('responsive can be all', () => { - const component = shallow( - - ); + const icon = getByRole('progressbar'); + expect(icon).toHaveAccessibleName('Loading'); + }); - expect(component).toMatchSnapshot(); - }); + test('iconOnly', () => { + const { getByText, container } = render( + + ); - test('responsive can be false', () => { - const component = shallow( - - ); + const icon = container.querySelector('[data-euiicon-type]'); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveAttribute('data-euiicon-type', 'refresh'); - expect(component).toMatchSnapshot(); - }); + const text = getByText('Refresh'); + expect(text).toBeInTheDocument(); + expect(text).toHaveClass('euiScreenReaderOnly'); + }); - test('forwards props to EuiButton', () => { - const speciallyHandledProps = { - className: 'testClass', - textProps: { - className: 'textPropsTestClass', - id: 'test', - }, - }; - const extraProps: Partial = { - fill: false, - size: 's', - contentProps: { id: 'contentSpan' }, - }; - - const component = mount( - {}} - {...speciallyHandledProps} - {...extraProps} - /> - ); + test('showTooltip', () => { + const { container } = render( + + ); - const { - // props not passed through - isDisabled, - isLoading, - onClick, + const tooltipWrapper = container.querySelector('.euiToolTipAnchor'); + expect(tooltipWrapper).toBeInTheDocument(); + }); - // props with special handling - className, - textProps, + test('children', () => { + const { getByRole } = render( + + + + ); - ...forwardedProps - } = component.find(EuiButton).props(); + const button = getByRole('button'); + expect(button).toHaveTextContent(''); + + const customChildren = within(button).getByTestSubject('custom-children'); + expect(customChildren).toBeInTheDocument(); + }); - expect(className).toBe('euiSuperUpdateButton testClass'); - expect(textProps).toEqual({ - className: 'textPropsTestClass', - id: 'test', + it('forwards props to EuiButton', () => { + const speciallyHandledProps = { + className: 'testClass', + textProps: { + className: 'textPropsTestClass', + id: 'test', + 'data-test-subj': 'text', + }, + }; + const extraProps: Partial = { + fill: false, + size: 's', + contentProps: { id: 'contentSpan', 'data-test-subj': 'content' }, + }; + + const { getByRole, getByTestSubject } = render( + {}} + {...speciallyHandledProps} + {...extraProps} + /> + ); + + const button = getByRole('button'); + expect(button).toHaveClass('testClass'); + + const text = getByTestSubject('text'); + expect(text).toBeInTheDocument(); + expect(text).toHaveAttribute('id', 'test'); + expect(text).toHaveClass('textPropsTestClass'); + + const buttonContent = within(button).getByTestSubject('content'); + expect(buttonContent).toBeInTheDocument(); }); - expect(forwardedProps).toMatchObject(extraProps); }); }); diff --git a/src/components/date_picker/super_date_picker/super_update_button.tsx b/src/components/date_picker/super_date_picker/super_update_button.tsx index 8c67f437ca8..58d7687e251 100644 --- a/src/components/date_picker/super_date_picker/super_update_button.tsx +++ b/src/components/date_picker/super_date_picker/super_update_button.tsx @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -import React, { Component, MouseEventHandler, ElementRef } from 'react'; +import React, { + Component, + MouseEventHandler, + ElementRef, + ReactNode, +} from 'react'; import classNames from 'classnames'; import { EuiButton, EuiButtonProps } from '../../button'; @@ -25,6 +30,15 @@ type EuiSuperUpdateButtonInternalProps = { }; export type EuiSuperUpdateButtonProps = { + /** + * Overrides the default button label with a custom React node. + * + * When defined, you're responsible for updating the custom label + * when the data needs updating (the `needsUpdate` prop) + * or is loading (the `isLoading` prop). + */ + children?: ReactNode; + /** * Show the "Click to apply" tooltip */ @@ -45,7 +59,9 @@ export type EuiSuperUpdateButtonProps = { * Remove completely with `false` or provide your own list of breakpoints. */ responsive?: false | EuiBreakpointSize[]; -} & Partial>; +} & Partial< + Omit +>; export class EuiSuperUpdateButton extends Component< EuiSuperUpdateButtonInternalProps & EuiSuperUpdateButtonProps @@ -104,6 +120,7 @@ export class EuiSuperUpdateButton extends Component< render() { const { + children, className, needsUpdate, isLoading, @@ -122,43 +139,6 @@ export class EuiSuperUpdateButton extends Component< const classes = classNames('euiSuperUpdateButton', className); - let buttonText = ( - - ); - if (needsUpdate || isLoading) { - buttonText = isLoading ? ( - - ) : ( - - ); - } - - let tooltipContent; - if (isDisabled) { - tooltipContent = ( - - ); - } else if (needsUpdate && !isLoading) { - tooltipContent = ( - - ); - } - const sharedButtonProps = { color: needsUpdate || isLoading ? 'success' : 'primary', iconType: needsUpdate || isLoading ? 'kqlFunction' : 'refresh', @@ -167,10 +147,12 @@ export class EuiSuperUpdateButton extends Component< isLoading: isLoading, }; + const buttonContent = this.renderButtonContent(); + return ( @@ -190,7 +172,7 @@ export class EuiSuperUpdateButton extends Component< }} {...rest} > - {buttonText} + {buttonContent} @@ -202,11 +184,64 @@ export class EuiSuperUpdateButton extends Component< textProps={restTextProps} {...rest} > - {buttonText} + {buttonContent} ); } + + private renderButtonContent(): ReactNode { + const { children, isLoading, needsUpdate } = this.props; + + if (children) { + return children; + } + + if (isLoading) { + return ( + + ); + } + + if (needsUpdate) { + return ( + + ); + } + + return ( + + ); + } + + private renderTooltipContent(): ReactNode { + if (this.props.isDisabled) { + return ( + + ); + } + + if (this.props.needsUpdate && !this.props.isLoading) { + return ( + + ); + } + } }