diff --git a/CHANGELOG.md b/CHANGELOG.md index e678eb4414d..85f2c56acd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Converted `EuiStat` to TS ([#1848](https://github.com/elastic/eui/pull/1848)) +- Added `isLoading` prop to `EuiStat` ([#1848](https://github.com/elastic/eui/pull/1848)) + **Bug fixes** - Fixed `EuiComboBox` to not pass its `inputRef` prop down to the DOM ([#1867](https://github.com/elastic/eui/pull/1867)) +- Fixed `euiBreakpoint()` warning to give accurate feedback ([#1874](https://github.com/elastic/eui/pull/1874)) +- Fixed type definitions around `EuiI18n`'s `default` prop to better support use cases ([#1861](https://github.com/elastic/eui/pull/1861)) ## [`10.1.0`](https://github.com/elastic/eui/tree/v10.1.0) diff --git a/src-docs/src/views/stat/stat.js b/src-docs/src/views/stat/stat.js index 148092c4174..4e177addfbe 100644 --- a/src-docs/src/views/stat/stat.js +++ b/src-docs/src/views/stat/stat.js @@ -12,7 +12,7 @@ export default () => ( diff --git a/src-docs/src/views/stat/stat_combos.js b/src-docs/src/views/stat/stat_combos.js index 1d02e59d5ba..76e2acd13d1 100644 --- a/src-docs/src/views/stat/stat_combos.js +++ b/src-docs/src/views/stat/stat_combos.js @@ -1,4 +1,6 @@ -import React from 'react'; +import React, { + Component, +} from 'react'; import { EuiStat, @@ -6,58 +8,86 @@ import { EuiFlexGroup, EuiPanel, EuiIcon, + EuiSwitch, + EuiSpacer, } from '../../../../src/components'; -export default () => ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-); +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + isLoading: false, + }; + } + + onToggleChange = (e) => { + this.setState({ isLoading: e.target.checked }); + } + + render() { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); + } +} diff --git a/src-docs/src/views/stat/stat_example.js b/src-docs/src/views/stat/stat_example.js index 4809a73e72c..dbdbd4046e1 100644 --- a/src-docs/src/views/stat/stat_example.js +++ b/src-docs/src/views/stat/stat_example.js @@ -14,27 +14,66 @@ import { import Stat from './stat'; const statSource = require('!!raw-loader!./stat'); const statHtml = renderToHtml(Stat); +const statSnippet = ` +`; import StatColors from './stat_colors'; const statColorsSource = require('!!raw-loader!./stat_colors'); const statColorsHtml = renderToHtml(StatColors); +const statColorSnippet = ` +`; import StatAlign from './stat_align'; const statAlignSource = require('!!raw-loader!./stat_align'); const statAlignHtml = renderToHtml(StatAlign); +const statAlignSnippet = ` +`; import StatSize from './stat_size'; const statSizeSource = require('!!raw-loader!./stat_size'); const statSizeHtml = renderToHtml(StatSize); +const statSizeSnippet = ` +`; import StatOrder from './stat_order'; const statOrderSource = require('!!raw-loader!./stat_order'); const statOrderHtml = renderToHtml(StatOrder); +const statOrderSnippet = ` +`; import StatCombos from './stat_combos'; const statCombosSource = require('!!raw-loader!./stat_combos'); const statCombosHtml = renderToHtml(StatCombos); +import StatLoading from './stat_loading'; +const statLoadingSource = require('!!raw-loader!./stat_loading'); +const statLoadingHtml = renderToHtml(StatLoading); +const statLoadingSnippet = ` +`; + export const StatExample = { title: 'Stat', sections: [{ @@ -53,6 +92,7 @@ export const StatExample = { ), props: { EuiStat }, demo: , + snippet: statSnippet, }, { title: 'Applying color', source: [{ @@ -68,6 +108,7 @@ export const StatExample = { For proper color contrast, only a limited set of EUI colors are offered. See the Props tab above for a list of available colors.

), + snippet: statColorSnippet, demo: , }, { title: 'Text alignment', @@ -83,6 +124,7 @@ export const StatExample = { EuiStat also offers alignment options. By default, text will be left aligned.

), + snippet: statAlignSnippet, demo: , }, { title: 'Title size', @@ -101,6 +143,7 @@ export const StatExample = { component properties.

), + snippet: statSizeSnippet, demo: , }, { title: 'Reverse the order', @@ -117,7 +160,25 @@ export const StatExample = { the reverse property to true. By default, the description (label) is displayed above the title (value).

), + snippet: statOrderSnippet, demo: , + }, { + title: 'Stat loading', + source: [{ + type: GuideSectionTypes.JS, + code: statLoadingSource, + }, { + type: GuideSectionTypes.HTML, + code: statLoadingHtml, + }], + text: ( +

+ If you apply the isLoading prop, the title will indicate the loading status by + swapping the provided title with two flashing dashes. +

+ ), + snippet: statLoadingSnippet, + demo: , }, { title: 'Putting it all together', source: [{ diff --git a/src-docs/src/views/stat/stat_loading.js b/src-docs/src/views/stat/stat_loading.js new file mode 100644 index 00000000000..b15089a3ff7 --- /dev/null +++ b/src-docs/src/views/stat/stat_loading.js @@ -0,0 +1,41 @@ +import React, { + Component, +} from 'react'; + +import { + EuiSwitch, + EuiStat, + EuiSpacer, +} from '../../../../src/components'; + +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + isLoading: true, + }; + } + + onToggleChange = (e) => { + this.setState({ isLoading: e.target.checked }); + } + + render() { + return ( +
+ + + +
+ ); + } +} \ No newline at end of file diff --git a/src/components/stat/__snapshots__/stat.test.js.snap b/src/components/stat/__snapshots__/stat.test.js.snap deleted file mode 100644 index c9caea14df2..00000000000 --- a/src/components/stat/__snapshots__/stat.test.js.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EuiStat is rendered 1`] = ` -
-
-

