Preview: http://dashboards.webkom.co/react/airframe
This commit is contained in:
Tomasz Owczarczyk
2019-08-15 00:54:44 +02:00
parent f975443095
commit 37092d1d6c
626 changed files with 56691 additions and 0 deletions

275
app/components/Layout/Layout.js Executable file
View File

@@ -0,0 +1,275 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
import _ from 'lodash';
import { LayoutContent } from './LayoutContent';
import { LayoutNavbar } from './LayoutNavbar';
import { LayoutSidebar } from './LayoutSidebar';
import { PageConfigContext } from './PageConfigContext';
import { ThemeClass } from './../Theme';
import config from './../../../config';
const findChildByType = (children, targetType) => {
let result;
React.Children.forEach(children, (child) => {
if (child.type.layoutPartName === targetType.layoutPartName) {
result = child;
}
});
return result;
};
const findChildrenByType = (children, targetType) => {
return _.filter(React.Children.toArray(children), (child) =>
child.type.layoutPartName === targetType.layoutPartName);
};
const responsiveBreakpoints = {
'xs': { max: 575.8 },
'sm': { min: 576, max: 767.8 },
'md': { min: 768, max: 991.8 },
'lg': { min: 992, max: 1199.8 },
'xl': { min: 1200 }
};
class Layout extends React.Component {
static propTypes = {
children: PropTypes.node,
sidebarSlim: PropTypes.bool,
location: PropTypes.object,
favIcons: PropTypes.array
}
constructor(props) {
super(props);
this.state = {
sidebarHidden: false,
navbarHidden: false,
footerHidden: false,
sidebarCollapsed: false,
screenSize: '',
animationsDisabled: true,
pageTitle: null,
pageDescription: config.siteDescription,
pageKeywords: config.siteKeywords
};
this.lastLgSidebarCollapsed = false;
this.containerRef = React.createRef();
}
componentDidMount() {
// Determine the current window size
// and set it up in the context state
const layoutAdjuster = () => {
const { screenSize } = this.state;
let currentScreenSize;
_.forOwn(responsiveBreakpoints, (value, key) => {
const queryParts = [
`${ _.isUndefined(value.min) ? '' : `(min-width: ${value.min}px)` }`,
`${ _.isUndefined(value.max) ? '' : `(max-width: ${value.max}px)`}`
];
const query = _.compact(queryParts).join(' and ');
if (window.matchMedia(query).matches) {
currentScreenSize = key;
}
});
if (screenSize !== currentScreenSize) {
this.setState({ screenSize: currentScreenSize });
this.updateLayoutOnScreenSize(currentScreenSize);
}
};
// Add window initialization
if (typeof window !== 'undefined') {
window.addEventListener('resize', () => {
setTimeout(layoutAdjuster.bind(this), 0);
});
layoutAdjuster();
window.requestAnimationFrame(() => {
this.setState({ animationsDisabled: false });
});
}
// Add document initialization
if (typeof document !== 'undefined') {
this.bodyElement = document.body;
this.documentElement = document.documentElement;
}
}
componentDidUpdate(prevProps, prevState) {
// Prevent content scrolling in overlay mode
if (
this.bodyElement && this.documentElement && (
this.state.screenSize === 'xs' ||
this.state.screenSize === 'sm' ||
this.state.screenSize === 'md'
)
) {
if (prevState.sidebarCollapsed !== this.state.sidebarCollapsed) {
// Most of the devices
const styleUpdate = this.state.sidebarCollapsed ? {
overflowY: 'auto',
touchAction: 'auto'
}: {
overflowY: 'hidden',
touchAction: 'none'
}
Object.assign(this.bodyElement.style, styleUpdate);
Object.assign(this.documentElement.style, styleUpdate);
}
}
// After location change
if (prevProps.location.pathname !== this.props.location.pathname) {
// Scroll to top
if (this.bodyElement && this.documentElement) {
this.documentElement.scrollTop = this.bodyElement.scrollTop = 0;
}
// Hide the sidebar when in overlay mode
if (
!this.state.sidebarCollapsed && (
this.state.screenSize === 'xs' ||
this.state.screenSize === 'sm' ||
this.state.screenSize === 'md'
)
) {
// Add some time to prevent jank while the dom is updating
setTimeout(() => {
this.setState({ sidebarCollapsed: true });
}, 100);
}
}
// Update positions of STICKY navbars
this.updateNavbarsPositions();
}
updateLayoutOnScreenSize(screenSize) {
if (
screenSize === 'md' ||
screenSize === 'sm' ||
screenSize === 'xs'
) {
// Save for recovering to lg later
this.lastLgSidebarCollapsed = this.state.sidebarCollapsed;
this.setState({ sidebarCollapsed: true });
} else {
this.setState({ sidebarCollapsed: this.lastLgSidebarCollapsed });
}
}
updateNavbarsPositions() {
// eslint-disable-next-line react/no-find-dom-node
const containerElement = ReactDOM.findDOMNode(this.containerRef.current);
if (containerElement) {
const navbarElements = containerElement.querySelectorAll(":scope .layout__navbar");
// Calculate and update style.top of each navbar
let totalNavbarsHeight = 0;
navbarElements.forEach((navbarElement) => {
const navbarBox = navbarElement.getBoundingClientRect();
navbarElement.style.top = `${totalNavbarsHeight}px`;
totalNavbarsHeight += navbarBox.height;
});
}
}
toggleSidebar() {
this.setState({
sidebarCollapsed: !this.state.sidebarCollapsed
});
}
setElementsVisibility(elements) {
this.setState(_.pick(elements, ['sidebarHidden', 'navbarHidden', 'footerHidden']));
}
render() {
const { children, favIcons } = this.props;
const sidebar = findChildByType(children, LayoutSidebar);
const navbars = findChildrenByType(children, LayoutNavbar);
const content = findChildByType(children, LayoutContent);
const otherChildren = _.differenceBy(
React.Children.toArray(children),
[
sidebar,
...navbars,
content
],
'type'
);
const layoutClass = classNames('layout', 'layout--animations-enabled', {
//'layout--only-navbar': this.state.sidebarHidden && !this.state.navbarHidden
});
return (
<PageConfigContext.Provider
value={{
...this.state,
sidebarSlim: !!this.props.sidebarSlim && (
this.state.screenSize === 'lg' ||
this.state.screenSize === 'xl'
),
toggleSidebar: this.toggleSidebar.bind(this),
setElementsVisibility: this.setElementsVisibility.bind(this),
changeMeta: (metaData) => { this.setState(metaData) }
}}
>
<Helmet>
<meta charSet="utf-8" />
<title>{ config.siteTitle + (this.state.pageTitle ? ` - ${this.state.pageTitle}` : '') }</title>
<link rel="canonical" href={ config.siteCannonicalUrl } />
<meta name="description" content={ this.state.pageDescription } />
{
_.map(favIcons, (favIcon, index) => (
<link { ...favIcon } key={ index } />
))
}
</Helmet>
<ThemeClass>
{(themeClass) => (
<div className={ classNames(layoutClass, themeClass) } ref={ this.containerRef }>
{
!this.state.sidebarHidden && sidebar && React.cloneElement(sidebar, {
sidebarSlim: !!this.props.sidebarSlim && this.state.sidebarCollapsed && (
this.state.screenSize === 'lg' || this.state.screenSize === 'xl'
),
sidebarCollapsed: !this.props.sidebarSlim && this.state.sidebarCollapsed
})
}
<div className="layout__wrap">
{ !this.state.navbarHidden && navbars }
{ content }
</div>
{ otherChildren }
</div>
)}
</ThemeClass>
</PageConfigContext.Provider>
);
}
}
const routedLayout = withRouter(Layout);
export { routedLayout as Layout };

