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

Commit 20d2b3b

Browse files
committed
feat(components): RichContent component for server HTML
PageBuilder CMS, like many CMSes, will provide raw HTML as a server response. Added a `RichContent` component to handle this common use case. - It uses the `dangerouslySetInnerHTML` API, rendering a string of HTML supplied as a prop. - If the outer system replaces the raw HTML with progressively enhanced functionality, such as a tree of live `ReactElements`, `RichContent` accepts `ReactElements` as children. Children will override the `sanitizedRawHtml` prop. feat(RichContent): Add configurability to wrapper tag - Added two props to configure the "parent element" that React requires to `dangerouslySetInnerHTML`: - `wrapperTag` to specify the tag name as a string - `wrapperProps` to pass an object of props to that tag - Updated docs, stories, and tests to reflect this change feat:retract children API for later
1 parent 99e6ac4 commit 20d2b3b

File tree

6 files changed

+186
-25
lines changed

6 files changed

+186
-25
lines changed

package-lock.json

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

src/Content/RichContent.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createElement, Component } from 'react';
2+
import { object, string } from 'prop-types';
3+
4+
export default class RichContent extends Component {
5+
static propTypes = {
6+
sanitizedRawHtml: string.isRequired,
7+
wrapperTag: string,
8+
wrapperProps: object
9+
};
10+
11+
static defaultProps = {
12+
wrapperTag: 'span',
13+
wrapperProps: {
14+
className: 'peregrine-raw-html'
15+
}
16+
};
17+
18+
render() {
19+
// JSX tags must be in PascalCase
20+
const WrapperTag = this.props.wrapperTag;
21+
22+
return (
23+
<WrapperTag
24+
{...this.props.wrapperProps}
25+
dangerouslySetInnerHTML={{
26+
__html: this.props.sanitizedRawHtml
27+
}}
28+
/>
29+
);
30+
}
31+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# RichContent
2+
3+
The `RichContent` component is a utility component for rendering visual content
4+
delivered by an external data provider. It can render raw HTML supplied as
5+
a string.
6+
7+
CMS and catalog systems often store HTML content in the server-side database,
8+
and deliver that content in API responses as a raw string. PWA Studio provides
9+
the `RichContent` component to easily embed content for this common
10+
situation.
11+
12+
## Usage
13+
14+
```jsx
15+
import React from 'react';
16+
import { RichContent } from '@magento/peregrine';
17+
18+
class ItemDescription extends React.Component {
19+
render() {
20+
const { data } = this.props;
21+
return (
22+
<div className="desc-container">
23+
<h3>Description</h3>
24+
<RichContent sanitizedRawHtml={data.long_description} />
25+
</div>
26+
);
27+
}
28+
}
29+
```
30+
31+
## Props
32+
33+
| Prop Name | Required? | Description |
34+
| ------------------ | :-------: | --------------------------------------------------: |
35+
| `sanitizedRawHtml` | ✅ | A string of raw HTML to display. Ignored if `children` are also passed and are not null.
36+
| `wrapperTag` | | Optional HTML tag to use as a custom wrapper element. Default `span.`.
37+
| `wrapperProps` | | Optional object to be passed as props to the `wrapperTag`. Default `{ className: "peregrine-raw-html" }`.
38+
39+
## Notes
40+
41+
- The `RichContent` component will wrap `sanitizedRawHtml` content with a parent
42+
element for compatibility with React. The default wrapper tag is a `span` with
43+
a default classname:
44+
45+
```html
46+
<RichContent
47+
sanitizedRawHtml="<h2>Header</h2><p>body text</p>" />
48+
49+
<!-- emits this HTML: -->
50+
<span class="peregrine-raw-html">
51+
<h2>Header</h2><p>body text</p>
52+
</span>
53+
```
54+
55+
Use the `wrapperTag` string prop and `wrapperProps` object prop to customize
56+
the parent tag:
57+
58+
```html
59+
<RichContent
60+
sanitizedRawHtml="<h2>Header</h2><p>body text</p>"
61+
wrapperTag="section"
62+
wrapperProps={{ className: "product-description loading" }}
63+
/>
64+
65+
<!-- emits this HTML: -->
66+
<section class="product-description loading">
67+
<h2>Header</h2><p>body text</p>
68+
</section>
69+
```
70+
71+
- ⚠️⚠️ **`RichContent` does *not* sanitize its input in any way. It is the
72+
responsibility of the data provider to ensure that the HTML is safe,
73+
contains no script tags and no escaping issues.**
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createElement } from 'react';
2+
import { storiesOf } from '@storybook/react';
3+
import { withReadme } from 'storybook-readme';
4+
import { RichContent } from '..';
5+
import docs from '../__docs__/RichContent.md';
6+
7+
storiesOf('RichContent', module)
8+
.addDecorator(withReadme(docs))
9+
.add('Raw HTML', () => (
10+
<RichContent sanitizedRawHtml="<p><em>HTML fallback content</em></p><img src='https://placebeard.it/420/320'/>" />
11+
))
12+
.add('Raw HTML with custom wrapper tag', () => (
13+
<RichContent
14+
sanitizedRawHtml="<p><em>HTML fallback content</em></p><img src='https://placebeard.it/320/240'/>"
15+
wrapperTag="article"
16+
wrapperProps={{ style: { background: 'whitesmoke' } }}
17+
/>
18+
));
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { createElement } from 'react';
2+
import { RichContent } from '..';
3+
import { configure, mount } from 'enzyme';
4+
import Adapter from 'enzyme-adapter-react-16';
5+
6+
configure({ adapter: new Adapter() });
7+
8+
test('Renders raw HTML in a wrapper element', () => {
9+
const wrapper = mount(
10+
<RichContent sanitizedRawHtml="<h1 id='o-no'>Raw!!!</h1>" />
11+
);
12+
expect(wrapper.html()).toEqual(
13+
'<span class="peregrine-raw-html"><h1 id="o-no">Raw!!!</h1></span>'
14+
);
15+
});
16+
17+
test('Takes DOM properties for a custom wrapper element', () => {
18+
const wrapper = mount(
19+
<RichContent
20+
sanitizedRawHtml="<h1 id='o-no'>Raw!!!</h1>"
21+
wrapperTag="article"
22+
/>
23+
);
24+
expect(wrapper.html()).toEqual(
25+
'<article class="peregrine-raw-html"><h1 id="o-no">Raw!!!</h1></article>'
26+
);
27+
});

src/Content/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as RichContent } from './RichContent';

0 commit comments

Comments
 (0)