@bloomreach/react-sdk
Advanced tools
Comparing version 0.1.0-saas to 0.2.0-saas
@@ -1,3 +0,118 @@ | ||
export { BrComponent, BrComponentContext, BrProps } from './component'; | ||
export { BrManageContentButton, BrManageMenuButton } from './cms'; | ||
export { BrPage, BrPageContext } from './page'; | ||
/// <reference types="react" /> | ||
import { Component, Page, Container, ContainerItem, ManageContentButton, Menu, Configuration, PageModel } from '@bloomreach/spa-sdk'; | ||
import React from 'react'; | ||
/** | ||
* The React Context holding the current brXM Component. | ||
*/ | ||
declare const BrComponentContext: React.Context<Component | undefined>; | ||
interface BrComponentProps { | ||
/** | ||
* The path to a component. | ||
* The path is defined as a slash-separated components name chain | ||
* relative to the current component (e.g. `main/container`). | ||
* If it is omitted, all the children will be rendered. | ||
*/ | ||
path?: string; | ||
} | ||
/** | ||
* The brXM component. | ||
*/ | ||
declare class BrComponent extends React.Component<BrComponentProps> { | ||
static contextType: React.Context<Component | undefined>; | ||
context: React.ContextType<typeof BrComponentContext>; | ||
private getComponents; | ||
private renderComponents; | ||
render(): JSX.Element; | ||
} | ||
/** | ||
* The mapped component properties. | ||
*/ | ||
interface BrProps<T extends Component = Component> { | ||
/** | ||
* The mapped component. | ||
*/ | ||
component: T; | ||
/** | ||
* The current page. | ||
*/ | ||
page: Page; | ||
} | ||
declare type BrComponent$1 = React.ComponentType<BrProps<Component>> | React.ComponentType<BrProps<Container>> | React.ComponentType<BrProps<ContainerItem>>; | ||
declare const BrMappingContext: React.Context<Record<string | number | symbol, BrComponent$1>>; | ||
/** | ||
* The React Context holding the current brXM Page. | ||
*/ | ||
declare const BrPageContext: React.Context<Page | undefined>; | ||
/** | ||
* The button component that opens for editing a content. | ||
*/ | ||
declare class BrManageContentButton extends React.Component<ManageContentButton> { | ||
static contextType: React.Context<Page | undefined>; | ||
context: React.ContextType<typeof BrPageContext>; | ||
render(): JSX.Element | null; | ||
} | ||
interface BrManageMenuButtonProps { | ||
/** | ||
* The related menu model. | ||
*/ | ||
menu: Menu; | ||
} | ||
/** | ||
* The button component that opens a menu editor. | ||
*/ | ||
declare class BrManageMenuButton extends React.Component<BrManageMenuButtonProps> { | ||
static contextType: React.Context<Page | undefined>; | ||
context: React.ContextType<typeof BrPageContext>; | ||
render(): JSX.Element | null; | ||
} | ||
interface BrPageProps { | ||
/** | ||
* The configuration of the SPA SDK. | ||
* @see https://www.npmjs.com/package/@bloomreach/spa-sdk#configuration | ||
*/ | ||
configuration: Configuration; | ||
/** | ||
* The brXM and React components mapping. | ||
*/ | ||
mapping: React.ContextType<typeof BrMappingContext>; | ||
/** | ||
* The pre-initialized page instance or prefetched page model. | ||
* Mostly this property should be used to transfer state from the server-side to the client-side. | ||
*/ | ||
page?: Page | PageModel; | ||
} | ||
interface BrPageState { | ||
page?: Page; | ||
} | ||
/** | ||
* @typedef {Object} BrPageProps | ||
* @property {Configuration} configuration The configuration of the SPA SDK. | ||
* @property {Object} mapping The brXM and React components mapping. | ||
* @property {Page | PageModel | undefined} page The pre-initialized page instance or prefetched page model. | ||
* Mostly this property should be used to transfer state from the server-side to the client-side. | ||
*/ | ||
/** | ||
* The brXM page. | ||
*/ | ||
declare class BrPage extends React.Component<BrPageProps, BrPageState> { | ||
/** | ||
* @param props {BrPageProps} | ||
*/ | ||
constructor(props: BrPageProps); | ||
componentDidMount(): void; | ||
componentDidUpdate(prevProps: BrPageProps, prevState: BrPageState): void; | ||
componentWillUnmount(): void; | ||
private initialize; | ||
private destroy; | ||
render(): JSX.Element | null; | ||
} | ||
export { BrComponent, BrComponentContext, BrManageContentButton, BrManageMenuButton, BrPage, BrPageContext, BrProps }; |
@@ -15,40 +15,24 @@ (function(global, factory) { | ||
*/ const BrComponentContext = React__default["default"].createContext(undefined); | ||
class BrMeta extends React__default["default"].Component { | ||
constructor() { | ||
super(...arguments); | ||
this.headRef = React__default["default"].createRef(); | ||
this.tailRef = React__default["default"].createRef(); | ||
} | ||
componentDidMount() { | ||
this.renderMeta(); | ||
} | ||
componentDidUpdate(prevProps) { | ||
prevProps.meta.clear(); | ||
this.renderMeta(); | ||
} | ||
componentWillUnmount() { | ||
this.props.meta.clear(); | ||
} | ||
renderMeta() { | ||
var _a, _b, _c; | ||
const head = (_b = (_a = this.headRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.nextSibling; | ||
const tail = (_c = this.tailRef) === null || _c === void 0 ? void 0 : _c.current; | ||
if (!head || !tail) { | ||
function BrMeta({children, meta}) { | ||
var _a; | ||
const head = React.useRef(null); | ||
const tail = React.useRef(null); | ||
React.useEffect((() => { | ||
var _a; | ||
if (!((_a = head.current) === null || _a === void 0 ? void 0 : _a.nextSibling) || !tail.current) { | ||
return; | ||
} | ||
this.props.meta.render(head, tail); | ||
} | ||
render() { | ||
return React__default["default"].createElement(React__default["default"].Fragment, null, this.props.meta.length > 0 && React__default["default"].createElement("span", { | ||
style: { | ||
display: "none" | ||
}, | ||
ref: this.headRef | ||
}), this.props.children, this.props.meta.length > 0 && React__default["default"].createElement("span", { | ||
style: { | ||
display: "none" | ||
}, | ||
ref: this.tailRef | ||
})); | ||
} | ||
return meta.render(head.current.nextSibling, tail.current); | ||
}), [ meta, (_a = head.current) === null || _a === void 0 ? void 0 : _a.nextSibling, tail.current ]); | ||
return React__default["default"].createElement(React__default["default"].Fragment, null, meta.length > 0 && React__default["default"].createElement("span", { | ||
style: { | ||
display: "none" | ||
}, | ||
ref: head | ||
}), children, meta.length > 0 && React__default["default"].createElement("span", { | ||
style: { | ||
display: "none" | ||
}, | ||
ref: tail | ||
})); | ||
} | ||
@@ -61,14 +45,10 @@ /** | ||
getMapping() { | ||
return this.props.component.getName(); | ||
return this.context[this.props.component.getName()]; | ||
} | ||
fallback() { | ||
return this.props.children; | ||
} | ||
render() { | ||
const mapping = this.getMapping(); | ||
const component = mapping && this.context[mapping]; | ||
if (!component) { | ||
return this.fallback(); | ||
if (!mapping) { | ||
return this.props.children; | ||
} | ||
return React__default["default"].createElement(component, this.props); | ||
return React__default["default"].createElement(mapping, this.props); | ||
} | ||
@@ -116,8 +96,5 @@ } | ||
var _a; | ||
if (!((_a = this.context) === null || _a === void 0 ? void 0 : _a.isPreview())) { | ||
return null; | ||
} | ||
return React__default["default"].createElement(BrMeta, { | ||
meta: this.props.content.getMeta() | ||
}); | ||
return ((_a = this.context) === null || _a === void 0 ? void 0 : _a.isPreview()) ? React__default["default"].createElement(BrMeta, { | ||
meta: this.context.getButton(spaSdk.TYPE_MANAGE_CONTENT_BUTTON, this.props) | ||
}) : null; | ||
} | ||
@@ -131,8 +108,4 @@ } | ||
var _a; | ||
if (!((_a = this.context) === null || _a === void 0 ? void 0 : _a.isPreview())) { | ||
return null; | ||
} | ||
const meta = spaSdk.isMenu(this.props.menu) ? this.props.menu.getMeta() : this.props.menu._meta && this.context.getMeta(this.props.menu._meta); | ||
return meta ? React__default["default"].createElement(BrMeta, { | ||
meta | ||
return ((_a = this.context) === null || _a === void 0 ? void 0 : _a.isPreview()) ? React__default["default"].createElement(BrMeta, { | ||
meta: this.context.getButton(spaSdk.TYPE_MANAGE_MENU_BUTTON, this.props.menu) | ||
}) : null; | ||
@@ -144,20 +117,21 @@ } | ||
getMapping() { | ||
return this.props.component.getType(); | ||
} | ||
fallback() { | ||
switch (this.props.component.getType()) { | ||
const type = this.props.component.getType(); | ||
if (type && type in this.context) { | ||
return this.context[type]; | ||
} | ||
switch (type) { | ||
case spaSdk.TYPE_CONTAINER_INLINE: | ||
return React__default["default"].createElement(BrContainerInline, Object.assign({}, this.props)); | ||
return BrContainerInline; | ||
case spaSdk.TYPE_CONTAINER_NO_MARKUP: | ||
return React__default["default"].createElement(BrContainerNoMarkup, Object.assign({}, this.props)); | ||
return BrContainerNoMarkup; | ||
case spaSdk.TYPE_CONTAINER_ORDERED_LIST: | ||
return React__default["default"].createElement(BrContainerOrderedList, Object.assign({}, this.props)); | ||
return BrContainerOrderedList; | ||
case spaSdk.TYPE_CONTAINER_UNORDERED_LIST: | ||
return React__default["default"].createElement(BrContainerUnorderedList, Object.assign({}, this.props)); | ||
return BrContainerUnorderedList; | ||
default: | ||
return React__default["default"].createElement(BrContainerBox, Object.assign({}, this.props)); | ||
return BrContainerBox; | ||
} | ||
@@ -184,7 +158,9 @@ } | ||
getMapping() { | ||
return this.props.component.getType(); | ||
var _a; | ||
const type = this.props.component.getType(); | ||
if (type && type in this.context) { | ||
return this.context[type]; | ||
} | ||
return (_a = this.context[spaSdk.TYPE_CONTAINER_ITEM_UNDEFINED]) !== null && _a !== void 0 ? _a : BrContainerItemUndefined; | ||
} | ||
fallback() { | ||
return React__default["default"].createElement(BrContainerItemUndefined, Object.assign({}, this.props)); | ||
} | ||
onUpdate() { | ||
@@ -191,0 +167,0 @@ this.forceUpdate((() => this.props.page.sync())); |
{ | ||
"name": "@bloomreach/react-sdk", | ||
"version": "0.1.0-saas", | ||
"version": "0.2.0-saas", | ||
"description": "Bloomreach SPA SDK for React", | ||
@@ -14,4 +14,4 @@ "keywords": [ | ||
"type": "git", | ||
"url": "https://code.onehippo.org/cms-community/bloomreach-spa-sdk", | ||
"directory": "packages/react-sdk" | ||
"url": "https://github.com/bloomreach/brxm.git", | ||
"directory": "spa-sdk/packages/react-sdk" | ||
}, | ||
@@ -42,3 +42,3 @@ "bugs": { | ||
"dependencies": { | ||
"@bloomreach/spa-sdk": "^0.1.0-saas" | ||
"@bloomreach/spa-sdk": "^0.2.0-saas" | ||
}, | ||
@@ -68,2 +68,3 @@ "peerDependencies": { | ||
"rollup": "^2.32", | ||
"rollup-plugin-dts": "^1.4", | ||
"rollup-plugin-terser": "^7.0", | ||
@@ -70,0 +71,0 @@ "rollup-plugin-typescript2": "^0.28", |
171
README.md
@@ -20,2 +20,3 @@ # Bloomreach React SDK | ||
- [React Router](https://reacttraining.com/react-router/) and [Next Routes](https://github.com/fridays/next-routes) support; | ||
- [React Native](https://reactnative.dev/) support; | ||
- [Enzyme](https://airbnb.io/enzyme/) and [Jest](https://jestjs.io/) support. | ||
@@ -27,3 +28,3 @@ | ||
```bash | ||
npm install @bloomreach/react-sdk | ||
npm install @bloomreach/react-sdk@saas | ||
``` | ||
@@ -33,3 +34,3 @@ | ||
```bash | ||
yarn add @bloomreach/react-sdk | ||
yarn add @bloomreach/react-sdk@saas | ||
``` | ||
@@ -116,2 +117,10 @@ | ||
- By default, container items that are not mapped will be rendered as a warning text. There is an option to override the fallback. | ||
```jsx | ||
import { TYPE_CONTAINER_ITEM_UNDEFINED } from '@bloomreach/spa-sdk'; | ||
import Fallback from './components/Fallback'; | ||
return <BrPage mapping={{ [TYPE_CONTAINER_ITEM_UNDEFINED]: Fallback }} />; | ||
``` | ||
### Inline Mapping | ||
@@ -160,2 +169,150 @@ There is also another way to render a component. | ||
### Buttons | ||
- Manage menu button can be placed inside a menu component using `BrManageMenuButton` component. | ||
```tsx | ||
import React from 'react'; | ||
import { Menu, Reference } from '@bloomreach/spa-sdk'; | ||
import { BrComponentContext, BrManageMenuButton, BrPageContext } from '@bloomreach/react-sdk'; | ||
interface MenuModels { | ||
menu: Reference; | ||
} | ||
export default function MenuComponent() { | ||
const component = React.useContext(BrComponentContext); | ||
const page = React.useContext(BrPageContext); | ||
const menuRef = component?.getModels<MenuModels>().menu; | ||
const menu = menuRef && page?.getContent<Menu>(menuRef); | ||
if (!menu) { | ||
return null; | ||
} | ||
return ( | ||
<ul className={page?.isPreview() ? 'has-edit-button' : ''}> | ||
{/* ... */} | ||
<BrManageMenuButton menu={menu} /> | ||
</ul> | ||
); | ||
} | ||
``` | ||
- Manage content button can be placed inside a component using `BrManageContentButton` component with non-empty `content` property. | ||
```tsx | ||
import React from 'react'; | ||
import { Document, Reference } from '@bloomreach/spa-sdk'; | ||
import { BrManageContentButton, BrProps } from '@bloomreach/react-sdk'; | ||
interface BannerModels { | ||
document: Reference; | ||
} | ||
export default function Banner({ component, page }: BrProps) { | ||
const { document: documentRef } = component.getModels<BannerModels>(); | ||
const document = documentRef && page.getContent<Document>(documentRef); | ||
return ( | ||
<div className={page.isPreview() ? 'has-edit-button' : ''}> | ||
{/* ... */} | ||
<BrManageContentButton | ||
content={document} | ||
documentTemplateQuery="new-banner-document" | ||
folderTemplateQuery="new-banner-folder" | ||
parameter="document" | ||
root="banners" | ||
relative | ||
/> | ||
</div> | ||
); | ||
} | ||
``` | ||
- Add new content button can be placed inside a component using `BrManageContentButton` directive but without passing a content entity. | ||
```tsx | ||
import React from 'react'; | ||
import { BrManageContentButton, BrProps } from '@bloomreach/react-sdk'; | ||
export default function News({ component, page }: BrProps) { | ||
// ... | ||
return ( | ||
<div className={page.isPreview() ? 'has-edit-button' : ''}> | ||
{/* ... */} | ||
<BrManageContentButton | ||
documentTemplateQuery="new-news-document" | ||
folderTemplateQuery="new-news-folder" | ||
root="news" | ||
/> | ||
</div> | ||
); | ||
} | ||
``` | ||
### React Native | ||
The SDK is fully compatible with [React Native](https://reactnative.dev/) framework, but there are some of the best practices. | ||
- It is impossible to use `<div>` elements in React Native, and it is recommended to use the `hst.nomarkup` container type in the backend configuration. | ||
- If there is a need to support other container types, those types should be overridden explicitly in the mapping. The default implementation is using HTML entities as described in the [documentation](https://documentation.bloomreach.com/library/concepts/template-composer/channel-editor-containers.html). | ||
```jsx | ||
import React from 'react'; | ||
import { View } from 'react-native'; | ||
import { TYPE_CONTAINER_BOX } from '@bloomreach/spa-sdk'; | ||
function MyBoxContainer() { | ||
return ( | ||
<View> | ||
{React.Children.map(props.children, child => ( | ||
<View> | ||
{child} | ||
</View> | ||
))} | ||
</View> | ||
); | ||
} | ||
export default function App() { | ||
return <BrPage mapping={{ [TYPE_CONTAINER_BOX]: MyBoxContainer }} />; | ||
} | ||
``` | ||
- The fallback mapping should be overridden to prevent errors on production when a new component type pops up on the backend since the default implementation is returning a plain text node. | ||
```jsx | ||
import React from 'react'; | ||
import { Text } from 'react-native'; | ||
import { TYPE_CONTAINER_ITEM_UNDEFINED } from '@bloomreach/spa-sdk'; | ||
function Fallback({ component, page }) { | ||
return page.isPreview() && <Text>Component "{component.getType()}" is not defined.</Text>; | ||
} | ||
export default function App() { | ||
return <BrPage mapping={{ [TYPE_CONTAINER_ITEM_UNDEFINED]: Fallback }} />; | ||
} | ||
``` | ||
- For integration with [the Relevance Module](https://documentation.bloomreach.com/14/library/enterprise/enterprise-features/targeting/targeting.html), the visitor identifier storing and passing should be handled on the application side. There is the `visitor` option in the [configuration](#configuration) that enables a setup without using cookies. | ||
```jsx | ||
import React, { useEffect, useMemo, useRef } from 'react'; | ||
import { read, write } from './storage'; | ||
export default function App() { | ||
const ref = useRef(null); | ||
const configuration = { | ||
/* ... */ | ||
visitor: read('visitor'), | ||
}; | ||
const visitor = ref.current | ||
&& ref.current.state.page | ||
&& ref.current.state.page.getVisitor(); | ||
useEffect(() => void visitor && write('visitor', visitor), [visitor && visitor.id]); | ||
return <BrPage ref={ref} configuration={configuration} />; | ||
} | ||
``` | ||
### Reference | ||
@@ -184,3 +341,3 @@ The React SDK is using [Bloomreach SPA SDK](https://www.npmjs.com/package/@bloomreach/spa-sdk#reference) to interact with the brXM. | ||
#### BrManageContentButton | ||
This component places a button on the page that opens the linked content in the document editor. | ||
This component places a button on the page that opens the linked content in the document editor or opens a document editor to create a new one. | ||
The button will only be shown in preview mode. | ||
@@ -190,3 +347,9 @@ | ||
--- | :---: | --- | ||
`content` | _yes_ | The content entity to open for editing. | ||
`content` | _no_ | The content entity to open for editing. | ||
`documentTemplateQuery` | _no_ | Template query to use for creating new documents. | ||
`folderTemplateQuery` | _no_ | Template query to use in case folders specified by `path` do not yet exist and must be created. | ||
`path` | _no_ | Initial location of a new document, relative to the `root`. | ||
`parameter` | _no_ | Name of the component parameter in which the document path is stored. | ||
`relative` | _no_ | Flag indicating that the picked value should be stored as a relative path. | ||
`root` | _no_ | Path to the root folder of selectable document locations. | ||
@@ -193,0 +356,0 @@ #### BrManageMenuButton |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
374
64575
27
7
1064
+ Added@bloomreach/spa-sdk@0.2.0-saas(transitive)
+ Addedcookie@0.4.2(transitive)
- Removed@bloomreach/spa-sdk@0.1.0-saas(transitive)