Skip to content
This repository was archived by the owner on Jun 19, 2018. It is now read-only.

Commit c899c03

Browse files
authored
Add component for Price display (#46)
<!-- (REQUIRED) What is the nature of this PR? --> ## This PR is a: [X] New feature [ ] Enhancement/Optimization [ ] Refactor [ ] Bugfix [ ] Test for existing code [ ] Documentation <!-- (REQUIRED) What does this PR change? --> ## Summary Adds a new component for display of prices. See deployed Storybook in PR for docs/usage. <!-- (OPTIONAL) What other information can you provide about this PR? --> ## Additional information <!-- Thank you for your contribution! Before submitting this pull request, please make sure you have read our Contribution Guidelines and your PR meets our contribution standards: https://github.com/magento-research/peregrine/blob/develop/.github/CONTRIBUTION.md Please fill out as much information as you can about your PR to help speed up the review process. If your PR addresses an existing GitHub Issue, please refer to it in the title or Additional Information section to make the connection. We may ask you for changes in your PR in order to meet the standards set in our Contribution Guidelines. PR's that do not comply with our guidelines may be closed at the maintainers' discretion. Feel free to remove this section before creating this PR. -->
1 parent 436fcae commit c899c03

File tree

7 files changed

+2250
-2773
lines changed

7 files changed

+2250
-2773
lines changed

package-lock.json

Lines changed: 2067 additions & 2772 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"eslint-plugin-jsx-a11y": "^6.0.3",
5757
"eslint-plugin-react": "^7.5.1",
5858
"execa": "^0.10.0",
59+
"intl": "^1.2.5",
5960
"jest": "^22.4.3",
6061
"jest-fetch-mock": "^1.4.1",
6162
"npm-merge-driver": "^2.3.5",

src/Price/Price.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { createElement, PureComponent, Fragment } from 'react';
2+
import { number, string, shape } from 'prop-types';
3+
4+
export default class Price extends PureComponent {
5+
static propTypes = {
6+
value: number.isRequired,
7+
currencyCode: string.isRequired,
8+
classes: shape({
9+
currency: string,
10+
integer: string,
11+
decimal: string,
12+
fraction: string
13+
})
14+
};
15+
16+
static defaultProps = {
17+
classes: {}
18+
};
19+
20+
render() {
21+
const { value, currencyCode, classes } = this.props;
22+
const parts = Intl.NumberFormat(undefined, {
23+
style: 'currency',
24+
currency: currencyCode
25+
}).formatToParts(value);
26+
27+
return (
28+
<Fragment>
29+
{parts.map((part, i) => {
30+
const partClass = classes[part.type];
31+
const key = `${i}-${part.value}`;
32+
return (
33+
<span key={key} className={partClass}>
34+
{part.value}
35+
</span>
36+
);
37+
})}
38+
</Fragment>
39+
);
40+
}
41+
}

src/Price/__docs__/Price.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Price
2+
3+
The `Price` component is used anywhere a price is rendered in PWA Studio.
4+
5+
Formatting of prices and currency symbol selection is handled entirely by the ECMAScript Internationalization API available in modern browsers. A [polyfill](https://www.npmjs.com/package/intl) will need to be loaded for any JavaScript runtime that does not have [`Intl.NumberFormat.prototype.formatToParts`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/formatToParts).
6+
7+
## Usage
8+
9+
```jsx
10+
import Price from '@peregrine/Price';
11+
import cssModule from './my-pricing-styles';
12+
13+
<Price value={100.99} currencyCode="USD" classes={cssModule} />;
14+
/*
15+
<span className="curr">$</span>
16+
<span className="int">88</span>
17+
<span className="dec">.</span>
18+
<span className="fract">81</span>
19+
*/
20+
```
21+
22+
## Props
23+
24+
| Prop Name | Required? | Description |
25+
| -------------- | :-------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26+
| `classes` || A classname object. |
27+
| `value` || Numeric price |
28+
| `currencyCode` || A string of with any currency code supported by [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat) |

src/Price/__stories__/Price.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { createElement } from 'react';
2+
import { storiesOf } from '@storybook/react';
3+
import { withReadme } from 'storybook-readme';
4+
import Price from '../Price';
5+
import docs from '../__docs__/Price.md';
6+
7+
const stories = storiesOf('Price', module);
8+
9+
stories.add(
10+
'USD',
11+
withReadme(docs, () => <Price value={100.99} currencyCode="USD" />)
12+
);
13+
14+
stories.add(
15+
'EUR',
16+
withReadme(docs, () => <Price value={100.99} currencyCode="EUR" />)
17+
);
18+
19+
stories.add(
20+
'JPY',
21+
withReadme(docs, () => <Price value={100.99} currencyCode="JPY" />)
22+
);
23+
24+
stories.add(
25+
'Custom Styles',
26+
withReadme(docs, () => {
27+
const classes = {
28+
currency: 'curr',
29+
integer: 'int',
30+
decimal: 'dec',
31+
fraction: 'fract'
32+
};
33+
return (
34+
<div>
35+
<style>{`
36+
.curr { color: green; font-weight: bold; }
37+
.int { color: red; }
38+
.dec { color: black; }
39+
.fract { color: blue; }
40+
`}</style>
41+
<Price value={100.99} currencyCode="USD" classes={classes} />
42+
</div>
43+
);
44+
})
45+
);

src/Price/__tests__/Price.spec.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { createElement, Fragment } from 'react';
2+
import { configure, shallow } from 'enzyme';
3+
import Adapter from 'enzyme-adapter-react-16';
4+
import Price from '../Price';
5+
import IntlPolyfill from 'intl';
6+
7+
configure({ adapter: new Adapter() });
8+
9+
if (!global.Intl.NumberFormat.prototype.formatToParts) {
10+
global.Intl = IntlPolyfill;
11+
}
12+
13+
test('Renders a USD price', () => {
14+
const wrapper = shallow(<Price value={100.99} currencyCode="USD" />);
15+
expect(
16+
wrapper.equals(
17+
<Fragment>
18+
<span>$</span>
19+
<span>100</span>
20+
<span>.</span>
21+
<span>99</span>
22+
</Fragment>
23+
)
24+
).toBe(true);
25+
});
26+
27+
test('Renders a EUR price', () => {
28+
const wrapper = shallow(<Price value={100.99} currencyCode="EUR" />);
29+
expect(
30+
wrapper.equals(
31+
<Fragment>
32+
<span></span>
33+
<span>100</span>
34+
<span>.</span>
35+
<span>99</span>
36+
</Fragment>
37+
)
38+
).toBe(true);
39+
});
40+
41+
test('Allows custom classnames for each part', () => {
42+
const classes = {
43+
currency: 'curr',
44+
integer: 'int',
45+
decimal: 'dec',
46+
fraction: 'fract'
47+
};
48+
const wrapper = shallow(
49+
<Price value={88.81} currencyCode="USD" classes={classes} />
50+
);
51+
expect(
52+
wrapper.equals(
53+
<Fragment>
54+
<span className="curr">$</span>
55+
<span className="int">88</span>
56+
<span className="dec">.</span>
57+
<span className="fract">81</span>
58+
</Fragment>
59+
)
60+
).toBe(true);
61+
});

src/store/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import { log } from './middleware';
55
const reducer = (state = {}) => state;
66

77
const initStore = () =>
8-
createStore(reducer, compose(applyMiddleware(log), exposeSlices));
8+
createStore(
9+
reducer,
10+
compose(
11+
applyMiddleware(log),
12+
exposeSlices
13+
)
14+
);
915

1016
export default initStore;

0 commit comments

Comments
 (0)