- description -

-
-

- title -

-
-`; diff --git a/src/components/stat/__snapshots__/stat.test.tsx.snap b/src/components/stat/__snapshots__/stat.test.tsx.snap new file mode 100644 index 00000000000..e8c56233ea5 --- /dev/null +++ b/src/components/stat/__snapshots__/stat.test.tsx.snap @@ -0,0 +1,405 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiStat is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props accent is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props center is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props danger is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props default is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props l is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props left is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props loading is rendered 1`] = ` +
+
+

+ description +

+
+

+

+ -- +

+

+

+`; + +exports[`EuiStat props m is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props primary is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props right is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props s is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props secondary is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props subdued is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props title and description are reversed 1`] = ` +
+

+ title +

+
+

+ description +

+
+
+`; + +exports[`EuiStat props xs is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props xxs is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; + +exports[`EuiStat props xxxs is rendered 1`] = ` +
+
+

+ description +

+
+

+ title +

+
+`; diff --git a/src/components/stat/_stat.scss b/src/components/stat/_stat.scss index 29fef83b1f7..c311e2d8851 100644 --- a/src/components/stat/_stat.scss +++ b/src/components/stat/_stat.scss @@ -19,6 +19,10 @@ } } + .euiStat__title-isLoading { + animation: euiStatPulse 1.5s infinite ease-in-out; + } + .euiStat__description { color: map-get($titleColors, dark); } @@ -38,3 +42,9 @@ align-items: flex-end; } } + +@keyframes euiStatPulse { + 0% { opacity: 1; } + 50% { opacity: .25; } + 100% { opacity: 1; } +} \ No newline at end of file diff --git a/src/components/stat/index.js b/src/components/stat/index.js deleted file mode 100644 index 4b25452538d..00000000000 --- a/src/components/stat/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { - EuiStat, -} from './stat'; diff --git a/src/components/stat/index.ts b/src/components/stat/index.ts new file mode 100644 index 00000000000..46ccf1619d4 --- /dev/null +++ b/src/components/stat/index.ts @@ -0,0 +1 @@ +export { EuiStat } from './stat'; diff --git a/src/components/stat/stat.js b/src/components/stat/stat.js deleted file mode 100644 index eb84d6f9017..00000000000 --- a/src/components/stat/stat.js +++ /dev/null @@ -1,138 +0,0 @@ -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -import { EuiText } from '../text'; -import { EuiTitle, TITLE_SIZES } from '../title/title'; - -const colorToClassNameMap = { - default: null, - subdued: 'euiStat__title--subdued', - primary: 'euiStat__title--primary', - secondary: 'euiStat__title--secondary', - danger: 'euiStat__title--danger', - accent: 'euiStat__title--accent', -}; - -export const COLORS = Object.keys(colorToClassNameMap); - -const textAlignToClassNameMap = { - left: 'euiStat--leftAligned', - center: 'euiStat--centerAligned', - right: 'euiStat--rightAligned', -}; - -export const ALIGNMENTS = Object.keys(textAlignToClassNameMap); - -export const EuiStat = ({ - children, - className, - description, - title, - titleSize, - titleColor, - textAlign, - reverse, - ...rest, -}) => { - - const classes = classNames( - 'euiStat', - textAlignToClassNameMap[textAlign], - className, - ); - - const titleClasses = classNames( - 'euiStat__title', - colorToClassNameMap[titleColor], - ); - - const descriptionDisplay = ( - -

{description}

-
- ); - - const titleDisplay = ( - -

{title}

-
- ); - - let statDisplay; - - if (reverse) { - statDisplay = ( - - {titleDisplay} - {descriptionDisplay} - - ); - } else { - statDisplay = ( - - {descriptionDisplay} - {titleDisplay} - - ); - } - - return ( -
- {statDisplay} - {children} -
- ); -}; - -EuiStat.propTypes = { - /** - * Set the title (value) text - */ - title: PropTypes.node.isRequired, - - /** - * Set the description (label) text - */ - description: PropTypes.node.isRequired, - - /** - * Places the title (value) above the description (label) - */ - reverse: PropTypes.bool.isRequired, - - /** - * Define the size of the title text. See EuiTitle for sizing options ('s', 'm', 'l'... etc) - */ - titleSize: PropTypes.oneOf(TITLE_SIZES), - - /** - * Define the color of the title text - */ - titleColor: PropTypes.oneOf(COLORS), - - /** - * Define how you want the content aligned - */ - textAlign: PropTypes.oneOf(ALIGNMENTS), - - /** - * Appends additional classes to parent - */ - className: PropTypes.string, - - /** - * Additional content that appears after the title and description - */ - children: PropTypes.node, -}; - -EuiStat.defaultProps = { - titleColor: 'default', - textAlign: 'left', - titleSize: 'l', - reverse: false, -}; diff --git a/src/components/stat/stat.test.js b/src/components/stat/stat.test.js deleted file mode 100644 index 7970c38c511..00000000000 --- a/src/components/stat/stat.test.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { render } from 'enzyme'; -import { requiredProps } from '../../test'; - -import { EuiStat } from './stat'; - -describe('EuiStat', () => { - test('is rendered', () => { - const component = render( - - ); - - expect(component) - .toMatchSnapshot(); - }); -}); diff --git a/src/components/stat/stat.test.tsx b/src/components/stat/stat.test.tsx new file mode 100644 index 00000000000..34fa54e9010 --- /dev/null +++ b/src/components/stat/stat.test.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test'; + +import { EuiStat, COLORS, ALIGNMENTS } from './stat'; +import { TITLE_SIZES } from '../title/title'; + +jest.mock(`./../form/form_row/make_id`, () => () => `generated-id`); + +describe('EuiStat', () => { + test('is rendered', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + describe('props', () => { + test('loading is rendered', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('title and description are reversed', () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + + ALIGNMENTS.forEach(alignment => { + test(`${alignment} is rendered`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + COLORS.forEach(color => { + test(`${color} is rendered`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + TITLE_SIZES.forEach(size => { + test(`${size} is rendered`, () => { + const component = render( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/src/components/stat/stat.tsx b/src/components/stat/stat.tsx new file mode 100644 index 00000000000..1190d67afa2 --- /dev/null +++ b/src/components/stat/stat.tsx @@ -0,0 +1,137 @@ +import React, { Fragment, HTMLAttributes, FunctionComponent } from 'react'; +import { CommonProps, keysOf } from '../common'; +import classNames from 'classnames'; + +import { EuiText } from '../text'; +import { EuiTitle, EuiTitleSize } from '../title/title'; +import { EuiI18n } from '../i18n'; +import makeId from '../form/form_row/make_id'; + +const colorToClassNameMap = { + default: null, + subdued: 'euiStat__title--subdued', + primary: 'euiStat__title--primary', + secondary: 'euiStat__title--secondary', + danger: 'euiStat__title--danger', + accent: 'euiStat__title--accent', +}; + +export const COLORS = keysOf(colorToClassNameMap); + +const textAlignToClassNameMap = { + left: 'euiStat--leftAligned', + center: 'euiStat--centerAligned', + right: 'euiStat--rightAligned', +}; + +export const ALIGNMENTS = keysOf(textAlignToClassNameMap); + +export interface EuiStatProps { + /** + * Set the description (label) text + */ + description: string; + /** + * Will hide the title with an animation until false + */ + isLoading?: boolean; + /** + * Flips the order of the description and title + */ + reverse?: boolean; + textAlign?: keyof typeof textAlignToClassNameMap; + /** + * The (value) text + */ + title: string; + /** + * The color of the title text + */ + titleColor?: keyof typeof colorToClassNameMap; + /** + * Size of the title. See EuiTitle for options ('s', 'm', 'l'... etc) + */ + titleSize?: EuiTitleSize; +} + +export const EuiStat: FunctionComponent< + CommonProps & HTMLAttributes & EuiStatProps +> = ({ + children, + className, + description, + isLoading = false, + reverse = false, + textAlign = 'left', + title, + titleColor = 'default', + titleSize = 'l', + ...rest +}) => { + const classes = classNames( + 'euiStat', + textAlignToClassNameMap[textAlign], + className + ); + + const ariaId = makeId(); + + const titleClasses = classNames( + 'euiStat__title', + colorToClassNameMap[titleColor], + { + 'euiStat__title-isLoading': isLoading, + } + ); + + const descriptionDisplay = ( + +

{description}

+
+ ); + + let titleText; + if (isLoading) { + titleText = ( + // EuiI18n has trouble with the string setting + // @ts-ignore + + {/* // @ts-ignore */} + {(loadingText: string) =>

--

} +
+ ); + } else { + titleText = title; + } + + const titleDisplay = ( + +

{titleText}

+
+ ); + + let statDisplay; + + if (reverse) { + statDisplay = ( + + {titleDisplay} + {descriptionDisplay} + + ); + } else { + statDisplay = ( + + {descriptionDisplay} + {titleDisplay} + + ); + } + + return ( +
+ {statDisplay} + {children} +
+ ); +};