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`] = `
-
-`;
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`] = `
+
+`;
+
+exports[`EuiStat props accent is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props center is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props danger is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props default is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props l is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props left is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props loading is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props m is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props primary is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props right is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props s is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props secondary is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props subdued is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props title and description are reversed 1`] = `
+
+`;
+
+exports[`EuiStat props xs is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props xxs is rendered 1`] = `
+
+`;
+
+exports[`EuiStat props xxxs is rendered 1`] = `
+
+`;
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}
+
+ );
+};