Skip to content

Commit 7e0013e

Browse files
authored
<StatusIndicator/> - New component (#5024)
1 parent cc075a0 commit 7e0013e

25 files changed

+416
-0
lines changed

.wuf/components.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,5 +460,8 @@
460460
},
461461
"CustomModal": {
462462
"path": "src/CustomModal"
463+
},
464+
"StatusIndicator": {
465+
"path": "src/StatusIndicator"
463466
}
464467
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import styles from './StatusIndicator.st.css';
5+
import Tooltip from '../Tooltip/TooltipNext';
6+
import FormFieldWarningFilled from 'wix-ui-icons-common/system/FormFieldWarningFilled';
7+
import FormFieldErrorFilled from 'wix-ui-icons-common/system/FormFieldErrorFilled';
8+
import Loader from '../Loader';
9+
import { dataHooks, STATUS, TOOLTIP_PLACEMENT } from './constants';
10+
11+
/** StatusIndicator */
12+
class StatusIndicator extends React.PureComponent {
13+
_renderStatus = () => {
14+
switch (this.props.status) {
15+
case STATUS.WARNING:
16+
return <FormFieldWarningFilled />;
17+
case STATUS.LOADING:
18+
return <Loader size="tiny" />;
19+
case STATUS.ERROR:
20+
default:
21+
return <FormFieldErrorFilled />;
22+
}
23+
};
24+
25+
render() {
26+
const { dataHook, status, message, tooltipPlacement } = this.props;
27+
28+
return (
29+
<div {...styles('root', { status }, this.props)} data-hook={dataHook}>
30+
{message ? (
31+
<Tooltip
32+
dataHook={dataHooks.tooltip}
33+
appendTo="window"
34+
placement={tooltipPlacement}
35+
exitDelay={100}
36+
content={message}
37+
maxWidth={250}
38+
>
39+
{this._renderStatus()}
40+
</Tooltip>
41+
) : (
42+
this._renderStatus()
43+
)}
44+
</div>
45+
);
46+
}
47+
}
48+
49+
StatusIndicator.displayName = 'StatusIndicator';
50+
51+
StatusIndicator.propTypes = {
52+
/** Applied as data-hook HTML attribute that can be used in the tests */
53+
dataHook: PropTypes.string,
54+
55+
/** A css class to be applied to the component's root element */
56+
className: PropTypes.string,
57+
58+
/** The indication type */
59+
status: PropTypes.oneOf(['error', 'warning', 'loading']),
60+
61+
/** A tooltip message */
62+
message: PropTypes.string,
63+
64+
/** The tooltip Placement */
65+
tooltipPlacement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
66+
};
67+
68+
StatusIndicator.defaultProps = {
69+
status: 'error',
70+
tooltipPlacement: 'top',
71+
};
72+
73+
export default StatusIndicator;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import StatusIndicator from './StatusIndicator';
2+
import Registry from '@ui-autotools/registry';
3+
4+
const metadata = Registry.getComponentMetadata(StatusIndicator);
5+
6+
metadata.exportedFrom({
7+
path: 'src/StatusIndicator/StatusIndicator.js',
8+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
:import {
2+
-st-from: '../Foundation/stylable/colors.st.css';
3+
-st-named: R10, Y10;
4+
}
5+
6+
.root {
7+
-st-states: status(enum(error, warning));
8+
9+
width: 18px;
10+
height: 18px;
11+
12+
/* Error is the default */
13+
color: value(R10);
14+
}
15+
16+
.root:status(warning) {
17+
color: value(Y10);
18+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { BaseUniDriver } from 'wix-ui-test-utils/unidriver';
2+
3+
export interface StatusIndicatorDriver extends BaseUniDriver {
4+
hasTooltip(): Promise<boolean>;
5+
getTooltipText(): Promise<string>;
6+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { baseUniDriverFactory } from 'wix-ui-test-utils/base-driver';
2+
import { tooltipDriverFactory } from '../Tooltip/TooltipNext/Tooltip.uni.driver';
3+
import { dataHooks } from './constants';
4+
5+
export const statusIndicatorDriverFactory = (base, body) => {
6+
return {
7+
...baseUniDriverFactory(base, body),
8+
9+
hasTooltip: async () => {
10+
const tooltipDriver = tooltipDriverFactory(
11+
base.$(`[data-hook="${dataHooks.tooltip}"]`),
12+
body,
13+
);
14+
return await tooltipDriver.exists();
15+
},
16+
17+
getTooltipText: async () => {
18+
const tooltipDriver = tooltipDriverFactory(
19+
base.$(`[data-hook="${dataHooks.tooltip}"]`),
20+
body,
21+
);
22+
23+
if (await tooltipDriver.exists()) {
24+
await tooltipDriver.mouseEnter();
25+
return await tooltipDriver.getTooltipText();
26+
} else {
27+
throw new Error(`Tooltip doesn't exist`);
28+
}
29+
},
30+
};
31+
};

src/StatusIndicator/constants.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const dataHooks = {
2+
tooltip: 'status-indicator-tooltip',
3+
};
4+
5+
export const STATUS = {
6+
ERROR: 'error',
7+
WARNING: 'warning',
8+
LOADING: 'loading',
9+
};
10+
11+
export const TOOLTIP_PLACEMENT = {
12+
TOP: 'top',
13+
RIGHT: 'right',
14+
BOTTOM: 'bottom',
15+
LEFT: 'left',
16+
};
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import React from 'react';
2+
import {
3+
header,
4+
tabs,
5+
tab,
6+
description,
7+
importExample,
8+
title,
9+
columns,
10+
divider,
11+
code as baseCode,
12+
playground,
13+
api,
14+
testkit,
15+
} from 'wix-storybook-utils/Sections';
16+
17+
import { storySettings } from '../test/storySettings';
18+
import allComponents from '../../../stories/utils/allComponents';
19+
import StatusIndicator from '..';
20+
import { STATUS, TOOLTIP_PLACEMENT } from '../constants';
21+
22+
const code = config => baseCode({ components: allComponents, ...config });
23+
24+
export default {
25+
category: storySettings.category,
26+
storyName: 'StatusIndicator',
27+
28+
component: StatusIndicator,
29+
componentPath: '..',
30+
31+
componentProps: {
32+
message: 'Tooltip',
33+
status: 'error',
34+
tooltipPlacement: 'top',
35+
},
36+
37+
exampleProps: {
38+
status: Object.values(STATUS).map(status => ({
39+
label: status,
40+
value: status,
41+
})),
42+
tooltipPlacement: Object.values(TOOLTIP_PLACEMENT).map(placement => ({
43+
label: placement,
44+
value: placement,
45+
})),
46+
},
47+
48+
sections: [
49+
header({
50+
sourceUrl:
51+
'https://github.com/wix/wix-style-react/tree/master/src/StatusIndicator/',
52+
component: (
53+
<div
54+
style={{
55+
display: 'grid',
56+
gridGap: '10px',
57+
gridAutoFlow: 'column',
58+
width: 'fit-content',
59+
}}
60+
>
61+
<StatusIndicator status="error" />
62+
<StatusIndicator status="warning" />
63+
<StatusIndicator status="loading" />
64+
</div>
65+
),
66+
}),
67+
68+
tabs([
69+
tab({
70+
title: 'Description',
71+
sections: [
72+
columns([
73+
description({
74+
title: 'Description',
75+
text: 'An icon with tooltip indicating a certain status.',
76+
}),
77+
]),
78+
79+
columns([
80+
importExample(
81+
"import StatusIndicator from 'wix-style-react/StatusIndicator';",
82+
),
83+
]),
84+
85+
divider(),
86+
87+
title('Examples'),
88+
89+
columns([
90+
description({
91+
title: 'Error',
92+
}),
93+
94+
code({
95+
compact: true,
96+
source: `<StatusIndicator status="error" message="Error Message"/>`,
97+
}),
98+
]),
99+
100+
columns([
101+
description({
102+
title: 'Warning',
103+
}),
104+
105+
code({
106+
compact: true,
107+
source: `<StatusIndicator status="warning" message="Warning Message"/>`,
108+
}),
109+
]),
110+
111+
columns([
112+
description({
113+
title: 'Loading',
114+
}),
115+
116+
code({
117+
compact: true,
118+
source: `<StatusIndicator status="loading" message="Loading Message"/>`,
119+
}),
120+
]),
121+
122+
columns([
123+
description({
124+
title: 'Tooltip Placement',
125+
text: `The component's tooltip placement can be one of the following:`,
126+
}),
127+
128+
code({
129+
compact: true,
130+
source: `
131+
<Layout cols={4}>
132+
<Cell span={1}>
133+
<Box marginBottom={1}>top:</Box>
134+
<StatusIndicator status="error" message="top" tooltipPlacement="top" />
135+
</Cell>
136+
<Cell span={1}>
137+
<Box marginBottom={1}>left:</Box>
138+
<StatusIndicator status="error" message="left" tooltipPlacement="left" />
139+
</Cell>
140+
<Cell span={1}>
141+
<Box marginBottom={1}>bottom:</Box>
142+
<StatusIndicator status="error" message="bottom" tooltipPlacement="bottom" />
143+
</Cell>
144+
<Cell span={1}>
145+
<Box marginBottom={1}>right:</Box>
146+
<StatusIndicator status="error" message="right" tooltipPlacement="right" />
147+
</Cell>
148+
</Layout>
149+
`,
150+
}),
151+
]),
152+
],
153+
}),
154+
155+
...[
156+
{ title: 'API', sections: [api()] },
157+
{ title: 'Testkit', sections: [testkit()] },
158+
{ title: 'Playground', sections: [playground()] },
159+
].map(tab),
160+
]),
161+
],
162+
};

src/StatusIndicator/index.d.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as React from 'react';
2+
3+
export type StatusIndicatorState = 'error' | 'warning' | 'loading';
4+
export type StatusIndicatorTooltipPlacement =
5+
| 'top'
6+
| 'right'
7+
| 'bottom'
8+
| 'left';
9+
10+
export interface StatusIndicatorProps {
11+
dataHook?: string;
12+
className?: string;
13+
status?: StatusIndicatorState;
14+
message?: string;
15+
tooltipPlacement?: StatusIndicatorTooltipPlacement;
16+
}
17+
18+
export default class StatusIndicator extends React.PureComponent<
19+
StatusIndicatorProps
20+
> {}

src/StatusIndicator/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './StatusIndicator.js';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { statusIndicatorDriverFactory as publicDriverFactory } from '../StatusIndicator.uni.driver';
2+
3+
export const statusIndicatorPrivateDriverFactory = (base, body) => {
4+
return {
5+
...publicDriverFactory(base, body),
6+
};
7+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import { createRendererWithUniDriver, cleanup } from '../../../test/utils/unit';
3+
4+
import StatusIndicator from '../StatusIndicator';
5+
import { statusIndicatorPrivateDriverFactory } from './StatusIndicator.private.uni.driver';
6+
7+
describe('StatusIndicator', () => {
8+
const render = createRendererWithUniDriver(
9+
statusIndicatorPrivateDriverFactory,
10+
);
11+
12+
afterEach(() => {
13+
cleanup();
14+
});
15+
16+
it('should render', async () => {
17+
const { driver } = render(<StatusIndicator />);
18+
19+
expect(await driver.exists()).toBe(true);
20+
});
21+
22+
it('with no tooltip', async () => {
23+
const { driver } = render(<StatusIndicator />);
24+
25+
expect(await driver.hasTooltip()).toBe(false);
26+
});
27+
28+
it('with tooltip', async () => {
29+
const message = 'Hello World';
30+
const { driver } = render(<StatusIndicator message={message} />);
31+
32+
expect(await driver.hasTooltip()).toBe(true);
33+
expect(await driver.getTooltipText()).toBe(message);
34+
});
35+
});

0 commit comments

Comments
 (0)