diff --git a/CHANGELOG.md b/CHANGELOG.md index c326eee1f35..28e9b4178af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ No public interface changes since `27.1.0`. - Added `index.d.ts` file to `lib/test` and `es/test` ([#3715](https://github.com/elastic/eui/pull/3715)) - Added `descriptionFlexItemProps` and `fieldFlexItemProps` props to `EuiDescribedFormGroup` ([#3717](https://github.com/elastic/eui/pull/3717)) - Expanded `EuiBasicTable`'s default action's name configuration to accept a function that returns a React node ([#3739](https://github.com/elastic/eui/pull/3739)) -- Added internal use only button building blocks for reusability in other button components ([#3730](https://github.com/elastic/eui/pull/3730)) ## [`27.0.0`](https://github.com/elastic/eui/tree/v27.0.0) - Added `paddingSize` prop to `EuiCard` ([#3638](https://github.com/elastic/eui/pull/3638)) diff --git a/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap b/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap index 5a12b3e9c5c..8d73fa7dd02 100644 --- a/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap +++ b/src/components/accessibility/__snapshots__/skip_link.test.tsx.snap @@ -9,7 +9,7 @@ exports[`EuiSkipLink is rendered 1`] = ` rel="noreferrer" > @@ -554,25 +545,15 @@ exports[`EuiInMemoryTable behavior pagination 1`] = ` onClick={[Function]} rel="noreferrer" > - - - 1 - + 1 - + @@ -630,31 +611,21 @@ exports[`EuiInMemoryTable behavior pagination 1`] = ` aria-controls="generated-id" aria-current={true} aria-label="Page 2 of 2" - className="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--xSmall euiButtonEmpty-isDisabled euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile" + className="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--xSmall euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile" data-test-subj="pagination-button-1" disabled={true} onClick={[Function]} type="button" > - - - 2 - + 2 - + diff --git a/src/components/button/__snapshots__/button.test.tsx.snap b/src/components/button/__snapshots__/button.test.tsx.snap index 8e699c46818..4df2e87c8ca 100644 --- a/src/components/button/__snapshots__/button.test.tsx.snap +++ b/src/components/button/__snapshots__/button.test.tsx.snap @@ -8,7 +8,7 @@ exports[`EuiButton is rendered 1`] = ` type="button" > `; -exports[`EuiButton props contentProps is rendered 1`] = ` - -`; - exports[`EuiButton props fill is rendered 1`] = ` -`; - -exports[`EuiButton props isDisabled renders if passed as disabled 1`] = ` - `; - -exports[`EuiButton props textProps is rendered 1`] = ` - -`; diff --git a/src/components/button/__snapshots__/button_content.test.tsx.snap b/src/components/button/__snapshots__/button_content.test.tsx.snap deleted file mode 100644 index 0625a6a37f6..00000000000 --- a/src/components/button/__snapshots__/button_content.test.tsx.snap +++ /dev/null @@ -1,79 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EuiButtonContent is rendered 1`] = ` - - - -`; - -exports[`EuiButtonContent props children is rendered 1`] = ` - - - Content - - -`; - -exports[`EuiButtonContent props iconSide is rendered 1`] = ` - -
- - -`; - -exports[`EuiButtonContent props iconType is rendered 1`] = ` - -
- - -`; - -exports[`EuiButtonContent props isLoading is rendered 1`] = ` - - - - -`; - -exports[`EuiButtonContent props isLoading replaces iconType with spinner 1`] = ` - - - - -`; - -exports[`EuiButtonContent props textProps is rendered 1`] = ` - - - -`; diff --git a/src/components/button/_button.scss b/src/components/button/_button.scss index d77c41f9db6..2551d35f3f5 100644 --- a/src/components/button/_button.scss +++ b/src/components/button/_button.scss @@ -10,6 +10,8 @@ min-width: $euiButtonMinWidth; .euiButton__content { + @include euiButtonContent; + padding: 0 ($euiSize - $euiSizeXS); } @@ -23,6 +25,12 @@ line-height: $euiButtonHeightSmall; // prevents descenders from getting cut off } + &.euiButton--iconRight { + .euiButton__content { + @include euiButtonContent($isReverse: true); + } + } + &:hover, &:active { @include euiSlightShadowHover; @@ -36,12 +44,19 @@ } &:disabled { - @include euiButtonContentDisabled; - color: $euiButtonColorDisabledText; border-color: $euiButtonColorDisabled; pointer-events: none; + .euiButton__content { + pointer-events: auto; + cursor: not-allowed; + } + + .euiButton__spinner { + border-color: euiLoadingSpinnerBorderColors(currentColor); + } + &.euiButton--fill { // Only increase the contrast of background color to text to 2.0 for disabled color: makeHighContrastColor($euiButtonColorDisabled, $euiButtonColorDisabled, 2); diff --git a/src/components/button/_button_content.scss b/src/components/button/_button_content.scss deleted file mode 100644 index e1933a0cbf7..00000000000 --- a/src/components/button/_button_content.scss +++ /dev/null @@ -1,7 +0,0 @@ -.euiButtonContent { - @include euiButtonContent; -} - -.euiButtonContent--iconRight { - @include euiButtonContent($isReverse: true); -} diff --git a/src/components/button/_index.scss b/src/components/button/_index.scss index 61a1784e7f4..2815f912feb 100644 --- a/src/components/button/_index.scss +++ b/src/components/button/_index.scss @@ -1,5 +1,4 @@ @import 'button'; -@import 'button_content'; @import 'button_empty/index'; @import 'button_icon/index'; @import 'button_toggle/index'; diff --git a/src/components/button/button.test.tsx b/src/components/button/button.test.tsx index bcc7c3cd655..69f0ba0af97 100644 --- a/src/components/button/button.test.tsx +++ b/src/components/button/button.test.tsx @@ -21,8 +21,7 @@ import React from 'react'; import { render, mount } from 'enzyme'; import { requiredProps } from '../../test/required_props'; -import { EuiButton, COLORS, SIZES } from './button'; -import { ICON_SIDES } from './button_content'; +import { EuiButton, COLORS, SIZES, ICON_SIDES } from './button'; describe('EuiButton', () => { test('is rendered', () => { @@ -52,12 +51,6 @@ describe('EuiButton', () => { expect(component).toMatchSnapshot(); }); - - it('renders if passed as disabled', () => { - const component = render(); - - expect(component).toMatchSnapshot(); - }); }); describe('isLoading', () => { @@ -141,21 +134,5 @@ describe('EuiButton', () => { expect(handler.mock.calls.length).toEqual(1); }); }); - - test('contentProps is rendered', () => { - const component = render( - Content - ); - - expect(component).toMatchSnapshot(); - }); - - test('textProps is rendered', () => { - const component = render( - Content - ); - - expect(component).toMatchSnapshot(); - }); }); }); diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx index c230b0d2214..7d01b5f9e44 100644 --- a/src/components/button/button.tsx +++ b/src/components/button/button.tsx @@ -17,7 +17,12 @@ * under the License. */ -import React, { FunctionComponent, Ref, ButtonHTMLAttributes } from 'react'; +import React, { + FunctionComponent, + HTMLAttributes, + Ref, + ButtonHTMLAttributes, +} from 'react'; import classNames from 'classnames'; import { @@ -27,14 +32,13 @@ import { PropsForButton, keysOf, } from '../common'; +import { EuiLoadingSpinner } from '../loading'; import { getSecureRelForTarget } from '../../services'; -import { - EuiButtonContentProps, - EuiButtonContentType, - EuiButtonContent, -} from './button_content'; +import { IconType, EuiIcon } from '../icon'; + +export type ButtonIconSide = 'left' | 'right'; export type ButtonColor = | 'primary' @@ -68,121 +72,35 @@ const sizeToClassNameMap: { [size in ButtonSize]: string | null } = { export const SIZES = keysOf(sizeToClassNameMap); -/** - * Extends EuiButtonContentProps which provides - * `iconType`, `iconSide`, and `textProps` - */ -export interface EuiButtonProps extends EuiButtonContentProps, CommonProps { - /** - * Make button a solid color for prominence - */ +const iconSideToClassNameMap: { [side in ButtonIconSide]: string | null } = { + left: null, + right: 'euiButton--iconRight', +}; + +export const ICON_SIDES = keysOf(iconSideToClassNameMap); + +export interface EuiButtonProps extends CommonProps { + iconType?: IconType; + iconSide?: ButtonIconSide; fill?: boolean; /** - * Any of our named colors. `text` color is set for deprecation + * `text` color is set for deprecation */ color?: ButtonColor; - /** - * Use size `s` in confined spaces - */ size?: ButtonSize; - /** - * `disabled` is also allowed - */ + isLoading?: boolean; isDisabled?: boolean; - /** - * Extends the button to 100% width - */ fullWidth?: boolean; /** - * Force disables the button and changes the icon to a loading spinner + * Object of props passed to the wrapping the button's content */ - isLoading?: boolean; + contentProps?: HTMLAttributes; /** - * Object of props passed to the wrapping the button's content + * Object of props passed to the wrapping the component's {children} */ - contentProps?: EuiButtonContentType; + textProps?: HTMLAttributes; } -export interface EuiButtonDisplayProps extends EuiButtonProps { - element: 'a' | 'button' | 'span' | 'label'; -} - -/** - * *INTERNAL ONLY* - * Component for displaying any element as a button - * EuiButton is largely responsible for providing relevant props - * and the logic for element-specific attributes - */ -const EuiButtonDisplay = React.forwardRef( - ( - { - children, - className, - iconType, - iconSide = 'left', - color = 'primary', - size = 'm', - fill = false, - isDisabled, - isLoading, - contentProps, - textProps, - fullWidth, - element = 'button', - ...rest - }, - ref - ) => { - const classes = classNames( - 'euiButton', - color ? colorToClassNameMap[color] : null, - size ? sizeToClassNameMap[size] : null, - className, - { - 'euiButton--fill': fill, - 'euiButton--fullWidth': fullWidth, - 'euiButton-isDisabled': isDisabled, - } - ); - - const contentClassNames = classNames( - 'euiButton__content', - contentProps && contentProps.className - ); - - const textClassNames = classNames( - 'euiButton__text', - textProps && textProps.className - ); - - const innerNode = ( - - {children} - - ); - - return React.createElement( - element, - { - className: classes, - ref, - ...rest, - }, - innerNode - ); - } -); - -EuiButtonDisplay.displayName = 'EuiButtonDisplay'; -export { EuiButtonDisplay }; - type EuiButtonPropsForAnchor = PropsForAnchor< EuiButtonProps, { @@ -203,26 +121,79 @@ export type Props = ExclusiveUnion< >; export const EuiButton: FunctionComponent = ({ + children, + className, + iconType, + iconSide = 'left', + color = 'primary', + size = 'm', + fill = false, isDisabled, - disabled, + isLoading, href, target, rel, type = 'button', buttonRef, + contentProps, + textProps, + fullWidth, ...rest }) => { - const buttonIsDisabled = rest.isLoading || isDisabled || disabled; - const element = href && !isDisabled ? 'a' : 'button'; - - let elementProps = {}; - // Props for all elements - elementProps = { ...elementProps, isDisabled: buttonIsDisabled }; - // Element-specific attributes - if (element === 'button') { - elementProps = { ...elementProps, disabled: buttonIsDisabled }; + // If in the loading state, force disabled to true + isDisabled = isLoading ? true : isDisabled; + + const classes = classNames( + 'euiButton', + color ? colorToClassNameMap[color] : null, + size ? sizeToClassNameMap[size] : null, + iconSide ? iconSideToClassNameMap[iconSide] : null, + className, + { + 'euiButton--fill': fill, + 'euiButton--fullWidth': fullWidth, + } + ); + + const contentClassNames = classNames( + 'euiButton__content', + contentProps && contentProps.className + ); + + const textClassNames = classNames( + 'euiButton__text', + textProps && textProps.className + ); + + // Add an icon to the button if one exists. + let buttonIcon; + + if (isLoading) { + buttonIcon = ; + } else if (iconType) { + buttonIcon = ( +
- - - + Sound the Alarm + + +
- - - + Sound the Alarm + + +
- - - + Sound the Alarm + + +
- - - + Sound the Alarm + + +
- - - + Sound the Alarm + + +
- - - + Sound the Alarm + + +