Skip to content

Commit

Permalink
Merge pull request #246 from plone/improved-helmet-body-classname-app…
Browse files Browse the repository at this point in the history
…endable

Add helper BodyClass for appending classes to the body tag from View …
  • Loading branch information
sneridagh committed Jul 27, 2018
2 parents 3f134eb + 8846cb8 commit 60a4069
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Custom tiles support @sneridagh
* Add full register/password reset views @sneridagh
* Make the list block types configurable @robgietema
* Add helper `BodyClass` for appending classes to the `body` tag from View components @sneridagh

### Changes

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
"react-router": "3.0.5",
"react-router-redux": "4.0.8",
"react-share": "2.1.0",
"react-side-effect": "1.1.5",
"react-styleguidist": "7.0.1",
"redraft": "0.10.1",
"redux": "3.7.2",
Expand Down
3 changes: 2 additions & 1 deletion src/components/manage/Toolbar/Toolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Button, Divider, Menu } from 'semantic-ui-react';
import jwtDecode from 'jwt-decode';
import cookie from 'react-cookie';
import { injectIntl } from 'react-intl';

import { BodyClass } from '../../../helpers';
import LogoImage from './pastanaga.svg';

@injectIntl
Expand Down Expand Up @@ -92,6 +92,7 @@ export default class Toolbar extends Component {
return (
this.props.token && (
<Fragment>
<BodyClass className="has-toolbar" />
<Menu
vertical
borderless
Expand Down
9 changes: 2 additions & 7 deletions src/components/theme/App/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { asyncConnect } from 'redux-connect';
import Helmet from 'react-helmet';
import { Segment } from 'semantic-ui-react';
import { bindActionCreators } from 'redux';
import Raven from 'raven-js';

import Error from '../../../error';

import { Breadcrumbs, Footer, Header, Messages } from '../../../components';
import { getBaseUrl, getView } from '../../../helpers';
import { BodyClass, getBaseUrl, getView } from '../../../helpers';
import {
getBreadcrumbs,
getContent,
Expand Down Expand Up @@ -105,11 +104,7 @@ export class AppComponent extends Component {

return (
<Fragment>
<Helmet
bodyAttributes={{
class: `view-${action}view`,
}}
/>
<BodyClass className={`view-${action}view`} />
<Header pathname={path} />
<Breadcrumbs pathname={path} />
<Segment basic className="content-area">
Expand Down
14 changes: 1 addition & 13 deletions src/components/theme/Navigation/Navigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Menu, Segment } from 'semantic-ui-react';

import { Anontools } from '../../../components';
import { getNavigation } from '../../../actions';
import { getBaseUrl } from '../../../helpers';
import { BodyClass, getBaseUrl } from '../../../helpers';

const messages = defineMessages({
closeMobileMenu: {
Expand Down Expand Up @@ -70,7 +70,6 @@ export default class Navigation extends Component {
this.closeMobileMenu = this.closeMobileMenu.bind(this);
this.state = {
isMobileMenuOpen: false,
bodyClasses: null,
};
}

Expand Down Expand Up @@ -114,11 +113,6 @@ export default class Navigation extends Component {
* @returns {undefined}
*/
toggleMobileMenu() {
this.setState({
bodyClasses:
Helmet.peek().bodyAttributes.class +
(!this.state.isMobileMenuOpen ? ' open-mobile-menu' : ''),
});
this.setState({ isMobileMenuOpen: !this.state.isMobileMenuOpen });
}

Expand All @@ -131,9 +125,6 @@ export default class Navigation extends Component {
if (!this.state.isMobileMenuOpen) {
return;
}
this.setState({
bodyClasses: Helmet.peek().bodyAttributes.class,
});
this.setState({ isMobileMenuOpen: false });
}

Expand All @@ -145,9 +136,6 @@ export default class Navigation extends Component {
render() {
return (
<Fragment>
{this.state.isMobileMenuOpen && (
<Helmet bodyAttributes={{ class: this.state.bodyClasses }} />
)}
<div className="hamburger-wrapper mobile only">
<button
className={
Expand Down
13 changes: 6 additions & 7 deletions src/components/theme/View/View.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import { Portal } from 'react-portal';
import { Link } from 'react-router';
Expand All @@ -26,7 +25,7 @@ import {
Workflow,
} from '../../../components';
import { listActions, getContent } from '../../../actions';
import { getBaseUrl } from '../../../helpers';
import { BodyClass, getBaseUrl } from '../../../helpers';

@injectIntl
@connect(
Expand Down Expand Up @@ -219,12 +218,12 @@ export default class View extends Component {

return (
<div id="view">
<Helmet
bodyAttributes={{
class: RenderedView.displayName
<BodyClass
className={
RenderedView.displayName
? `view-${RenderedView.displayName.toLowerCase()}`
: 'view-undefined',
}}
: null
}
/>

<RenderedView
Expand Down
66 changes: 66 additions & 0 deletions src/helpers/BodyClass/BodyClass.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Component, Children } from 'react';
import PropTypes from 'prop-types';
import withSideEffect from 'react-side-effect';

/**
* @export
* @class BodyClass
* @extends {Component}
*/
class BodyClass extends Component {
/**
* Render method.
* @method render
* @returns {string} Markup for the component.
*/
render() {
if (this.props.children) {
return Children.only(this.props.children);
}
return null;
}
}

BodyClass.propTypes = {
children: PropTypes.element,
className: PropTypes.string,
};

BodyClass.defaultProps = {
children: null,
className: null,
};

/**
* reducePropsToState
* @function reducePropsToState
* @param {*} propsList propsList
* @returns {List} classList
*/
function reducePropsToState(propsList) {
let classList = [];
propsList.map(props => {
if (props.className) {
classList = classList.concat(props.className);
}
});
return classList;
}

/**
* handleStateChangeOnClient
* @function handleStateChangeOnClient
* @param {*} classList classList
* @returns {null} null
*/
function handleStateChangeOnClient(classList) {
classList.map(className => {
if (!document.body.classList.contains(className)) {
document.body.classList.add(className);
}
});
}

export default withSideEffect(reducePropsToState, handleStateChangeOnClient)(
BodyClass,
);
10 changes: 4 additions & 6 deletions src/helpers/Html/Html.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import PropTypes from 'prop-types';
import ReactDOM from 'react-dom/server';
import Helmet from 'react-helmet';
import serialize from 'serialize-javascript';
import { join } from 'lodash';
import { BodyClass } from '../.';

/**
* Html class.
Expand All @@ -28,11 +30,7 @@ import serialize from 'serialize-javascript';
export const Html = ({ assets, component, store }) => {
const content = ReactDOM.renderToString(component);
const head = Helmet.rewind();
// Workaround for testing
// Otherwise we get TypeError: _reactHelmet2.default.renderStatic is not a function
const helmet =
process.env.NODE_ENV === 'production' ? Helmet.renderStatic() : null;
const bodyAttrs = helmet && helmet.bodyAttributes.toComponent();
const bodyClass = join(BodyClass.rewind(), ' ');

return (
<html lang="en">
Expand Down Expand Up @@ -64,7 +62,7 @@ export const Html = ({ assets, component, store }) => {
charSet="UTF-8"
/>
</head>
<body {...bodyAttrs}>
<body className={bodyClass}>
<div id="toolbar" />
<div id="main" dangerouslySetInnerHTML={{ __html: content }} />
<script
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/Html/Html.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ jest.mock('react-helmet', () => ({
}),
}));

jest.mock('../BodyClass/BodyClass', () => ({
rewind: () => ['class1', 'class2'],
}));

describe('Html', () => {
it('renders a html component', () => {
const component = renderer.create(
Expand Down
4 changes: 3 additions & 1 deletion src/helpers/Html/__snapshots__/Html.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ exports[`Html renders a html component 1`] = `
type="text/css"
/>
</head>
<body>
<body
className="class1 class2"
>
<div
id="toolbar"
/>
Expand Down
1 change: 1 addition & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { Html } from './Html/Html';
export { getAuthToken, persistAuthToken } from './AuthToken/AuthToken';
export { getBaseUrl, getIcon, getView } from './Url/Url';
export { generateSitemap } from './Sitemap/Sitemap';
export BodyClass from './BodyClass/BodyClass';
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10507,6 +10507,13 @@ react-share@2.1.0:
jsonp "^0.2.1"
prop-types "^15.5.8"

react-side-effect@1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d"
dependencies:
exenv "^1.2.1"
shallowequal "^1.0.1"

react-side-effect@^1.1.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.3.tgz#512c25abe0dec172834c4001ec5c51e04d41bc5c"
Expand Down

0 comments on commit 60a4069

Please sign in to comment.