Skip to content

Commit 0b19b41

Browse files
authored
feat(Progress): update markup & support nested progress bars (#261)
* feat(Progress): update markup & support stacked progress bars * tests(Progress): add tests for nested Progress
1 parent 763a8aa commit 0b19b41

6 files changed

Lines changed: 78 additions & 150 deletions

File tree

docs/lib/Components/ProgressPage.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,6 @@ Progress.defaultProps = {
8383
<p>
8484
The <code>animated</code> prop also adds the <code>striped</code> prop; there is no need to pass both.
8585
</p>
86-
<Card block outline color="danger">
87-
<CardText>
88-
Currently, animated progress does not work in bootstrap v4 (alpha 3). This is an issue bootstrap has to
89-
resolve.
90-
</CardText>
91-
</Card>
9286
<div className="docs-example">
9387
<div>
9488
<ProgressAnimatedExample />

docs/lib/examples/Progress.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ const Example = (props) => {
1414
<Progress value={75} />
1515
<div className="text-xs-center">100%</div>
1616
<Progress value="100" />
17+
<div className="text-xs-center">Multiple bars</div>
18+
<Progress>
19+
<Progress value="15" />
20+
<Progress color="success" value="30" />
21+
<Progress color="info" value="25" />
22+
<Progress color="warning" value="20" />
23+
<Progress color="danger" value="5" />
24+
</Progress>
1725
</div>
1826
);
1927
};

docs/static/docs.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ pre, code {
180180
margin-bottom: 1rem;
181181
}
182182

183+
.docs-example .progress {
184+
margin-bottom: 1rem;
185+
}
186+
183187

184188
/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+bash+jsx */
185189
/**

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,18 @@
5353
},
5454
"peerDependencies": {
5555
"react": "^0.14.0 || ^15.0.0",
56-
"react-addons-transition-group": "^0.14.0 || ^15.0.0",
5756
"react-addons-css-transition-group": "^0.14.0 || ^15.0.0",
57+
"react-addons-transition-group": "^0.14.0 || ^15.0.0",
5858
"react-dom": "^0.14.0 || ^15.0.0"
5959
},
6060
"devDependencies": {
6161
"babel-cli": "^6.14.0",
6262
"babel-loader": "^6.2.2",
6363
"babel-preset-es2015": "^6.14.0",
6464
"babel-preset-react": "^6.11.1",
65-
"babel-preset-stage-0": "^6.5.0",
6665
"babel-preset-react-app": "^0.2.1",
67-
"bootstrap": "^4.0.0-alpha.5",
66+
"babel-preset-stage-0": "^6.5.0",
67+
"bootstrap": "twbs/bootstrap#d82914fb2fc02cc9f9bad132782feb91a049f226",
6868
"clean-webpack-plugin": "^0.1.8",
6969
"conventional-changelog-cli": "^1.1.1",
7070
"conventional-recommended-bump": "^0.3.0",

src/Progress.js

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import toNumber from 'lodash.tonumber';
44
import { mapToCssModules } from './utils';
55

66
const propTypes = {
7+
children: PropTypes.node,
8+
bar: PropTypes.bool,
79
tag: PropTypes.string,
810
value: PropTypes.oneOfType([
911
PropTypes.string,
@@ -21,47 +23,54 @@ const propTypes = {
2123
};
2224

2325
const defaultProps = {
24-
tag: 'progress',
26+
tag: 'div',
2527
value: 0,
2628
max: 100,
2729
};
2830

2931
const Progress = (props) => {
3032
const {
33+
children,
3134
className,
3235
cssModule,
3336
value,
3437
max,
3538
animated,
3639
striped,
3740
color,
41+
bar,
3842
tag: Tag,
3943
...attributes
4044
} = props;
4145

4246
const percent = ((toNumber(value) / toNumber(max)) * 100);
4347

44-
const nonProgressClasses = mapToCssModules(classNames(
45-
className,
46-
'progress',
47-
animated ? 'progress-animated' : null
48-
), cssModule);
49-
5048
const progressClasses = mapToCssModules(classNames(
51-
nonProgressClasses,
52-
color ? `progress-${color}` : null,
53-
striped || animated ? 'progress-striped' : null
49+
className,
50+
'progress'
5451
), cssModule);
5552

56-
const fallbackClasses = mapToCssModules(classNames(
53+
const progressBarClasses = mapToCssModules(classNames(
5754
'progress-bar',
58-
color ? `progress-${color}` : null,
55+
animated ? 'progress-bar-animated' : null,
56+
color ? `bg-${color}` : null,
5957
striped || animated ? 'progress-bar-striped' : null
6058
), cssModule);
6159

62-
const fallbackFill = (
63-
<span
64-
className={fallbackClasses}
60+
if (children) {
61+
const childrenWithProps = React.Children.map(children, el => {
62+
return React.cloneElement(el, {
63+
bar: true
64+
});
65+
});
66+
return (
67+
<Tag {...attributes} className={progressClasses} children={childrenWithProps} />
68+
);
69+
}
70+
71+
const ProgressBar = (
72+
<div
73+
className={progressBarClasses}
6574
style={{ width: `${percent}%` }}
6675
role="progressbar"
6776
aria-valuenow={value}
@@ -70,16 +79,12 @@ const Progress = (props) => {
7079
/>
7180
);
7281

73-
if (Tag === 'progress') {
74-
return (
75-
<Tag {...attributes} className={progressClasses} value={value} max={max}>
76-
<div className={nonProgressClasses} children={fallbackFill} />
77-
</Tag>
78-
);
82+
if (bar) {
83+
return ProgressBar;
7984
}
8085

8186
return (
82-
<Tag {...attributes} className={nonProgressClasses} children={fallbackFill} />
87+
<Tag {...attributes} className={progressClasses} children={ProgressBar} />
8388
);
8489
};
8590

src/__tests__/Progress.spec.js

Lines changed: 36 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React from 'react';
2-
import { shallow } from 'enzyme';
2+
import { shallow, mount } from 'enzyme';
33
import { Progress } from '../';
44

55
describe('Progress', () => {
6-
it('should render with "progress" tag by default', () => {
6+
it('should render with "div" tag by default', () => {
77
const wrapper = shallow(<Progress />);
88

9-
expect(wrapper.type()).toBe('progress');
9+
expect(wrapper.type()).toBe('div');
1010
});
1111

1212
it('should render with "progress" class', () => {
@@ -18,56 +18,56 @@ describe('Progress', () => {
1818
it('should render with "value" 0 by default', () => {
1919
const wrapper = shallow(<Progress />);
2020

21-
expect(wrapper.prop('value')).toBe(0);
21+
expect(wrapper.instance().props['value']).toBe(0);
2222
});
2323

2424
it('should render with "max" 100 by default', () => {
2525
const wrapper = shallow(<Progress />);
2626

27-
expect(wrapper.prop('max')).toBe(100);
27+
expect(wrapper.instance().props['max']).toBe(100);
2828
});
2929

3030
it('should render with the given "value" when passed as string prop', () => {
3131
const wrapper = shallow(<Progress value="10" />);
3232

33-
expect(wrapper.prop('value')).toBe('10');
33+
expect(wrapper.instance().props['value']).toBe('10');
3434
});
3535

3636
it('should render with the given "value" when passed as number prop', () => {
3737
const wrapper = shallow(<Progress value={10} />);
3838

39-
expect(wrapper.prop('value')).toBe(10);
39+
expect(wrapper.instance().props['value']).toBe(10);
4040
});
4141

4242
it('should render with the given "max" when passed as string prop', () => {
4343
const wrapper = shallow(<Progress max="10" />);
4444

45-
expect(wrapper.prop('max')).toBe('10');
45+
expect(wrapper.instance().props['max']).toBe('10');
4646
});
4747

4848
it('should render with the given "max" when passed as number prop', () => {
4949
const wrapper = shallow(<Progress max={10} />);
5050

51-
expect(wrapper.prop('max')).toBe(10);
51+
expect(wrapper.instance().props['max']).toBe(10);
5252
});
5353

54-
it('should render with "progress-striped" class when striped prop is truthy', () => {
54+
it('should render with "progress-bar-striped" class when striped prop is truthy', () => {
5555
const wrapper = shallow(<Progress striped />);
5656

57-
expect(wrapper.hasClass('progress-striped')).toBe(true);
57+
expect(wrapper.find('.progress-bar').hasClass('progress-bar-striped')).toBe(true);
5858
});
5959

60-
it('should render with "progress-striped" and "progress-animated" classes when animated prop is truthy', () => {
60+
it('should render with "progress-bar-striped" and "progress-bar-animated" classes when animated prop is truthy', () => {
6161
const wrapper = shallow(<Progress animated />);
6262

63-
expect(wrapper.hasClass('progress-striped')).toBe(true);
64-
expect(wrapper.hasClass('progress-animated')).toBe(true);
63+
expect(wrapper.find('.progress-bar').hasClass('progress-bar-striped')).toBe(true);
64+
expect(wrapper.find('.progress-bar').hasClass('progress-bar-animated')).toBe(true);
6565
});
6666

67-
it('should render with "progress-${color}" class when color prop is defined', () => {
67+
it('should render with "bg-${color}" class when color prop is defined', () => {
6868
const wrapper = shallow(<Progress color="yo" />);
6969

70-
expect(wrapper.hasClass('progress-yo')).toBe(true);
70+
expect(wrapper.find('.progress-bar').hasClass('bg-yo')).toBe(true);
7171
});
7272

7373
it('should render additional classes', () => {
@@ -77,114 +77,31 @@ describe('Progress', () => {
7777
expect(wrapper.hasClass('progress')).toBe(true);
7878
});
7979

80-
it('should have div fallback for IE9', () => {
81-
const wrapper = shallow(<Progress />);
80+
it('should render custom tag', () => {
81+
const wrapper = shallow(<Progress tag="main" />);
8282

83-
expect(wrapper.childAt(0).type()).toBe('div');
83+
expect(wrapper.type()).toBe('main');
8484
});
8585

86-
describe('div fallback', () => {
87-
it('should render with "progress" class', () => {
88-
const div = shallow(<Progress />).childAt(0);
89-
90-
expect(div.hasClass('progress')).toBe(true);
91-
});
92-
93-
it('should render with "progress-animated" class when animated is truthy', () => {
94-
const div = shallow(<Progress animated />).childAt(0);
95-
96-
expect(div.hasClass('progress-animated')).toBe(true);
97-
});
98-
99-
it('should render additional classes', () => {
100-
const div = shallow(<Progress className="other" />).childAt(0);
101-
102-
expect(div.hasClass('other')).toBe(true);
103-
expect(div.hasClass('progress')).toBe(true);
104-
});
105-
106-
it('should have a span', () => {
107-
const div = shallow(<Progress />).childAt(0);
108-
109-
expect(div.childAt(0).type()).toBe('span');
110-
});
111-
112-
describe('the span', () => {
113-
it('should render with "progress-bar" class', () => {
114-
const span = shallow(<Progress />).childAt(0).childAt(0);
115-
116-
expect(span.hasClass('progress-bar')).toBe(true);
117-
});
118-
119-
it('should render with "progress-bar-striped" class when striped is truthy', () => {
120-
const span = shallow(<Progress striped />).childAt(0).childAt(0);
121-
122-
expect(span.hasClass('progress-bar-striped')).toBe(true);
123-
});
124-
125-
it('should render with "progress-bar-striped" class when animated is truthy', () => {
126-
const span = shallow(<Progress animated />).childAt(0).childAt(0);
127-
128-
expect(span.hasClass('progress-bar-striped')).toBe(true);
129-
});
130-
131-
it('should render with a style width matching the percent of the fill', () => {
132-
const span = shallow(<Progress value="25" />).childAt(0).childAt(0);
86+
it('should render only the .progress-bar when "bar" is passed', () => {
87+
const wrapper = shallow(<Progress bar />);
13388

134-
expect(span.prop('style').width).toBe('25%');
135-
});
136-
137-
it('should render with a style width matching the percent of the fill (with max)', () => {
138-
const span = shallow(<Progress value="25" max="50" />).childAt(0).childAt(0);
139-
140-
expect(span.prop('style').width).toBe('50%');
141-
});
142-
});
89+
expect(wrapper.type()).toBe('div');
90+
expect(wrapper.hasClass('progress-bar')).toBe(true);
14391
});
14492

145-
describe('with a custom tag', () => {
146-
it('should render custom tag', () => {
147-
const wrapper = shallow(<Progress tag="main" />);
148-
149-
expect(wrapper.type()).toBe('main');
150-
});
151-
152-
it('should have a span', () => {
153-
const span = shallow(<Progress tag="main" />).childAt(0);
154-
155-
expect(span.type()).toBe('span');
156-
});
157-
158-
describe('the span', () => {
159-
it('should render with "progress-bar" class', () => {
160-
const span = shallow(<Progress tag="main" />).childAt(0);
161-
162-
expect(span.hasClass('progress-bar')).toBe(true);
163-
});
164-
165-
it('should render with "progress-bar-striped" class when striped is truthy', () => {
166-
const span = shallow(<Progress tag="main" striped />).childAt(0);
167-
168-
expect(span.hasClass('progress-bar-striped')).toBe(true);
169-
});
170-
171-
it('should render with "progress-bar-striped" class when animated is truthy', () => {
172-
const span = shallow(<Progress tag="main" animated />).childAt(0);
173-
174-
expect(span.hasClass('progress-bar-striped')).toBe(true);
175-
});
176-
177-
it('should render with a style width matching the percent of the fill', () => {
178-
const span = shallow(<Progress tag="main" value="25" />).childAt(0);
179-
180-
expect(span.prop('style').width).toBe('25%');
181-
});
182-
183-
it('should render with a style width matching the percent of the fill (with max)', () => {
184-
const span = shallow(<Progress tag="main" value="25" max="50" />).childAt(0);
185-
186-
expect(span.prop('style').width).toBe('50%');
187-
});
188-
});
93+
it('should render nested progress bars', () => {
94+
const wrapper = mount(
95+
<Progress>
96+
<Progress value="15" />
97+
<Progress color="success" value="30" />
98+
<Progress color="info" value="25" />
99+
<Progress color="warning" value="20" />
100+
<Progress color="danger" value="5" />
101+
</Progress>
102+
);
103+
104+
expect(wrapper.find('.progress').length).toBe(1);
105+
expect(wrapper.find('.progress-bar').length).toBe(5);
189106
});
190107
});

0 commit comments

Comments
 (0)