diff --git a/src-docs/src/views/facet/facet_layout.js b/src-docs/src/views/facet/facet_layout.js index ad1fdc47892..95fc6afb02c 100644 --- a/src-docs/src/views/facet/facet_layout.js +++ b/src-docs/src/views/facet/facet_layout.js @@ -5,8 +5,6 @@ import { EuiFacetGroup, EuiIcon, EuiAvatar, - EuiTitle, - EuiSpacer, } from '../../../../src/components'; import { euiPaletteColorBlind } from '../../../../src/services'; @@ -146,22 +144,8 @@ export default () => { }; return ( -
- -

Vertical

-
- - {facets('Vertical')} - - - - - -

Horizontal and large gutter

-
- - {facets('Horizontal')} - -
+ + {facets('Vertical')} + ); }; diff --git a/src-docs/src/views/loading/loading_example.js b/src-docs/src/views/loading/loading_example.tsx similarity index 91% rename from src-docs/src/views/loading/loading_example.js rename to src-docs/src/views/loading/loading_example.tsx index d2cc9f43379..95e44130ffc 100644 --- a/src-docs/src/views/loading/loading_example.js +++ b/src-docs/src/views/loading/loading_example.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { Link } from 'react-router-dom'; - +// @ts-ignore Importing from JS import { GuideSectionTypes } from '../../components'; import { @@ -12,14 +12,22 @@ import { EuiLoadingChart, EuiLoadingContent, } from '../../../../src/components'; + import { loadingElasticConfig, loadingChartConfig, loadingLogoConfig, loadingSpinnerConfig, loadingContentConfig, + // @ts-ignore Importing from JS } from './playground'; +import { EuiLoadingSpinnerColor as EuiLoadingSpinnerColorProps } from '../../../../src/components/loading/loading_spinner'; + +export const EuiLoadingSpinnerColor: FunctionComponent = () => ( +
+); + import LoadingLogo from './loading_kibana'; const loadingLogoSource = require('!!raw-loader!./loading_kibana'); @@ -129,7 +137,7 @@ export const LoadingExample = { loading contexts.

), - props: { EuiLoadingSpinner }, + props: { EuiLoadingSpinner, EuiLoadingSpinnerColor }, demo: , snippet: '', playground: loadingSpinnerConfig, diff --git a/src/components/accordion/__snapshots__/accordion.test.tsx.snap b/src/components/accordion/__snapshots__/accordion.test.tsx.snap index 2694fa8d6ec..ae68bc7eed7 100644 --- a/src/components/accordion/__snapshots__/accordion.test.tsx.snap +++ b/src/components/accordion/__snapshots__/accordion.test.tsx.snap @@ -862,7 +862,7 @@ exports[`EuiAccordion props isLoading is rendered 1`] = ` >
@@ -921,7 +921,7 @@ exports[`EuiAccordion props isLoadingMessage is rendered 1`] = ` > @@ -939,7 +939,7 @@ exports[`EuiAccordion props isLoadingMessage is rendered 1`] = ` >
@@ -65,7 +65,7 @@ exports[`EuiButtonContent props isLoading replaces iconType with spinner 1`] = ` > diff --git a/src/components/button/button_content.tsx b/src/components/button/_button_content_deprecated.tsx similarity index 87% rename from src/components/button/button_content.tsx rename to src/components/button/_button_content_deprecated.tsx index 622f8024b7a..fa9dd804b79 100644 --- a/src/components/button/button_content.tsx +++ b/src/components/button/_button_content_deprecated.tsx @@ -26,8 +26,10 @@ export const ICON_SIDES = keysOf(iconSideToClassNameMap); export type EuiButtonContentType = HTMLAttributes; /** - * *INTERNAL ONLY* + * *INTERNAL ONLY / DEPRECATED* * This component is simply a helper component for reuse within other button components + * This component has been deprecated in favor of the new EuiButtonDisplayContent + * that can be found in `src/components/button/button_display/_button_display_content.tsx`. */ export interface EuiButtonContentProps extends CommonProps { /** @@ -40,7 +42,8 @@ export interface EuiButtonContentProps extends CommonProps { iconSide?: ButtonContentIconSide; isLoading?: boolean; /** - * Object of props passed to the wrapping the content's text/children only (not icon) + * Object of props passed to the wrapping the content's text (only if the children is a `string`) + * It doesn't apply to the icon. */ textProps?: HTMLAttributes & CommonProps & { @@ -50,7 +53,7 @@ export interface EuiButtonContentProps extends CommonProps { iconSize?: 's' | 'm'; } -export const EuiButtonContent: FunctionComponent< +export const EuiButtonContentDeprecated: FunctionComponent< EuiButtonContentType & EuiButtonContentProps > = ({ children, diff --git a/src/components/button/button.test.tsx b/src/components/button/button.test.tsx index ac22ca17796..de901391660 100644 --- a/src/components/button/button.test.tsx +++ b/src/components/button/button.test.tsx @@ -11,7 +11,8 @@ import { render, mount } from 'enzyme'; import { requiredProps } from '../../test/required_props'; import { EuiButton, COLORS, SIZES } from './button'; -import { ICON_SIDES } from './button_content'; + +import { ICON_SIDES } from './_button_content_deprecated'; describe('EuiButton', () => { test('is rendered', () => { diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx index 29b45c16315..c95d7ec980d 100644 --- a/src/components/button/button.tsx +++ b/src/components/button/button.tsx @@ -30,8 +30,8 @@ import { getSecureRelForTarget } from '../../services'; import { EuiButtonContentProps, EuiButtonContentType, - EuiButtonContent, -} from './button_content'; + EuiButtonContentDeprecated as EuiButtonContent, +} from './_button_content_deprecated'; import { validateHref } from '../../services/security/href_validator'; export type ButtonColor = @@ -174,7 +174,8 @@ export const EuiButton: FunctionComponent = ({ } return ( - ( +export const EuiButtonDisplayDeprecated = forwardRef< + HTMLElement, + EuiButtonDisplayProps +>( ( { element = 'button', @@ -295,4 +302,4 @@ export const EuiButtonDisplay = forwardRef( ); } ); -EuiButtonDisplay.displayName = 'EuiButtonDisplay'; +EuiButtonDisplayDeprecated.displayName = 'EuiButtonDisplay'; diff --git a/src/components/button/button_content.test.tsx b/src/components/button/button_content.test.tsx index 145de430f84..21ea25c4aa3 100644 --- a/src/components/button/button_content.test.tsx +++ b/src/components/button/button_content.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { render } from 'enzyme'; import { requiredProps } from '../../test/required_props'; -import { EuiButtonContent } from './button_content'; +import { EuiButtonContentDeprecated as EuiButtonContent } from './_button_content_deprecated'; describe('EuiButtonContent', () => { test('is rendered', () => { diff --git a/src/components/button/button_display/_button_display.styles.ts b/src/components/button/button_display/_button_display.styles.ts new file mode 100644 index 00000000000..f723a835f64 --- /dev/null +++ b/src/components/button/button_display/_button_display.styles.ts @@ -0,0 +1,63 @@ +/* + * 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 '../../../services'; +import { + euiFontSize, + logicalCSS, + logicalTextAlignStyle, +} from '../../../global_styling'; + +// Provides a solid reset and base for handling sizing layout +// Does not include any visual styles +export const euiButtonBaseCSS = () => { + return ` + display: inline-block; + appearance: none; + cursor: pointer; + ${logicalTextAlignStyle('center')}; + white-space: nowrap; + ${logicalCSS('max-width', '100%')}; + vertical-align: middle; + `; +}; + +const _buttonSize = (size: string) => { + return ` + ${logicalCSS('height', size)}; + // prevents descenders from getting cut off + line-height: ${size}; + `; +}; + +export const euiButtonDisplayStyles = ( + euiThemeContext: UseEuiTheme, + minWidth: string +) => { + const { euiTheme } = euiThemeContext; + + return { + // Base + euiButtonDisplay: css` + ${euiButtonBaseCSS()}; + ${minWidth && logicalCSS('min-width', minWidth)}; + `, + // States + isDisabled: css` + cursor: not-allowed; + `, + fullWidth: css` + display: block; + width: 100%; + `, + // Sizes + xs: css(_buttonSize(euiTheme.size.l), euiFontSize(euiThemeContext, 'xs')), + s: css(_buttonSize(euiTheme.size.xl), euiFontSize(euiThemeContext, 's')), + m: css(_buttonSize(euiTheme.size.xxl), euiFontSize(euiThemeContext, 's')), + }; +}; diff --git a/src/components/button/button_display/_button_display.tsx b/src/components/button/button_display/_button_display.tsx new file mode 100644 index 00000000000..73a791ac80c --- /dev/null +++ b/src/components/button/button_display/_button_display.tsx @@ -0,0 +1,160 @@ +/* + * 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, { + forwardRef, + CSSProperties, + HTMLAttributes, + ReactNode, + Ref, +} from 'react'; + +// @ts-ignore module doesn't export `createElement` +import { createElement } from '@emotion/react'; +import { useEuiTheme } from '../../../services'; + +import { + CommonProps, + ExclusiveUnion, + PropsForAnchor, + PropsForButton, +} from '../../common'; + +import { euiButtonDisplayStyles } from './_button_display.styles'; +import { + EuiButtonDisplayContent, + EuiButtonDisplayContentProps, + EuiButtonDisplayContentType, +} from './_button_display_content'; + +/** + * Extends EuiButtonDisplayContentProps which provides + * `iconType`, `iconSide`, and `textProps` + */ +export interface EuiButtonDisplayCommonProps + extends EuiButtonDisplayContentProps, + CommonProps { + children?: ReactNode; + size?: 'xs' | 's' | 'm'; + /** + * Applies the boolean state as the `aria-pressed` property to create a toggle button. + * *Only use when the readable text does not change between states.* + */ + isSelected?: boolean; + /** + * Extends the button to 100% width + */ + fullWidth?: boolean; + /** + * Override the default minimum width + */ + minWidth?: CSSProperties['minWidth']; + /** + * Force disables the button and changes the icon to a loading spinner + */ + isLoading?: boolean; + /** + * Object of props passed to the wrapping the button's content + */ + contentProps?: EuiButtonDisplayContentType; + style?: CSSProperties; +} + +export type EuiButtonDisplayPropsForAnchor = PropsForAnchor< + EuiButtonDisplayCommonProps, + { + buttonRef?: Ref; + } +>; + +export type EuiButtonDisplayPropsForButton = PropsForButton< + EuiButtonDisplayCommonProps, + { + buttonRef?: Ref; + } +>; + +export type Props = ExclusiveUnion< + EuiButtonDisplayPropsForAnchor, + EuiButtonDisplayPropsForButton +>; + +export type EuiButtonDisplayProps = EuiButtonDisplayCommonProps & + HTMLAttributes & { + /** + * Provide a valid element to render the element as + */ + element?: 'a' | 'button' | 'span' | 'label'; + }; + +/** + * EuiButtonDisplay is an internal-only component used for displaying + * any element as a button. + */ +export const EuiButtonDisplay = forwardRef( + ( + { + element = 'button', + children, + iconType, + iconSide, + size = 'm', + isDisabled = false, + isLoading, + isSelected, + fullWidth, + minWidth, + contentProps, + textProps, + ...rest + }, + ref + ) => { + const buttonIsDisabled = isLoading || isDisabled; + + const minWidthPx: string = + minWidth === 'number' ? `${minWidth}px` : (minWidth as string); + + const theme = useEuiTheme(); + + const styles = euiButtonDisplayStyles(theme, minWidthPx); + const cssStyles = [ + styles.euiButtonDisplay, + styles[size], + fullWidth && styles.fullWidth, + isDisabled && styles.isDisabled, + ]; + + const innerNode = ( + + {children} + + ); + + return createElement( + element, + { + css: cssStyles, + disabled: element === 'button' && buttonIsDisabled, + 'aria-pressed': element === 'button' ? isSelected : undefined, + ref, + ...rest, + }, + innerNode + ); + } +); + +EuiButtonDisplay.displayName = 'EuiButtonDisplay'; diff --git a/src/components/button/button_display/_button_display_content.styles.ts b/src/components/button/button_display/_button_display_content.styles.ts new file mode 100644 index 00000000000..9838957b43f --- /dev/null +++ b/src/components/button/button_display/_button_display_content.styles.ts @@ -0,0 +1,38 @@ +/* + * 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 '../../../services'; +import { logicalCSS } from '../../../global_styling'; + +export const euiButtonDisplayContentStyles = ({ euiTheme }: UseEuiTheme) => ({ + // Base + euiButtonDisplayContent: css` + ${logicalCSS('height', '100%')}; + ${logicalCSS('width', '100%')}; + display: flex; + justify-content: center; + align-items: center; + vertical-align: middle; + gap: ${euiTheme.size.s}; + `, + // Icon side + left: css``, + right: css` + flex-direction: row-reverse; + `, + euiButtonDisplayContent__spinner: css` + flex-shrink: 0; + `, + euiButtonDisplayContent__icon: css` + flex-shrink: 0; + `, + isDisabled: css` + pointer-events: auto; + cursor: not-allowed; + `, +}); diff --git a/src/components/button/button_display/_button_display_content.tsx b/src/components/button/button_display/_button_display_content.tsx new file mode 100644 index 00000000000..4bb7b1d5e25 --- /dev/null +++ b/src/components/button/button_display/_button_display_content.tsx @@ -0,0 +1,110 @@ +/* + * 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, { HTMLAttributes, FunctionComponent, Ref } from 'react'; +import { useEuiTheme } from '../../../services'; +import { CommonProps } from '../../common'; +import { EuiLoadingSpinner, EuiLoadingSpinnerProps } from '../../loading'; +import { EuiIcon, IconType } from '../../icon'; +import { euiButtonDisplayContentStyles } from './_button_display_content.styles'; + +export type ButtonContentIconSide = 'left' | 'right' | undefined; + +export type EuiButtonDisplayContentType = HTMLAttributes; + +/** + * *INTERNAL ONLY* + * This component is simply a helper component for reuse within other button components. + */ +export interface EuiButtonDisplayContentProps extends CommonProps { + /** + * Any `type` accepted by EuiIcon + */ + iconType?: IconType; + /** + * Can only be one side `left` or `right` + */ + iconSide?: ButtonContentIconSide; + isLoading?: boolean; + /** + * Object of props passed to the wrapping the content's text/children only (not icon) + */ + textProps?: HTMLAttributes & + CommonProps & { + ref?: Ref; + 'data-text'?: string; + }; + iconSize?: 's' | 'm'; + isDisabled: boolean; +} + +export const EuiButtonDisplayContent: FunctionComponent< + EuiButtonDisplayContentType & EuiButtonDisplayContentProps +> = ({ + children, + textProps, + isLoading = false, + isDisabled = false, + iconType, + iconSize = 'm', + iconSide, + ...contentProps +}) => { + const theme = useEuiTheme(); + const styles = euiButtonDisplayContentStyles(theme); + + const cssStyles = [ + styles.euiButtonDisplayContent, + iconSide && styles[iconSide], + isDisabled && styles.isDisabled, + ]; + const cssSpinnerStyles = [styles.euiButtonDisplayContent__spinner]; + const cssIconStyles = [styles.euiButtonDisplayContent__icon]; + + // Add an icon to the button if one exists. + let icon; + + // When the button is disabled the text gets gray + // and in some buttons the background gets a light gray + // for better contrast we want to change the border of the spinner + // to have the same color of the text. This way we ensure the borders + // are always visible. The default spinner color could be very light. + const loadingSpinnerColor = isDisabled + ? ({ + border: 'currentColor', + } as EuiLoadingSpinnerProps['color']) + : undefined; + + if (isLoading) { + icon = ( + + ); + } else if (iconType) { + icon = ( + + ); + } + + const isText = typeof children === 'string'; + + return ( + + {icon} + {isText ? {children} : children} + + ); +}; diff --git a/src/components/button/button_empty/__snapshots__/button_empty.test.tsx.snap b/src/components/button/button_empty/__snapshots__/button_empty.test.tsx.snap index d3773a14d2d..2bc21572809 100644 --- a/src/components/button/button_empty/__snapshots__/button_empty.test.tsx.snap +++ b/src/components/button/button_empty/__snapshots__/button_empty.test.tsx.snap @@ -313,7 +313,7 @@ exports[`EuiButtonEmpty props isLoading is rendered 1`] = ` > { test('is rendered', () => { diff --git a/src/components/button/button_empty/button_empty.tsx b/src/components/button/button_empty/button_empty.tsx index a754862f1f7..40748723016 100644 --- a/src/components/button/button_empty/button_empty.tsx +++ b/src/components/button/button_empty/button_empty.tsx @@ -17,11 +17,13 @@ import { keysOf, } from '../../common'; import { getSecureRelForTarget } from '../../../services'; + import { - EuiButtonContent, + EuiButtonContentDeprecated as EuiButtonContent, EuiButtonContentProps, EuiButtonContentType, -} from '../button_content'; +} from '../_button_content_deprecated'; + import { validateHref } from '../../../services/security/href_validator'; export type EuiButtonEmptyColor = diff --git a/src/components/button/button_group/button_group.tsx b/src/components/button/button_group/button_group.tsx index 132c2b378e0..6b10869705d 100644 --- a/src/components/button/button_group/button_group.tsx +++ b/src/components/button/button_group/button_group.tsx @@ -11,7 +11,7 @@ import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; import { EuiScreenReaderOnly } from '../../accessibility'; import { EuiButtonGroupButton } from './button_group_button'; import { colorToClassNameMap, ButtonColor } from '../button'; -import { EuiButtonContentProps } from '../button_content'; +import { EuiButtonContentProps } from '../_button_content_deprecated'; import { CommonProps } from '../../common'; import { useGeneratedHtmlId } from '../../../services'; diff --git a/src/components/button/button_group/button_group_button.tsx b/src/components/button/button_group/button_group_button.tsx index 422074ddd80..da61bf5752a 100644 --- a/src/components/button/button_group/button_group_button.tsx +++ b/src/components/button/button_group/button_group_button.tsx @@ -8,7 +8,7 @@ import classNames from 'classnames'; import React, { FunctionComponent } from 'react'; -import { EuiButtonDisplay } from '../button'; +import { EuiButtonDisplayDeprecated as EuiButtonDisplay } from '../button'; import { EuiButtonGroupOptionProps, EuiButtonGroupProps } from './button_group'; import { useInnerText } from '../../inner_text'; import { useGeneratedHtmlId } from '../../../services'; diff --git a/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap b/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap index 1e90bbc2294..c2ef2912792 100644 --- a/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap +++ b/src/components/button/button_icon/__snapshots__/button_icon.test.tsx.snap @@ -255,7 +255,7 @@ exports[`EuiButtonIcon props isLoading is rendered 1`] = ` > diff --git a/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap b/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap index d41b45eb7ed..c05dbff3287 100644 --- a/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap +++ b/src/components/control_bar/__snapshots__/control_bar.test.tsx.snap @@ -349,7 +349,7 @@ exports[`EuiControlBar props leftOffset is rendered 1`] = ` } type="button" > - - + @@ -691,7 +691,7 @@ exports[`EuiControlBar props maxHeight is rendered 1`] = ` } type="button" > - - + @@ -1032,7 +1032,7 @@ exports[`EuiControlBar props mobile is rendered 1`] = ` } type="button" > - - + @@ -1277,7 +1277,7 @@ exports[`EuiControlBar props position is rendered 1`] = ` } type="button" > - - + @@ -1603,7 +1603,7 @@ exports[`EuiControlBar props rightOffset is rendered 1`] = ` } type="button" > - - + @@ -1949,7 +1949,7 @@ exports[`EuiControlBar props showContent is rendered 1`] = ` } type="button" > - - + @@ -2295,7 +2295,7 @@ exports[`EuiControlBar props size is rendered 1`] = ` } type="button" > - - + diff --git a/src/components/facet/__snapshots__/facet_button.test.tsx.snap b/src/components/facet/__snapshots__/facet_button.test.tsx.snap index 9d6e5a809db..1e66f3b1ea5 100644 --- a/src/components/facet/__snapshots__/facet_button.test.tsx.snap +++ b/src/components/facet/__snapshots__/facet_button.test.tsx.snap @@ -3,16 +3,15 @@ exports[`EuiFacetButton is rendered 1`] = ` `; exports[`EuiFacetButton props isLoading is rendered 1`] = ` + + {buttonQuantity} + )} ); diff --git a/src/components/facet/facet_group.styles.ts b/src/components/facet/facet_group.styles.ts new file mode 100644 index 00000000000..3ec589c56ef --- /dev/null +++ b/src/components/facet/facet_group.styles.ts @@ -0,0 +1,72 @@ +/* + * 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, useEuiTheme } from '../../services'; +import { EuiFacetGroupLayout } from './facet_group'; + +const _facetGroupGutterSize = ({ + gutterSize, + layout, +}: { + gutterSize: string; + layout: EuiFacetGroupLayout; +}) => { + const { euiTheme } = useEuiTheme(); + const isHorizontalLayout = layout === 'horizontal'; + const gutterHorizontal = `calc(${euiTheme.size.m} + ${gutterSize})`; + const gutterVertical = gutterSize; + + return isHorizontalLayout + ? `gap: ${gutterVertical} ${gutterHorizontal};` + : `gap: ${gutterVertical} 0;`; +}; + +export const euiFacetGroupStyles = ( + { euiTheme }: UseEuiTheme, + layout: EuiFacetGroupLayout +) => ({ + // Base + euiFacetGroup: css` + display: flex; + flex-grow: 1; + `, + // Gutter sizes + none: css( + _facetGroupGutterSize({ + gutterSize: '0', + layout, + }) + ), + s: css( + _facetGroupGutterSize({ + gutterSize: euiTheme.size.xs, + layout, + }) + ), + m: css( + _facetGroupGutterSize({ + gutterSize: euiTheme.size.s, + layout, + }) + ), + l: css( + _facetGroupGutterSize({ + gutterSize: euiTheme.size.m, + layout, + }) + ), + // layouts + horizontal: css` + flex-direction: row; + flex-wrap: wrap; + `, + vertical: css` + flex-direction: column; + `, +}); diff --git a/src/components/facet/facet_group.test.tsx b/src/components/facet/facet_group.test.tsx index 7761cd0e493..831c41d8c35 100644 --- a/src/components/facet/facet_group.test.tsx +++ b/src/components/facet/facet_group.test.tsx @@ -11,8 +11,11 @@ import { render } from 'enzyme'; import { requiredProps } from '../../test'; import { EuiFacetGroup, LAYOUTS, GUTTER_SIZES } from './facet_group'; +import { shouldRenderCustomStyles } from '../../test/internal'; describe('EuiFacetGroup', () => { + shouldRenderCustomStyles(Content); + test('is rendered', () => { const component = render(); diff --git a/src/components/facet/facet_group.tsx b/src/components/facet/facet_group.tsx index 04ec5ded385..ba1b1052e5e 100644 --- a/src/components/facet/facet_group.tsx +++ b/src/components/facet/facet_group.tsx @@ -9,42 +9,28 @@ import React, { FunctionComponent, HTMLAttributes } from 'react'; import classNames from 'classnames'; -import { CommonProps, keysOf } from '../common'; -import { EuiFlexGroup } from '../flex'; +import { CommonProps } from '../common'; -type FacetGroupLayout = 'vertical' | 'horizontal'; +import { useEuiTheme } from '../../services'; +import { euiFacetGroupStyles } from './facet_group.styles'; -const layoutToClassNameMap: { [layout in FacetGroupLayout]: string } = { - vertical: 'euiFacetGroup--vertical', - horizontal: 'euiFacetGroup--horizontal', -}; - -export const LAYOUTS = keysOf(layoutToClassNameMap); +export const LAYOUTS = ['vertical', 'horizontal'] as const; +export type EuiFacetGroupLayout = typeof LAYOUTS[number]; -type FacetGroupGutterSize = 'none' | 's' | 'm' | 'l'; - -const gutterSizeToClassNameMap: { - [gutterSize in FacetGroupGutterSize]: string; -} = { - none: 'euiFacetGroup--gutterNone', - s: 'euiFacetGroup--gutterSmall', - m: 'euiFacetGroup--gutterMedium', - l: 'euiFacetGroup--gutterLarge', -}; - -export const GUTTER_SIZES = keysOf(gutterSizeToClassNameMap); +export const GUTTER_SIZES = ['none', 's', 'm', 'l'] as const; +export type EuiFacetGroupGutterSizes = typeof GUTTER_SIZES[number]; export type EuiFacetGroupProps = CommonProps & HTMLAttributes & { /** * Vertically in a column, or horizontally in one wrapping line */ - layout?: FacetGroupLayout; + layout?: EuiFacetGroupLayout; /** * Distance between facet buttons. * Horizontal layout always adds more distance horizontally between buttons. */ - gutterSize?: FacetGroupGutterSize; + gutterSize?: EuiFacetGroupGutterSizes; }; export const EuiFacetGroup: FunctionComponent = ({ @@ -54,24 +40,15 @@ export const EuiFacetGroup: FunctionComponent = ({ gutterSize = 'm', ...rest }) => { - const classes = classNames( - 'euiFacetGroup', - layoutToClassNameMap[layout], - gutterSizeToClassNameMap[gutterSize], - className - ); - const direction = layout === 'vertical' ? 'column' : 'row'; - const wrap = layout === 'vertical' ? false : true; + const theme = useEuiTheme(); + const styles = euiFacetGroupStyles(theme, layout); + const cssStyles = [styles.euiFacetGroup, styles[gutterSize], styles[layout]]; + + const classes = classNames('euiFacetGroup', className); return ( - +
{children} - +
); }; diff --git a/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap b/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap index 47c533df277..10846546b3a 100644 --- a/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap +++ b/src/components/form/field_password/__snapshots__/field_password.test.tsx.snap @@ -264,7 +264,7 @@ exports[`EuiFieldPassword props isLoading is rendered 1`] = ` >
diff --git a/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap b/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap index 30c77301ed4..21c110b8466 100644 --- a/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap +++ b/src/components/form/form_control_layout/__snapshots__/form_control_layout.test.tsx.snap @@ -75,7 +75,7 @@ exports[`EuiFormControlLayout props compressed renders small-sized icon, clear b @@ -245,7 +245,7 @@ exports[`EuiFormControlLayout props isLoading is rendered 1`] = ` > diff --git a/src/components/form/range/__snapshots__/dual_range.test.tsx.snap b/src/components/form/range/__snapshots__/dual_range.test.tsx.snap index ad76cb90ac9..04de6af7649 100644 --- a/src/components/form/range/__snapshots__/dual_range.test.tsx.snap +++ b/src/components/form/range/__snapshots__/dual_range.test.tsx.snap @@ -543,7 +543,7 @@ exports[`EuiDualRange props loading should display when showInput="inputWithPopo > diff --git a/src/components/form/range/__snapshots__/range.test.tsx.snap b/src/components/form/range/__snapshots__/range.test.tsx.snap index 402759a2267..c3066f25cda 100644 --- a/src/components/form/range/__snapshots__/range.test.tsx.snap +++ b/src/components/form/range/__snapshots__/range.test.tsx.snap @@ -343,7 +343,7 @@ exports[`EuiRange props loading should display when showInput="inputWithPopover" > diff --git a/src/components/form/super_select/__snapshots__/super_select_control.test.tsx.snap b/src/components/form/super_select/__snapshots__/super_select_control.test.tsx.snap index 3b7b5621e84..9bf28fa9307 100644 --- a/src/components/form/super_select/__snapshots__/super_select_control.test.tsx.snap +++ b/src/components/form/super_select/__snapshots__/super_select_control.test.tsx.snap @@ -273,7 +273,7 @@ Array [ > @@ -12,7 +12,7 @@ exports[`EuiLoadingSpinner is rendered 1`] = ` exports[`EuiLoadingSpinner size l is rendered 1`] = ` `; @@ -20,7 +20,7 @@ exports[`EuiLoadingSpinner size l is rendered 1`] = ` exports[`EuiLoadingSpinner size m is rendered 1`] = ` `; @@ -28,7 +28,7 @@ exports[`EuiLoadingSpinner size m is rendered 1`] = ` exports[`EuiLoadingSpinner size s is rendered 1`] = ` `; @@ -36,7 +36,7 @@ exports[`EuiLoadingSpinner size s is rendered 1`] = ` exports[`EuiLoadingSpinner size xl is rendered 1`] = ` `; @@ -44,7 +44,7 @@ exports[`EuiLoadingSpinner size xl is rendered 1`] = ` exports[`EuiLoadingSpinner size xxl is rendered 1`] = ` `; diff --git a/src/components/loading/loading_spinner.styles.ts b/src/components/loading/loading_spinner.styles.ts index a1f0f97abf8..95c064f0878 100644 --- a/src/components/loading/loading_spinner.styles.ts +++ b/src/components/loading/loading_spinner.styles.ts @@ -5,11 +5,13 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import { css, keyframes } from '@emotion/react'; import { _EuiThemeSize, euiCanAnimate } from '../../global_styling'; import { UseEuiTheme } from '../../services'; -import { EuiLoadingSpinnerSize } from './loading_spinner'; +import { + EuiLoadingSpinnerSize, + EuiLoadingSpinnerProps, +} from './loading_spinner'; const _loadingSpinner = keyframes` from { @@ -31,20 +33,25 @@ const spinnerSizes: { xxl: 'xxl', }; -const spinnerColors = (main: string, highlight: string) => { - return `${highlight} ${main} ${main} ${main}`; +const spinnerColorsCSS = (border?: string, highlight?: string) => { + return ` + border-color: ${highlight} ${border} ${border} ${border}; + `; }; -export const euiLoadingSpinnerStyles = ({ euiTheme }: UseEuiTheme) => { +export const euiLoadingSpinnerStyles = ( + { euiTheme }: UseEuiTheme, + color?: EuiLoadingSpinnerProps['color'] +) => { return { euiLoadingSpinner: css` flex-shrink: 0; // Ensures it never scales down below its intended size display: inline-block; border-radius: 50%; border: ${euiTheme.border.thick}; - border-color: ${spinnerColors( - euiTheme.border.color, - euiTheme.colors.primary + ${spinnerColorsCSS( + color?.border || euiTheme.colors.lightShade, + color?.highlight || euiTheme.colors.primary )}; ${euiCanAnimate} { diff --git a/src/components/loading/loading_spinner.tsx b/src/components/loading/loading_spinner.tsx index 90e184c2bde..44445370c47 100644 --- a/src/components/loading/loading_spinner.tsx +++ b/src/components/loading/loading_spinner.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { HTMLAttributes, FunctionComponent } from 'react'; +import React, { HTMLAttributes, FunctionComponent, CSSProperties } from 'react'; import { CommonProps } from '../common'; import classNames from 'classnames'; import { useEuiTheme } from '../..//services'; @@ -16,19 +16,31 @@ import { euiLoadingSpinnerStyles } from './loading_spinner.styles'; export const SIZES = ['s', 'm', 'l', 'xl', 'xxl'] as const; export type EuiLoadingSpinnerSize = typeof SIZES[number]; +export type EuiLoadingSpinnerColor = { + border?: CSSProperties['color']; + highlight?: CSSProperties['color']; +}; + export type EuiLoadingSpinnerProps = CommonProps & - HTMLAttributes & { + Omit, 'color'> & { size?: EuiLoadingSpinnerSize; + /** + * Sets the color of the border and highlight. + * Each key accepts any valid CSS color value as a `string` + * See #EuiLoadingSpinnerColor + */ + color?: EuiLoadingSpinnerColor; }; export const EuiLoadingSpinner: FunctionComponent = ({ size = 'm', className, 'aria-label': ariaLabel, + color, ...rest }) => { const euiTheme = useEuiTheme(); - const styles = euiLoadingSpinnerStyles(euiTheme); + const styles = euiLoadingSpinnerStyles(euiTheme, color); const cssStyles = [styles.euiLoadingSpinner, styles[size]]; const classes = classNames('euiLoadingSpinner', className); const defaultLabel = useLoadingAriaLabel(); diff --git a/src/components/steps/__snapshots__/step.test.tsx.snap b/src/components/steps/__snapshots__/step.test.tsx.snap index a9ba3129940..4b0e0533823 100644 --- a/src/components/steps/__snapshots__/step.test.tsx.snap +++ b/src/components/steps/__snapshots__/step.test.tsx.snap @@ -275,7 +275,7 @@ exports[`EuiStep props status loading is rendered 1`] = ` diff --git a/src/components/steps/__snapshots__/step_horizontal.test.tsx.snap b/src/components/steps/__snapshots__/step_horizontal.test.tsx.snap index 8146fa4ca32..4118f833165 100644 --- a/src/components/steps/__snapshots__/step_horizontal.test.tsx.snap +++ b/src/components/steps/__snapshots__/step_horizontal.test.tsx.snap @@ -190,7 +190,7 @@ exports[`EuiStepHorizontal props status loading is rendered 1`] = ` diff --git a/src/components/steps/__snapshots__/step_number.test.tsx.snap b/src/components/steps/__snapshots__/step_number.test.tsx.snap index 85fd3dab440..e6fe291e444 100644 --- a/src/components/steps/__snapshots__/step_number.test.tsx.snap +++ b/src/components/steps/__snapshots__/step_number.test.tsx.snap @@ -127,7 +127,7 @@ exports[`EuiStepNumber props status loading is rendered 1`] = ` diff --git a/src/components/suggest/__snapshots__/suggest.test.tsx.snap b/src/components/suggest/__snapshots__/suggest.test.tsx.snap index a5609a3d625..0376d778c2c 100644 --- a/src/components/suggest/__snapshots__/suggest.test.tsx.snap +++ b/src/components/suggest/__snapshots__/suggest.test.tsx.snap @@ -1022,7 +1022,7 @@ Array [ > @@ -1277,7 +1277,7 @@ Array [ > diff --git a/src/test/internal/render_custom_styles.tsx b/src/test/internal/render_custom_styles.tsx index a823067b207..2bd9c3991aa 100644 --- a/src/test/internal/render_custom_styles.tsx +++ b/src/test/internal/render_custom_styles.tsx @@ -37,7 +37,7 @@ export const shouldRenderCustomStyles = (component: ReactElement) => { expect(componentNode).toHaveLength(1); // css expect(componentNode.attr('class')).toEqual( - expect.stringMatching(/css-[\d\w]{6,7}-css/) // should have generated an emotion class ending with -css + expect.stringMatching(/css-[\d\w-]{6,}-css/) // should have generated an emotion class ending with -css ); // style expect(componentNode.attr('style')).toContain("content:'world'"); diff --git a/src/themes/amsterdam/overrides/_facet.scss b/src/themes/amsterdam/overrides/_facet.scss deleted file mode 100644 index d854667d1a5..00000000000 --- a/src/themes/amsterdam/overrides/_facet.scss +++ /dev/null @@ -1,10 +0,0 @@ -.euiFacetButton { - &:focus { - background-color: transparent; - box-shadow: none; - - &:not(:disabled) .euiFacetButton__text { - @include euiLinkFocus; - } - } -} diff --git a/src/themes/amsterdam/overrides/_index.scss b/src/themes/amsterdam/overrides/_index.scss index bece407f677..830da42b470 100644 --- a/src/themes/amsterdam/overrides/_index.scss +++ b/src/themes/amsterdam/overrides/_index.scss @@ -11,7 +11,6 @@ @import 'date_picker'; @import 'date_popover_button'; @import 'description_list'; -@import 'facet'; @import 'filter_group'; @import 'form_control_layout'; @import 'form_control_layout_delimited'; diff --git a/upcoming_changelogs/5878.md b/upcoming_changelogs/5878.md new file mode 100644 index 00000000000..2d9b53e4ed5 --- /dev/null +++ b/upcoming_changelogs/5878.md @@ -0,0 +1,6 @@ + +- Added new `color` prop to `EuiLoadingSpinner` + +**CSS-in-JS** + +- Converted `EuiFacetGroup` and `EuiFacetButton` to Emotion and removed `$euiFacetGutterSizes` \ No newline at end of file