Skip to content

Commit f105990

Browse files
authored
feat: ObjectStatus new component (#1118)
* ObjectStatus component:Initial commit * Updated tests * Minor fixes * Updated test * Accessibility fixes and updated stories * Fixed test issues * Handling keypress event * Updated storyshot
1 parent 0e09ce0 commit f105990

File tree

7 files changed

+812
-0
lines changed

7 files changed

+812
-0
lines changed

src/ObjectStatus/ObjectStatus.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import classnames from 'classnames';
2+
import CustomPropTypes from '../utils/CustomPropTypes/CustomPropTypes';
3+
import PropTypes from 'prop-types';
4+
import React from 'react';
5+
import { OBJECT_STATUS_SIZES, OBJECT_STATUS_TYPES } from '../utils/constants';
6+
import 'fundamental-styles/dist/icon.css';
7+
import 'fundamental-styles/dist/object-status.css';
8+
9+
/** *Object Status* is a short text that represents the semantic status of an object. It has a semantic color and an optional icon.
10+
* Typically, the object status is used in the dynamic page header and as a status attribute of a line item in a table. */
11+
12+
const ObjectStatus = React.forwardRef(({ ariaLabel, children, className, glyph, indication, inverted, link, onClick, size, status, ...props }, ref) => {
13+
const objectStatusClasses = classnames(
14+
'fd-object-status',
15+
{
16+
[`sap-icon--${glyph}`]: !!glyph,
17+
[`fd-object-status--indication-${indication}`]: !!indication,
18+
'fd-object-status--inverted': inverted,
19+
'fd-object-status--large': size === 'l',
20+
[`fd-object-status--${status}`]: !!status,
21+
['fd-object-status--link']: !!link || !!onClick
22+
},
23+
className
24+
);
25+
26+
const handleKeyPress = e => {
27+
if (e.key === 'Enter' || e.key === ' ')
28+
onClick();
29+
};
30+
31+
let StatusTag = 'span';
32+
let semanticProps = {};
33+
34+
if (link?.length) {
35+
StatusTag = 'a';
36+
semanticProps.href = link;
37+
} else if ( typeof onClick === 'function') {
38+
StatusTag = 'span';
39+
semanticProps.onClick = onClick;
40+
semanticProps.onKeyPress = handleKeyPress;
41+
semanticProps.role = 'button';
42+
semanticProps.tabIndex = 0;
43+
}
44+
return (
45+
<StatusTag
46+
{...props}
47+
aria-label={ariaLabel}
48+
className={objectStatusClasses}
49+
{...semanticProps}
50+
ref={ref}>
51+
{children}
52+
</StatusTag>
53+
);
54+
});
55+
56+
57+
ObjectStatus.displayName = 'ObjectStatus';
58+
59+
ObjectStatus.propTypes = {
60+
/**
61+
* aria-label is required for a11y, when object status has only icon
62+
*/
63+
64+
ariaLabel: (props) => {
65+
if ((!props.children && !props.ariaLabel) || (!props.children && typeof props.ariaLabel !== 'string'))
66+
return new Error('ariaLabel is required if there is no text inside the ObjectStatus component and must be a string');
67+
},
68+
/** Node(s) to render within the component */
69+
children: PropTypes.node,
70+
/** CSS class(es) to add to the element */
71+
className: PropTypes.string,
72+
/** The icon to include. See the icon page for the list of icons */
73+
glyph: PropTypes.string,
74+
/** Generic Indication Colors which will appear on the text */
75+
indication: CustomPropTypes.range(1, 8),
76+
/** Inverted Object Status(optional inverted visualization) determines whether the background color reflects
77+
* the set state instead of the control’s text. Use the inverted object status if the information is crucial
78+
* for the user’s actions and needs to stand out visually */
79+
inverted: PropTypes.bool,
80+
/** The link for the anchor tag */
81+
link: PropTypes.string,
82+
/** Size of the component:
83+
'l' */
84+
size: PropTypes.oneOf(OBJECT_STATUS_SIZES),
85+
/** Display the semantic status of an object: 'negative',
86+
'critical',
87+
'positive',
88+
'informative'*/
89+
status: PropTypes.oneOf(OBJECT_STATUS_TYPES),
90+
/** Callback function when user clicks on the component*/
91+
onClick: PropTypes.func
92+
};
93+
94+
export default ObjectStatus;
95+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { mount } from 'enzyme';
2+
import ObjectStatus from './ObjectStatus';
3+
import React from 'react';
4+
5+
describe('<ObjectStatus />', () => {
6+
describe('Prop spreading', () => {
7+
test('should allow props to be spread to the ObjectStatus component', () => {
8+
const element = mount(<ObjectStatus data-sample='Sample' />);
9+
10+
expect(
11+
element.getDOMNode().attributes['data-sample'].value
12+
).toBe('Sample');
13+
});
14+
});
15+
test('forwards the ref', () => {
16+
let ref;
17+
class Test extends React.Component {
18+
constructor(props) {
19+
super(props);
20+
ref = React.createRef();
21+
}
22+
render = () => <ObjectStatus ref={ref}>ObjectStatus</ObjectStatus>;
23+
}
24+
mount(<Test />);
25+
expect(ref.current.tagName).toEqual('SPAN');
26+
expect(ref.current.className).toEqual('fd-object-status');
27+
});
28+
});
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/* eslint-disable react/no-multi-comp */
2+
import ObjectStatus from '../ObjectStatus';
3+
import React from 'react';
4+
5+
export default {
6+
title: 'Component API/Object Display Components/ObjectStatus',
7+
component: ObjectStatus
8+
};
9+
10+
/**
11+
* Object Status can display the semantic status of an object: negative, critical, positive, informative, or none.
12+
*/
13+
14+
export const primary = () => (
15+
<div className='fddocs-container'>
16+
<ObjectStatus glyph='status-negative' status='negative'>Negative</ObjectStatus>
17+
<ObjectStatus glyph='status-critical' status='critical'>Critical</ObjectStatus>
18+
<ObjectStatus glyph='status-positive' status='positive'>Positive</ObjectStatus>
19+
<ObjectStatus glyph='hint' status='informative' >Info</ObjectStatus>
20+
<ObjectStatus glyph='to-be-reviewed'>Neutral</ObjectStatus>
21+
</div>);
22+
23+
export const icons = () => (
24+
<div className='fddocs-container'>
25+
<ObjectStatus ariaLabel='negative' glyph='status-negative'
26+
status='negative' />
27+
<ObjectStatus ariaLabel='critical' glyph='status-critical'
28+
status='critical' />
29+
<ObjectStatus ariaLabel='positive' glyph='status-positive'
30+
status='positive' />
31+
<ObjectStatus ariaLabel='info' glyph='hint'
32+
status='informative' />
33+
<ObjectStatus ariaLabel='neutral' glyph='to-be-reviewed' />
34+
</div>
35+
);
36+
icons.storyName = 'Icon Only';
37+
38+
export const text = () => (
39+
<div className='fddocs-container'>
40+
<ObjectStatus status='negative'>Negative</ObjectStatus>
41+
<ObjectStatus status='critical'>Critical</ObjectStatus>
42+
<ObjectStatus status='positive'>Positive</ObjectStatus>
43+
<ObjectStatus status='informative' >Info</ObjectStatus>
44+
<ObjectStatus>Neutral</ObjectStatus>
45+
</div>
46+
);
47+
text.storyName = 'Text Only';
48+
49+
export const iconAndText = () => (
50+
<div className='fddocs-container'>
51+
<ObjectStatus glyph='status-negative' status='negative'>Negative</ObjectStatus>
52+
<ObjectStatus glyph='status-critical' status='critical'>Critical</ObjectStatus>
53+
<ObjectStatus glyph='status-positive' status='positive'>Positive</ObjectStatus>
54+
<ObjectStatus glyph='hint' status='informative' >Info</ObjectStatus>
55+
<ObjectStatus glyph='to-be-reviewed'>Neutral</ObjectStatus>
56+
</div>
57+
);
58+
iconAndText.storyName = 'Icon and Text';
59+
60+
export const indications = () => (
61+
<div className='fddocs-container'>
62+
<ObjectStatus indication='1'>Dark Red</ObjectStatus>
63+
<ObjectStatus indication='2'>Red</ObjectStatus>
64+
<ObjectStatus indication='3'>Yellow</ObjectStatus>
65+
<ObjectStatus indication='4'>Green</ObjectStatus>
66+
<ObjectStatus indication='5'>Blue</ObjectStatus>
67+
<ObjectStatus indication='6'>Teal</ObjectStatus>
68+
<ObjectStatus indication='7'>Purple</ObjectStatus>
69+
<ObjectStatus indication='8'>Pink</ObjectStatus>
70+
71+
</div>
72+
);
73+
indications.storyName = 'Generic Indication Colors';
74+
75+
/**
76+
* Inverted Object Status(optional inverted visualization) determines whether the background color reflects the set state instead of the control’s text.
77+
* Use the inverted object status if the information is crucial for the user’s actions and needs to stand out visually.
78+
*/
79+
80+
export const inverted = () => (
81+
<>
82+
<div className='fddocs-container'>
83+
<ObjectStatus inverted status='negative'>Negative</ObjectStatus>
84+
<ObjectStatus inverted status='critical'>Critical</ObjectStatus>
85+
<ObjectStatus inverted status='positive'>Positive</ObjectStatus>
86+
<ObjectStatus inverted status='informative'>Info</ObjectStatus>
87+
<ObjectStatus inverted>Neutral</ObjectStatus>
88+
<ObjectStatus inverted status='negative'>7</ObjectStatus>
89+
<ObjectStatus inverted status='positive'>3.8</ObjectStatus>
90+
</div>
91+
<div className='fddocs-container'>
92+
<ObjectStatus glyph='status-negative' inverted
93+
status='negative'>Negative</ObjectStatus>
94+
<ObjectStatus glyph='status-critical' inverted
95+
status='critical'>Critical</ObjectStatus>
96+
<ObjectStatus indication='1' inverted>Dark Red</ObjectStatus>
97+
<ObjectStatus indication='2' inverted>Red</ObjectStatus>
98+
<ObjectStatus ariaLabel='informative' glyph='hint'
99+
inverted status='informative' />
100+
<ObjectStatus ariaLabel='neutral' glyph='to-be-reviewed'
101+
inverted />
102+
</div>
103+
</>
104+
);
105+
inverted.storyName = 'Inverted';
106+
107+
/**
108+
Use this feature if the object status is important in the business context and needs to stand out visually in the page header.
109+
*/
110+
111+
export const large = () => (
112+
<>
113+
<div className='fddocs-container'>
114+
<ObjectStatus size='l' status='negative'>Negative</ObjectStatus>
115+
<ObjectStatus size='l' status='critical'>Critical</ObjectStatus>
116+
<ObjectStatus size='l' status='positive'>Positive</ObjectStatus>
117+
<ObjectStatus size='l' status='informative'>Info</ObjectStatus>
118+
<ObjectStatus size='l'>Neutral</ObjectStatus>
119+
<ObjectStatus size='l' status='negative'>7</ObjectStatus>
120+
<ObjectStatus size='l' status='positive'>3.8</ObjectStatus>
121+
</div>
122+
<div className='fddocs-container'>
123+
<ObjectStatus glyph='status-negative' size='l'
124+
status='negative'>Negative</ObjectStatus>
125+
<ObjectStatus glyph='status-critical' size='l'
126+
status='critical'>Critical</ObjectStatus>
127+
<ObjectStatus indication='1' size='l'>Dark Red</ObjectStatus>
128+
<ObjectStatus indication='2' size='l'>Red</ObjectStatus>
129+
<ObjectStatus ariaLabel='informative' glyph='hint'
130+
size='l' status='informative' />
131+
<ObjectStatus ariaLabel='neutral' glyph='to-be-reviewed'
132+
size='l' />
133+
</div>
134+
</>
135+
);
136+
large.storyName = 'Large Design';
137+
138+
const handleClick = () => {
139+
alert('Status clicked');
140+
};
141+
142+
/**
143+
If the object status is used as a link or if a click event is attached, a hover effect is shown on non-touch devices.
144+
If the object status is shown using a combination of icon and text, there is no hover effect for the icon.
145+
*/
146+
147+
export const clickable = () => (
148+
<>
149+
<div className='fddocs-container'>
150+
<ObjectStatus glyph='status-negative' onClick={handleClick}
151+
status='negative'>Negative</ObjectStatus>
152+
<ObjectStatus glyph='status-critical' onClick={handleClick}
153+
status='critical'>Critical</ObjectStatus>
154+
<ObjectStatus glyph='status-positive' onClick={handleClick}
155+
status='positive'>Positive</ObjectStatus>
156+
<ObjectStatus glyph='hint' link='#'
157+
status='informative'>Info</ObjectStatus>
158+
<ObjectStatus glyph='to-be-reviewed'
159+
onClick={handleClick}>Neutral</ObjectStatus>
160+
</div>
161+
<div className='fddocs-container'>
162+
<ObjectStatus glyph='status-negative' inverted
163+
link='#' status='negative'>Negative</ObjectStatus>
164+
<ObjectStatus glyph='status-critical' inverted
165+
link='#' status='critical'>Critical</ObjectStatus>
166+
<ObjectStatus indication='1' inverted
167+
link='#'>Dark Red</ObjectStatus>
168+
<ObjectStatus indication='2' inverted
169+
link='#'>Red</ObjectStatus>
170+
<ObjectStatus ariaLabel='informative' glyph='hint'
171+
inverted link='#'
172+
status='informative' />
173+
<ObjectStatus ariaLabel='neutral' glyph='to-be-reviewed'
174+
inverted link='#' />
175+
</div>
176+
<div className='fddocs-container'>
177+
<ObjectStatus glyph='status-negative' onClick={handleClick}
178+
size='l' status='negative'>Negative</ObjectStatus>
179+
<ObjectStatus glyph='status-critical' onClick={handleClick}
180+
size='l' status='critical'>Critical</ObjectStatus>
181+
<ObjectStatus indication='1' onClick={handleClick}
182+
size='l'>Dark Red</ObjectStatus>
183+
<ObjectStatus indication='2' link='#'
184+
size='l'>Red</ObjectStatus>
185+
<ObjectStatus ariaLabel='info' glyph='hint'
186+
link='#' size='l'
187+
status='informative' />
188+
<ObjectStatus ariaLabel='info' glyph='to-be-reviewed'
189+
link='#' size='l' />
190+
</div>
191+
</>
192+
);
193+
clickable.storyName = 'Clickable';

0 commit comments

Comments
 (0)