View File

@@ -0,0 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
const LayoutContent = (props) => (
<div className="layout__content">
{ props.children }
</div>
);
LayoutContent.propTypes = {
children: PropTypes.node
};
LayoutContent.layoutPartName = "content";
export {
LayoutContent
};

View File

@@ -0,0 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
const LayoutNavbar = (props) => {
const navbar = React.Children.only(props.children);
return (
<div className="layout__navbar">
{
React.cloneElement(navbar, { fixed: null })
}
</div>
);
};
LayoutNavbar.propTypes = {
children: PropTypes.node
};
LayoutNavbar.layoutPartName = "navbar";
export {
LayoutNavbar
};

View File

@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
const LayoutSidebar = (props) => {
const sidebarClass = classNames("layout__sidebar", {
"layout__sidebar--slim": props.sidebarSlim,
"layout__sidebar--collapsed": props.sidebarCollapsed
});
return (
<div className={ sidebarClass }>
{ props.children }
</div>
);
};
LayoutSidebar.propTypes = {
children: PropTypes.node,
sidebarSlim: PropTypes.bool,
sidebarCollapsed: PropTypes.bool
};
LayoutSidebar.layoutPartName = "sidebar";
export {
LayoutSidebar
};

View File

@@ -0,0 +1,7 @@
import React from 'react';
const PageConfigContext = React.createContext();
export {
PageConfigContext
};

17
app/components/Layout/index.js Executable file
View File

@@ -0,0 +1,17 @@
import { Layout } from './Layout';
import { LayoutContent } from './LayoutContent';
import { LayoutNavbar } from './LayoutNavbar';
import { LayoutSidebar } from './LayoutSidebar';
import { withPageConfig } from './withPageConfig';
import { setupPage } from './setupPage';
import { PageConfigContext } from './PageConfigContext';
Layout.Sidebar = LayoutSidebar;
Layout.Navbar = LayoutNavbar;
Layout.Content = LayoutContent;
const PageConfigProvider = PageConfigContext.Provider;
const PageConfigConsumer = PageConfigContext.Consumer;
export default Layout;
export { withPageConfig, setupPage, PageConfigProvider, PageConfigConsumer };

View File

@@ -0,0 +1,30 @@
import React from 'react';
import _ from 'lodash';
import PropTypes from 'prop-types';
import { withPageConfig } from './withPageConfig'
export const setupPage = (startupConfig) =>
(Component) => {
class PageSetupWrap extends React.Component {
static propTypes = {
pageConfig: PropTypes.object
}
componentDidMount() {
this.prevConfig = _.pick(this.props.pageConfig,
['pageTitle', 'pageDescription', 'pageKeywords']);
this.props.pageConfig.changeMeta(startupConfig);
}
componentWillUnmount() {
this.props.pageConfig.changeMeta(this.prevConfig);
}
render() {
return (
<Component { ...this.props } />
)
}
}
return withPageConfig(PageSetupWrap);
};

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { PageConfigContext } from './PageConfigContext';
export const withPageConfig = (Component) => {
const WithPageConfig = (props) => (
<PageConfigContext.Consumer>
{
(pageConfig) => <Component pageConfig={ pageConfig } { ...props } />
}
</PageConfigContext.Consumer>
);
return WithPageConfig;
};