19
.babelrc
Executable file
19
.babelrc
Executable file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"universal-import",
|
||||
"react-hot-loader/babel"
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": [
|
||||
"react-hot-loader/babel"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
26
.eslintrc
Executable file
26
.eslintrc
Executable file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
"experimentalObjectRestSpread": true
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"rules": {
|
||||
}
|
||||
}
|
10
.gitignore
vendored
Executable file
10
.gitignore
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
# build output
|
||||
dist
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# other
|
||||
.DS_Store
|
1
.npmrc
Executable file
1
.npmrc
Executable file
@@ -0,0 +1 @@
|
||||
//registry.npmjs.org/:_authToken=57c97b55-1617-4b16-9f1b-2bffb2cdcc7e
|
14
app/colors.js
Executable file
14
app/colors.js
Executable file
@@ -0,0 +1,14 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import colors from './colors.scss';
|
||||
|
||||
const colorKeys = _
|
||||
.chain(colors)
|
||||
.keys()
|
||||
.filter((colorKey) => (
|
||||
colorKey.indexOf('bg-') === -1 &&
|
||||
colorKey.indexOf('fg-') === -1
|
||||
))
|
||||
.value();
|
||||
|
||||
export default _.pick(colors, colorKeys);
|
16
app/colors.scss
Executable file
16
app/colors.scss
Executable file
@@ -0,0 +1,16 @@
|
||||
@import "./styles/variables";
|
||||
|
||||
@each $name, $color in $dashboard-colors {
|
||||
.fg-color--#{ $name } {
|
||||
color: $color;
|
||||
}
|
||||
.bg-color--#{ $name } {
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
:export {
|
||||
@each $name, $color in $dashboard-colors {
|
||||
#{$name}: $color
|
||||
}
|
||||
}
|
3
app/common.js
Executable file
3
app/common.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import * as CommonDashboardFuncs from '@owczar/dashboard-style--airframe';
|
||||
|
||||
export default CommonDashboardFuncs;
|
62
app/components/Accordion/Accordion.js
Executable file
62
app/components/Accordion/Accordion.js
Executable file
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Card from './../Card';
|
||||
|
||||
import { Provider } from './context';
|
||||
|
||||
export class Accordion extends React.Component {
|
||||
static propTypes = {
|
||||
initialOpen: PropTypes.bool,
|
||||
onToggle: PropTypes.func,
|
||||
open: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isOpen: props.initialOpen
|
||||
}
|
||||
|
||||
if (props.open !== 'undefined' && props.onToggle === 'undefined') {
|
||||
throw "Accordion: props.open has to be used combined with props.onToggle " +
|
||||
"use props.initialOpen to create an uncontrolled Accordion."
|
||||
}
|
||||
}
|
||||
|
||||
toggleHandler() {
|
||||
const { onToggle } = this.props;
|
||||
|
||||
if (!onToggle) {
|
||||
this.setState({ isOpen: !this.state.isOpen });
|
||||
} else {
|
||||
this.onToggle(!this.props.open);
|
||||
}
|
||||
}
|
||||
|
||||
isOpen() {
|
||||
return !this.props.onToggle ?
|
||||
this.state.isOpen : this.props.open;
|
||||
}
|
||||
|
||||
render() {
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const { className, children, initialOpen, ...otherProps } = this.props;
|
||||
|
||||
return (
|
||||
<Provider
|
||||
value={{
|
||||
onToggle: this.toggleHandler.bind(this),
|
||||
isOpen: this.isOpen()
|
||||
}}
|
||||
>
|
||||
<Card className={ className } { ...otherProps }>
|
||||
{ children }
|
||||
</Card>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
24
app/components/Accordion/AccordionBody.js
Executable file
24
app/components/Accordion/AccordionBody.js
Executable file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Collapse, CardBody } from 'reactstrap';
|
||||
|
||||
import { Consumer } from './context';
|
||||
|
||||
export const AccordionBody = (props) => (
|
||||
<Consumer>
|
||||
{
|
||||
({ isOpen }) => (
|
||||
<Collapse isOpen={ isOpen }>
|
||||
<CardBody className={ classNames(props.className, 'pt-0') }>
|
||||
{ props.children }
|
||||
</CardBody>
|
||||
</Collapse>
|
||||
)
|
||||
}
|
||||
</Consumer>
|
||||
);
|
||||
AccordionBody.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
33
app/components/Accordion/AccordionHeader.js
Executable file
33
app/components/Accordion/AccordionHeader.js
Executable file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import CardHeader from './../CardHeader';
|
||||
|
||||
import { Consumer } from './context';
|
||||
import classes from './AccordionHeader.scss';
|
||||
|
||||
export const AccordionHeader = (props) => (
|
||||
<Consumer>
|
||||
{
|
||||
({ onToggle }) => (
|
||||
<CardHeader
|
||||
className={
|
||||
classNames(
|
||||
props.className,
|
||||
classes.header
|
||||
)
|
||||
}
|
||||
onClick={ onToggle}
|
||||
>
|
||||
{ props.children }
|
||||
</CardHeader>
|
||||
)
|
||||
}
|
||||
</Consumer>
|
||||
);
|
||||
AccordionHeader.propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClick: PropTypes.func,
|
||||
className: PropTypes.string
|
||||
};
|
14
app/components/Accordion/AccordionHeader.scss
Executable file
14
app/components/Accordion/AccordionHeader.scss
Executable file
@@ -0,0 +1,14 @@
|
||||
@import '../../styles/variables';
|
||||
|
||||
div.header {
|
||||
background: none;
|
||||
border-bottom: none;
|
||||
cursor: pointer;
|
||||
color: $link-color;
|
||||
text-decoration: $link-decoration;
|
||||
|
||||
&:hover {
|
||||
color: $link-hover-color;
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
}
|
27
app/components/Accordion/AccordionIndicator.js
Executable file
27
app/components/Accordion/AccordionIndicator.js
Executable file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Consumer } from './context';
|
||||
|
||||
export const AccordionIndicator = (props) => (
|
||||
<Consumer>
|
||||
{
|
||||
({ isOpen }) => isOpen ?
|
||||
React.cloneElement(props.open, {
|
||||
className: classNames(props.className, props.open.props.className)
|
||||
}) : React.cloneElement(props.closed, {
|
||||
className: classNames(props.className, props.closed.props.className)
|
||||
})
|
||||
}
|
||||
</Consumer>
|
||||
);
|
||||
AccordionIndicator.propTypes = {
|
||||
open: PropTypes.node,
|
||||
closed: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
}
|
||||
AccordionIndicator.defaultProps = {
|
||||
open: <i className="fa fa-fw fa-minus"></i>,
|
||||
closed: <i className="fa fa-fw fa-plus"></i>
|
||||
}
|
8
app/components/Accordion/context.js
Executable file
8
app/components/Accordion/context.js
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const { Provider, Consumer } = React.createContext();
|
||||
|
||||
export {
|
||||
Provider,
|
||||
Consumer
|
||||
};
|
10
app/components/Accordion/index.js
Executable file
10
app/components/Accordion/index.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { Accordion } from './Accordion';
|
||||
import { AccordionBody } from './AccordionBody';
|
||||
import { AccordionHeader } from './AccordionHeader';
|
||||
import { AccordionIndicator } from './AccordionIndicator';
|
||||
|
||||
Accordion.Body = AccordionBody;
|
||||
Accordion.Header = AccordionHeader;
|
||||
Accordion.Indicator = AccordionIndicator;
|
||||
|
||||
export default Accordion;
|
20
app/components/App/AppClient.js
Executable file
20
app/components/App/AppClient.js
Executable file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { hot } from 'react-hot-loader'
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import AppLayout from './../../layout/default';
|
||||
import { RoutedContent } from './../../routes';
|
||||
|
||||
const basePath = process.env.BASE_PATH || '/';
|
||||
|
||||
const AppClient = () => {
|
||||
return (
|
||||
<Router basename={ basePath }>
|
||||
<AppLayout>
|
||||
<RoutedContent />
|
||||
</AppLayout>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default hot(module)(AppClient);
|
3
app/components/App/index.js
Executable file
3
app/components/App/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import AppClient from './AppClient';
|
||||
|
||||
export default AppClient;
|
71
app/components/Avatar/Avatar.js
Executable file
71
app/components/Avatar/Avatar.js
Executable file
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
const Avatar = (props) => {
|
||||
const avatarClass = classNames(
|
||||
'avatar',
|
||||
`avatar--${ props.size }`,
|
||||
props.className
|
||||
);
|
||||
const addOnsdArr = React.Children.toArray(props.addOns);
|
||||
const badge = _.find(addOnsdArr, (avatarAddOn) =>
|
||||
avatarAddOn.type.addOnId === "avatar--badge");
|
||||
const icons = _.filter(addOnsdArr, (avatarAddOn) =>
|
||||
avatarAddOn.type.addOnId === "avatar--icon");
|
||||
const isNested = _.reduce(addOnsdArr, (acc, avatarAddOn) =>
|
||||
acc || !!avatarAddOn.props.small, false);
|
||||
|
||||
return (
|
||||
<div className={ avatarClass } style={ props.style }>
|
||||
{
|
||||
badge && (
|
||||
<div className="avatar__badge">
|
||||
{ badge }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!_.isEmpty(icons) && (() => {
|
||||
switch(icons.length) {
|
||||
case 1:
|
||||
return (
|
||||
<div className="avatar__icon">
|
||||
{ _.first(icons) }
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classNames({
|
||||
'avatar__icon--nested': isNested,
|
||||
}, 'avatar__icon', 'avatar__icon--stack')
|
||||
}
|
||||
>
|
||||
{ icons }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})()
|
||||
}
|
||||
<div className='avatar__content'>
|
||||
{ props.children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Avatar.propTypes = {
|
||||
size: PropTypes.string,
|
||||
children: PropTypes.node.isRequired,
|
||||
addOns: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
className: PropTypes.string
|
||||
};
|
||||
Avatar.defaultProps = {
|
||||
size: "md",
|
||||
style: {}
|
||||
};
|
||||
|
||||
export { Avatar };
|
21
app/components/Avatar/AvatarAddonBadge.js
Executable file
21
app/components/Avatar/AvatarAddonBadge.js
Executable file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Badge } from 'reactstrap';
|
||||
|
||||
const AvatarAddonBadge = (props) => {
|
||||
const { children, ...badgeProps } = props;
|
||||
|
||||
return (
|
||||
<Badge {...badgeProps}>
|
||||
{ children }
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
AvatarAddonBadge.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
AvatarAddonBadge.addOnId = "avatar--badge";
|
||||
|
||||
export { AvatarAddonBadge };
|
26
app/components/Avatar/AvatarAddonIcon.js
Executable file
26
app/components/Avatar/AvatarAddonIcon.js
Executable file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import avatarColors from './../../colors.scss';
|
||||
|
||||
const AvatarAddonIcon = (props) => {
|
||||
const addOnClass = classNames({
|
||||
'avatar__icon__inner': props.small
|
||||
}, avatarColors[`fg-color--${ props.color }`]);
|
||||
|
||||
return (
|
||||
<i className={ classNames(addOnClass, props.className) }></i>
|
||||
);
|
||||
};
|
||||
AvatarAddonIcon.propTypes = {
|
||||
small: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
};
|
||||
AvatarAddonIcon.defaultProps = {
|
||||
color: "success"
|
||||
};
|
||||
AvatarAddonIcon.addOnId = "avatar--icon";
|
||||
|
||||
export { AvatarAddonIcon };
|
65
app/components/Avatar/AvatarFont.js
Executable file
65
app/components/Avatar/AvatarFont.js
Executable file
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Avatar } from './Avatar';
|
||||
|
||||
import avatarColors from './../../colors.scss';
|
||||
|
||||
const AvatarFont = (props) => {
|
||||
const {
|
||||
children,
|
||||
bgColor,
|
||||
fgColor,
|
||||
bgColorCustom,
|
||||
fgColorCustom,
|
||||
...avatarProps
|
||||
} = props;
|
||||
const parentClass = classNames(
|
||||
'avatar-font',
|
||||
`avatar-font--${avatarProps.size}`,
|
||||
bgColor && avatarColors[`bg-color--${ bgColor }`]
|
||||
);
|
||||
const childClass = classNames('avatar-font__text',
|
||||
fgColor && avatarColors[`fg-color--${ fgColor }`]
|
||||
);
|
||||
const parentCustomStyle = bgColorCustom ? {
|
||||
backgroundColor: bgColorCustom
|
||||
} : { };
|
||||
const childCustomStyle = fgColorCustom ? {
|
||||
color: fgColorCustom
|
||||
} : { };
|
||||
const child = (
|
||||
<span>
|
||||
{ children }
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<Avatar { ...avatarProps }>
|
||||
<div className={ parentClass } style={parentCustomStyle}>
|
||||
{
|
||||
React.cloneElement(child, {
|
||||
style: childCustomStyle,
|
||||
className: classNames(child.props.className, childClass)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
AvatarFont.propTypes = {
|
||||
children: PropTypes.node,
|
||||
bgColor: PropTypes.string,
|
||||
fgColor: PropTypes.string,
|
||||
bgColorCustom: PropTypes.string,
|
||||
fgColorCustom: PropTypes.string,
|
||||
...Avatar.propTypes
|
||||
};
|
||||
AvatarFont.defaultProps = {
|
||||
bgColor: '400',
|
||||
fgColor: 'white',
|
||||
size: 'md'
|
||||
};
|
||||
|
||||
export { AvatarFont };
|
57
app/components/Avatar/AvatarImage.js
Executable file
57
app/components/Avatar/AvatarImage.js
Executable file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Avatar } from './Avatar';
|
||||
import { AvatarFont } from './AvatarFont';
|
||||
|
||||
class AvatarImage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
placeholder: PropTypes.node,
|
||||
alt: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
..._.omit(Avatar.propTypes, ['children'])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
placeholder: <i className="fa fa-user fa-fw"></i>
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
imgLoaded: false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { src, placeholder, alt, className, ...avatarProps } = this.props;
|
||||
const parentClass = classNames('avatar-image', {
|
||||
'avatar-image--loaded': this.state.imgLoaded
|
||||
}, className);
|
||||
|
||||
return (
|
||||
<div className={ parentClass }>
|
||||
<Avatar className="avatar-image__image" {...avatarProps}>
|
||||
<img
|
||||
src={ src }
|
||||
alt={ alt }
|
||||
onLoad={ () => { this.setState({ imgLoaded: true }) } }
|
||||
/>
|
||||
</Avatar>
|
||||
{
|
||||
!this.state.imgLoaded && (
|
||||
<AvatarFont className="avatar-image__placeholder" {...avatarProps}>
|
||||
{ placeholder }
|
||||
</AvatarFont>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export { AvatarImage };
|
17
app/components/Avatar/index.js
Executable file
17
app/components/Avatar/index.js
Executable file
@@ -0,0 +1,17 @@
|
||||
import { Avatar } from './Avatar';
|
||||
import { AvatarFont } from './AvatarFont';
|
||||
import { AvatarImage } from './AvatarImage';
|
||||
|
||||
import { AvatarAddonBadge } from './AvatarAddonBadge';
|
||||
import { AvatarAddonIcon } from './AvatarAddonIcon';
|
||||
|
||||
Avatar.Font = AvatarFont;
|
||||
Avatar.Image = AvatarImage;
|
||||
|
||||
const AvatarAddOn = {
|
||||
Icon: AvatarAddonIcon,
|
||||
Badge: AvatarAddonBadge
|
||||
};
|
||||
|
||||
export default Avatar;
|
||||
export { AvatarAddOn };
|
33
app/components/Card/Card.js
Executable file
33
app/components/Card/Card.js
Executable file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Card as BsCard
|
||||
} from 'reactstrap';
|
||||
|
||||
import classes from './Card.scss';
|
||||
|
||||
const Card = (props) => {
|
||||
const { children, type, color, className, ...otherProps } = props;
|
||||
const cardClass = classNames(className,
|
||||
classes['custom-card'],
|
||||
classes[`custom-card--${ type }`],
|
||||
color && classes[`custom-card--color-${ color }`]
|
||||
);
|
||||
return (
|
||||
<BsCard className={ cardClass } { ...otherProps }>
|
||||
{ children }
|
||||
</BsCard>
|
||||
);
|
||||
}
|
||||
Card.propTypes = {
|
||||
...BsCard.propTypes,
|
||||
type: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
};
|
||||
Card.defaultProps = {
|
||||
type: 'border',
|
||||
color: null
|
||||
};
|
||||
|
||||
export { Card };
|
52
app/components/Card/Card.scss
Executable file
52
app/components/Card/Card.scss
Executable file
@@ -0,0 +1,52 @@
|
||||
@import "../../styles/variables";
|
||||
|
||||
.custom-card {
|
||||
&--border-dash,
|
||||
&--border-dot,
|
||||
&--border {
|
||||
@each $name, $color in $dashboard-colors {
|
||||
&.custom-card--color-#{$name} {
|
||||
border-color: $color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--side-border {
|
||||
border-left-width: 2px;
|
||||
|
||||
@each $name, $color in $dashboard-colors {
|
||||
&.custom-card--color-#{$name} {
|
||||
border-left-color: $color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--background {
|
||||
color: map-get($dashboard-colors, 'white');
|
||||
|
||||
@each $name, $color in $dashboard-colors {
|
||||
&.custom-card--color-#{$name} {
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--border-dash {
|
||||
border-style: dashed;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
&--border-dot {
|
||||
border-style: dotted;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
&--shadow {
|
||||
box-shadow: 0 1px 2px 0 rgba(31, 45, 61, 0.07);
|
||||
}
|
||||
|
||||
&--none {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
}
|
3
app/components/Card/index.js
Executable file
3
app/components/Card/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { Card } from './Card';
|
||||
|
||||
export default Card;
|
30
app/components/CardHeader/CardHeader.js
Executable file
30
app/components/CardHeader/CardHeader.js
Executable file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
CardHeader as BsCardHeader
|
||||
} from 'reactstrap';
|
||||
|
||||
import classes from './CardHeader.scss';
|
||||
|
||||
const CardHeader = (props) => {
|
||||
const { type, color, className, children, ...otherProps } = props;
|
||||
const cardHeaderClass = classNames(className,
|
||||
classes['custom-card-header'],
|
||||
type && classes[`custom-card-header--${ type }`],
|
||||
color && classes[`custom-card-header--color-${ color }`]
|
||||
);
|
||||
return (
|
||||
<BsCardHeader className={ cardHeaderClass } { ...otherProps }>
|
||||
{ children }
|
||||
</BsCardHeader>
|
||||
);
|
||||
};
|
||||
CardHeader.propTypes = {
|
||||
type: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
...BsCardHeader.propTypes
|
||||
};
|
||||
|
||||
export { CardHeader };
|
24
app/components/CardHeader/CardHeader.scss
Executable file
24
app/components/CardHeader/CardHeader.scss
Executable file
@@ -0,0 +1,24 @@
|
||||
@import "../../styles/variables";
|
||||
|
||||
.custom-card-header {
|
||||
&--background {
|
||||
color: map-get($dashboard-colors, "white");
|
||||
|
||||
@each $name, $color in $dashboard-colors {
|
||||
&.custom-card-header--color-#{$name} {
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--border {
|
||||
@each $name, $color in $dashboard-colors {
|
||||
border-bottom: 2px solid;
|
||||
background: none;
|
||||
|
||||
&.custom-card-header--color-#{$name} {
|
||||
border-bottom-color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
app/components/CardHeader/index.js
Executable file
3
app/components/CardHeader/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { CardHeader } from './CardHeader';
|
||||
|
||||
export default CardHeader;
|
45
app/components/Checkable/Checkable.js
Executable file
45
app/components/Checkable/Checkable.js
Executable file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Provider } from './context';
|
||||
|
||||
class Checkable extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
tag: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.string
|
||||
])
|
||||
};
|
||||
static defaultProps = {
|
||||
tag: "div"
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isChecked: false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { tag, children, ...otherProps } = this.props;
|
||||
const Tag = this.props.tag;
|
||||
|
||||
return (
|
||||
<Provider
|
||||
value={{
|
||||
isChecked: this.state.isChecked,
|
||||
toggle: (enabled) => { this.setState({ isChecked: enabled || !this.state.isChecked }) }
|
||||
}}
|
||||
>
|
||||
<Tag { ...otherProps }>
|
||||
{ children }
|
||||
</Tag>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Checkable };
|
53
app/components/Checkable/CheckableInput.js
Executable file
53
app/components/Checkable/CheckableInput.js
Executable file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Input } from 'reactstrap';
|
||||
|
||||
import { Consumer } from './context';
|
||||
|
||||
class CheckableInput extends React.Component {
|
||||
static propTypes = {
|
||||
tag: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.string
|
||||
]),
|
||||
type: PropTypes.string,
|
||||
defaultChecked: PropTypes.bool,
|
||||
toggle: PropTypes.func,
|
||||
isChecked: PropTypes.bool
|
||||
};
|
||||
static defaultProps = {
|
||||
tag: Input,
|
||||
type: "checkbox"
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.defaultChecked) {
|
||||
this.props.toggle(this.props.defaultChecked);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { tag, isChecked, toggle, ...otherProps } = this.props;
|
||||
const Tag = tag;
|
||||
|
||||
return (
|
||||
<Tag
|
||||
checked={ isChecked }
|
||||
onChange={ (e) => { toggle(e.target.checked) } }
|
||||
{ ...otherProps }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ContextCheckableInput = (props) => (
|
||||
<Consumer>
|
||||
{
|
||||
(value) => (
|
||||
<CheckableInput { ...{ ...props, ...value } } />
|
||||
)
|
||||
}
|
||||
</Consumer>
|
||||
);
|
||||
|
||||
export { ContextCheckableInput as CheckableInput };
|
38
app/components/Checkable/CheckableTrigger.js
Executable file
38
app/components/Checkable/CheckableTrigger.js
Executable file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Consumer } from './context';
|
||||
|
||||
import classes from './CheckableTrigger.scss';
|
||||
|
||||
const CheckableTrigger = (props) => {
|
||||
const { children, tag, className, ...otherProps } = props;
|
||||
const Tag = tag;
|
||||
const tagClass = classNames(classes['checkable__trigger'], className);
|
||||
|
||||
return (
|
||||
<Consumer>
|
||||
{
|
||||
(value) => (
|
||||
<Tag { ...otherProps } className={ tagClass } onClick={ () => { value.toggle() } }>
|
||||
{ children }
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
</Consumer>
|
||||
);
|
||||
};
|
||||
CheckableTrigger.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
tag: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.string
|
||||
])
|
||||
};
|
||||
CheckableTrigger.defaultProps = {
|
||||
tag: "div"
|
||||
};
|
||||
|
||||
export { CheckableTrigger };
|
3
app/components/Checkable/CheckableTrigger.scss
Executable file
3
app/components/Checkable/CheckableTrigger.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.checkable__trigger {
|
||||
cursor: pointer;
|
||||
}
|
8
app/components/Checkable/context.js
Executable file
8
app/components/Checkable/context.js
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const { Provider, Consumer } = React.createContext();
|
||||
|
||||
export {
|
||||
Provider,
|
||||
Consumer
|
||||
};
|
8
app/components/Checkable/index.js
Executable file
8
app/components/Checkable/index.js
Executable file
@@ -0,0 +1,8 @@
|
||||
import { Checkable } from './Checkable';
|
||||
import { CheckableTrigger } from './CheckableTrigger';
|
||||
import { CheckableInput } from './CheckableInput';
|
||||
|
||||
Checkable.Trigger = CheckableTrigger;
|
||||
Checkable.Input = CheckableInput;
|
||||
|
||||
export default Checkable;
|
17
app/components/CustomInput/CustomInput.js
Executable file
17
app/components/CustomInput/CustomInput.js
Executable file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { CustomInput as RSCustomInput } from 'reactstrap';
|
||||
|
||||
const CustomInput = (props) => {
|
||||
const { className, ...otherProps } = props;
|
||||
const inputClass = classNames(className, {
|
||||
'custom-control-empty': !props.label
|
||||
});
|
||||
|
||||
return (
|
||||
<RSCustomInput className={ inputClass } { ...otherProps } />
|
||||
);
|
||||
}
|
||||
CustomInput.propTypes = { ...RSCustomInput.propTypes };
|
||||
|
||||
export { CustomInput };
|
3
app/components/CustomInput/index.js
Executable file
3
app/components/CustomInput/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { CustomInput } from './CustomInput';
|
||||
|
||||
export default CustomInput;
|
22
app/components/Divider/Divider.js
Executable file
22
app/components/Divider/Divider.js
Executable file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const Divider = ({ position, children, className }) => {
|
||||
const dividerClass = classNames({
|
||||
'hr-text-center': position === 'center',
|
||||
'hr-text-right': position === 'right'
|
||||
}, 'hr-text', className);
|
||||
|
||||
return (
|
||||
<div className={ dividerClass }>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Divider.propTypes = {
|
||||
position: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
}
|
3
app/components/Divider/index.js
Executable file
3
app/components/Divider/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { Divider } from './Divider';
|
||||
|
||||
export default Divider;
|
47
app/components/EmptyLayout/EmptyLayout.js
Executable file
47
app/components/EmptyLayout/EmptyLayout.js
Executable file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
withPageConfig
|
||||
} from './../../components/Layout';
|
||||
|
||||
class EmptyLayout extends React.Component {
|
||||
static propTypes = {
|
||||
pageConfig: PropTypes.object.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.pageConfig.setElementsVisibility({
|
||||
navbarHidden: true,
|
||||
sidebarHidden: true,
|
||||
footerHidden: true
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.pageConfig.setElementsVisibility({
|
||||
navbarHidden: false,
|
||||
sidebarHidden: false,
|
||||
footerHidden: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const emptyLayoutClass = classNames('fullscreen', this.props.className);
|
||||
|
||||
return (
|
||||
<div className={ emptyLayoutClass }>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const PageConfigEmptyLayout = withPageConfig(EmptyLayout);
|
||||
|
||||
export {
|
||||
PageConfigEmptyLayout as EmptyLayout
|
||||
};
|
32
app/components/EmptyLayout/EmptyLayoutSection.js
Executable file
32
app/components/EmptyLayout/EmptyLayoutSection.js
Executable file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const EmptyLayoutSection = (props) => {
|
||||
const sectionClass = classNames(props.className, 'fullscreen__section', {
|
||||
'fullscreen__section--center': props.center
|
||||
});
|
||||
const maxWidth = _.isNumber(props.width) ? `${props.width}px` : props.width;
|
||||
return (
|
||||
<div className={ sectionClass }>
|
||||
{
|
||||
props.center ?
|
||||
<div className="fullscrenn__section__child" style={{ maxWidth }}>
|
||||
{ props.children }
|
||||
</div> : props.children
|
||||
}
|
||||
</div>
|
||||
)
|
||||
};
|
||||
EmptyLayoutSection.propTypes = {
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node.isRequired,
|
||||
center: PropTypes.bool,
|
||||
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||
};
|
||||
EmptyLayoutSection.defaultProps = {
|
||||
width: '420px'
|
||||
}
|
||||
|
||||
export { EmptyLayoutSection };
|
6
app/components/EmptyLayout/index.js
Executable file
6
app/components/EmptyLayout/index.js
Executable file
@@ -0,0 +1,6 @@
|
||||
import { EmptyLayout } from './EmptyLayout';
|
||||
import { EmptyLayoutSection } from './EmptyLayoutSection';
|
||||
|
||||
EmptyLayout.Section = EmptyLayoutSection;
|
||||
|
||||
export default EmptyLayout;
|
17
app/components/ExtendedDropdown/ExtendedDropdown.js
Executable file
17
app/components/ExtendedDropdown/ExtendedDropdown.js
Executable file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { DropdownMenu } from 'reactstrap';
|
||||
|
||||
export const ExtendedDropdown = ({ className, ...otherProps }) => {
|
||||
const classes = classNames(
|
||||
className,
|
||||
'extended-dropdown'
|
||||
);
|
||||
return (
|
||||
<DropdownMenu className={ classes } { ...otherProps } />
|
||||
);
|
||||
}
|
||||
ExtendedDropdown.propTypes = {
|
||||
className: PropTypes.string,
|
||||
};
|
23
app/components/ExtendedDropdown/ExtendedDropdownLink.js
Executable file
23
app/components/ExtendedDropdown/ExtendedDropdownLink.js
Executable file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DropdownContext } from 'reactstrap/es/DropdownContext';
|
||||
|
||||
const ExtendedDropdownLink = (props) => {
|
||||
const { children, ...otherProps } = props;
|
||||
|
||||
return (
|
||||
<DropdownContext.Consumer>
|
||||
{
|
||||
({ toggle }) => (
|
||||
<Link { ...otherProps } onClick={ () => { toggle(); } }>
|
||||
{ children }
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</DropdownContext.Consumer>
|
||||
);
|
||||
};
|
||||
ExtendedDropdownLink.propTypes = { ...Link.propTypes };
|
||||
ExtendedDropdownLink.defaultProps = { ...Link.defaultProps };
|
||||
|
||||
export { ExtendedDropdownLink };
|
30
app/components/ExtendedDropdown/ExtendedDropdownSection.js
Executable file
30
app/components/ExtendedDropdown/ExtendedDropdownSection.js
Executable file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const ExtendedDropdownSection = (props) => {
|
||||
const { children, list, className, tag, ...otherProps } = props;
|
||||
const sectionClass = classNames(
|
||||
"extended-dropdown__section", className, {
|
||||
"extended-dropdown__section--list": list
|
||||
}
|
||||
);
|
||||
const Tag = tag;
|
||||
|
||||
return (
|
||||
<Tag className={ sectionClass } { ...otherProps }>
|
||||
{ children }
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
ExtendedDropdownSection.propTypes = {
|
||||
children: PropTypes.node,
|
||||
list: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
tag: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
|
||||
};
|
||||
ExtendedDropdownSection.defaultProps = {
|
||||
tag: "div"
|
||||
};
|
||||
|
||||
export { ExtendedDropdownSection };
|
8
app/components/ExtendedDropdown/index.js
Executable file
8
app/components/ExtendedDropdown/index.js
Executable file
@@ -0,0 +1,8 @@
|
||||
import { ExtendedDropdown } from './ExtendedDropdown';
|
||||
import { ExtendedDropdownSection } from './ExtendedDropdownSection';
|
||||
import { ExtendedDropdownLink } from './ExtendedDropdownLink';
|
||||
|
||||
ExtendedDropdown.Section = ExtendedDropdownSection;
|
||||
ExtendedDropdown.Link = ExtendedDropdownLink;
|
||||
|
||||
export default ExtendedDropdown;
|
89
app/components/FloatGrid/Col.js
Executable file
89
app/components/FloatGrid/Col.js
Executable file
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Col as BootstrapCol } from 'reactstrap';
|
||||
|
||||
// Twice Smaller than Bootstrap breakpoints
|
||||
const breakPoints = [
|
||||
{ id: 'xl', min: 600 },
|
||||
{ id: 'lg', min: 496, max: 600 },
|
||||
{ id: 'md', min: 384, max: 496 },
|
||||
{ id: 'sm', min: 288, max: 384 },
|
||||
{ id: 'xs', max: 288 }
|
||||
];
|
||||
|
||||
const getCurrentbreakPoint = (width, breakPoints) => {
|
||||
let output = 'xl';
|
||||
for (let bp of breakPoints) {
|
||||
if (
|
||||
(_.isUndefined(bp.min) || bp.min <= width) &&
|
||||
(_.isUndefined(bp.max) || bp.max > width)
|
||||
) {
|
||||
output = bp.id;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
export class Col extends React.Component {
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
|
||||
lg: PropTypes.number,
|
||||
md: PropTypes.number,
|
||||
sm: PropTypes.number,
|
||||
xs: PropTypes.number,
|
||||
xl: PropTypes.number,
|
||||
|
||||
xlH: PropTypes.number,
|
||||
lgH: PropTypes.number,
|
||||
mdH: PropTypes.number,
|
||||
smH: PropTypes.number,
|
||||
xsH: PropTypes.number,
|
||||
|
||||
xlX: PropTypes.number,
|
||||
lgX: PropTypes.number,
|
||||
mdX: PropTypes.number,
|
||||
smX: PropTypes.number,
|
||||
xsX: PropTypes.number,
|
||||
|
||||
xlY: PropTypes.number,
|
||||
lgY: PropTypes.number,
|
||||
mdY: PropTypes.number,
|
||||
smY: PropTypes.number,
|
||||
xsY: PropTypes.number,
|
||||
|
||||
trueSize: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
active: true
|
||||
}
|
||||
|
||||
render() {
|
||||
const { active, children, className, trueSize } = this.props;
|
||||
const bsColumnProps = _.pick(this.props, ['xl', 'lg', 'md', 'sm', 'xs']);
|
||||
const otherProps = _.omit(this.props, [..._.keys(Col.propTypes),
|
||||
'minW', 'maxW', 'minH', 'maxH', 'moved', 'static', 'isDraggable', 'isResizable']);
|
||||
const floatColBpId = trueSize ? getCurrentbreakPoint(trueSize.wPx, breakPoints) : 'xl';
|
||||
const floatColClasses = classNames(className, 'float-col',
|
||||
'float-column', `float-column--size-${floatColBpId}`);
|
||||
|
||||
return active ? (
|
||||
<div { ...otherProps } className={ floatColClasses }>
|
||||
{ children }
|
||||
</div>
|
||||
) : (
|
||||
<BootstrapCol
|
||||
{ ...(_.extend(bsColumnProps, otherProps)) }
|
||||
className={ classNames(className, 'pb-3') }
|
||||
>
|
||||
{ children }
|
||||
</BootstrapCol>
|
||||
);
|
||||
}
|
||||
}
|
122
app/components/FloatGrid/Grid.js
Executable file
122
app/components/FloatGrid/Grid.js
Executable file
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Container } from 'reactstrap';
|
||||
import { FloatGridContext } from './floatGridContext';
|
||||
import './../../styles/components/float-grid.scss';
|
||||
|
||||
export class Grid extends React.Component {
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
fluid: PropTypes.bool,
|
||||
rowHeight: PropTypes.number,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
active: true,
|
||||
fluid: false,
|
||||
rowHeight: 100
|
||||
}
|
||||
|
||||
state = {
|
||||
gridSize: { w: 0, h: 0 },
|
||||
gridReady: false,
|
||||
}
|
||||
_gridRef = React.createRef();
|
||||
_resizeDebounceTimeout = 0;
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
gridSize: {
|
||||
w: this._gridRef.current.clientWidth,
|
||||
h: this._gridRef.current.clientHeight
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('resize', this._resizeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(nextProps) {
|
||||
// HACK
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
nextProps.fluid !== this.props.fluid
|
||||
) {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('resize', this._resizeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { active, children, fluid, className, rowHeight, ...otherProps } = this.props;
|
||||
const { gridSize } = this.state;
|
||||
const modifiedChildren = React.Children.map(children, child => (
|
||||
React.cloneElement(child, {
|
||||
...otherProps,
|
||||
active,
|
||||
gridSize
|
||||
})
|
||||
));
|
||||
|
||||
const floatWrapClasses = classNames({
|
||||
['float-grid-parent__static']: !fluid
|
||||
}, className, 'float-grid-parent');
|
||||
|
||||
return(
|
||||
<FloatGridContext.Provider
|
||||
value={{
|
||||
gridUnitsToPx: (w, h) => {
|
||||
return {
|
||||
wPx: w / 12 * gridSize.w,
|
||||
hPx: h * rowHeight
|
||||
}
|
||||
},
|
||||
active,
|
||||
gridReady: this.state.gridReady,
|
||||
setGridReady: () => { this.setState({ gridReady: true }) }
|
||||
}}
|
||||
>
|
||||
{
|
||||
active ? (
|
||||
<div
|
||||
className={ floatWrapClasses }
|
||||
ref={ this._gridRef }
|
||||
>
|
||||
{ modifiedChildren }
|
||||
</div>
|
||||
) : (
|
||||
<div ref={ this._gridRef }>
|
||||
<Container fluid={ fluid } className={ className } { ...otherProps }>
|
||||
{ modifiedChildren }
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</FloatGridContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
_resizeHandler = () => {
|
||||
clearInterval(this._resizeDebounceTimeout);
|
||||
|
||||
this._resizeDebounceTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
gridSize: {
|
||||
w: this._gridRef.current.clientWidth,
|
||||
h: this._gridRef.current.clientHeight
|
||||
}
|
||||
});
|
||||
}, 1000 / 60 * 10); //Every 10 frames debounce
|
||||
}
|
||||
}
|
14
app/components/FloatGrid/Ready.js
Executable file
14
app/components/FloatGrid/Ready.js
Executable file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FloatGridContext } from './floatGridContext';
|
||||
|
||||
export const Ready = ({ children }) => (
|
||||
<FloatGridContext.Consumer>
|
||||
{
|
||||
({ gridReady }) => gridReady ? children : null
|
||||
}
|
||||
</FloatGridContext.Consumer>
|
||||
);
|
||||
Ready.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
207
app/components/FloatGrid/Row.js
Executable file
207
app/components/FloatGrid/Row.js
Executable file
@@ -0,0 +1,207 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
WidthProvider,
|
||||
Responsive
|
||||
} from 'react-grid-layout';
|
||||
import { Row as BSRow } from 'reactstrap';
|
||||
|
||||
import { FloatGridContext } from './floatGridContext';
|
||||
|
||||
const ResponsiveGrid = WidthProvider(Responsive);
|
||||
const responsiveBreakpoints = {
|
||||
xl: 1139, lg: 959, md: 719, sm: 539, xs: 0
|
||||
//xl: Number.MAX_VALUE, lg: 1199, md: 991, sm: 767, xs: 576
|
||||
};
|
||||
const breakPointSteps = _.keys(responsiveBreakpoints);
|
||||
const TOTAL_ROW = 12;
|
||||
|
||||
const simplifyChildrenArray = (reactChildren) => _.map(reactChildren, child => (
|
||||
{ ...child, key: child.key.replace(/\.\$/g, '') }
|
||||
));
|
||||
|
||||
export class Row extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
columns: PropTypes.object,
|
||||
onLayoutChange: PropTypes.func,
|
||||
rowHeight: PropTypes.number,
|
||||
gridSize: PropTypes.object
|
||||
};
|
||||
|
||||
static contextType = FloatGridContext;
|
||||
|
||||
_lastLayouts = { };
|
||||
state = {
|
||||
trueColSizes: { },
|
||||
activeLayout: 'xl'
|
||||
}
|
||||
initialDebounceTimeout = false;
|
||||
|
||||
componentDidUpdate(nextProps) {
|
||||
if (!_.isEqual(nextProps.gridSize, this.props.gridSize)) {
|
||||
if (!_.isEmpty(this._lastLayouts)) {
|
||||
this._updateTrueColSizes(this._lastLayouts[this.state.activeLayout]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, rowHeight, onLayoutChange, ...otherProps } = this.props;
|
||||
const { trueColSizes } = this.state;
|
||||
|
||||
if (this.context.active) {
|
||||
const layouts = this._lastLayouts = this._calculateLayouts(children);
|
||||
const adjustedChildren = simplifyChildrenArray(
|
||||
React.Children.map(children, (child) =>
|
||||
React.cloneElement(child, { trueSize: trueColSizes[child.props.i] })));
|
||||
|
||||
return (
|
||||
<ResponsiveGrid
|
||||
cols={{ xl: 12, lg: 12, md: 12, sm: 12, xs: 12 }}
|
||||
breakpoints={ responsiveBreakpoints }
|
||||
layouts={ layouts }
|
||||
padding={ [ 0, 0 ] }
|
||||
margin={ [ 0, 0 ] }
|
||||
rowHeight={ rowHeight }
|
||||
onLayoutChange={(currentLayout, allLayouts) => {
|
||||
// Notify the parent
|
||||
onLayoutChange(this._transformForChangeHandler(allLayouts));
|
||||
// Recalculate true sizes
|
||||
this._updateTrueColSizes(currentLayout);
|
||||
|
||||
clearTimeout(this.initialDebounceTimeout);
|
||||
this.initialDebounceTimeout = setTimeout(() => {
|
||||
this.context.setGridReady();
|
||||
}, 0);
|
||||
}}
|
||||
onBreakpointChange={(activeLayout) => {
|
||||
this.setState({ activeLayout });
|
||||
}}
|
||||
onResize={
|
||||
(layout, oldItem, newItem) => {
|
||||
this.setState({
|
||||
trueColSizes: {
|
||||
...trueColSizes,
|
||||
[newItem.i]: this.context.gridUnitsToPx(newItem.w, newItem.h)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{ ...otherProps }
|
||||
>
|
||||
{ adjustedChildren }
|
||||
</ResponsiveGrid>
|
||||
);
|
||||
} else {
|
||||
const adjustedChildren = React.Children.map(children, (child) =>
|
||||
React.cloneElement(child, { active: false }));
|
||||
|
||||
return (
|
||||
<BSRow>
|
||||
{ adjustedChildren }
|
||||
</BSRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_updateTrueColSizes = (layout) => {
|
||||
const { trueColSizes } = this.state;
|
||||
for (let child of layout) {
|
||||
trueColSizes[child.i] = this.context.gridUnitsToPx(child.w, child.h);
|
||||
}
|
||||
this.setState({ trueColSizes });
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nearest breakpoint relative to the one provided in the
|
||||
* first param. For example - when the `definition` param contains
|
||||
* such bps - { md: 6, xs: 8 }, for `breakpoint` - xl/md will return 6
|
||||
*/
|
||||
_findClosestBreakpoint = (breakpoint, definition) => {
|
||||
let found = 12;
|
||||
for (let bp of _.drop(breakPointSteps, _.indexOf(breakPointSteps, breakpoint))) {
|
||||
if (!_.isUndefined(definition[bp])) {
|
||||
found = definition[bp];
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
_calculateLayouts = (children) => {
|
||||
let output = { };
|
||||
const childrenArray = React.Children.toArray(children);
|
||||
for (let breakPoint of breakPointSteps) {
|
||||
let rowChildren = [];
|
||||
let rowCounter = 0;
|
||||
let y = 0;
|
||||
for (let child of childrenArray) {
|
||||
let bpData = { };
|
||||
// Save the props for current child and breakpoint
|
||||
const config = _.pick(child.props, [
|
||||
'i',
|
||||
'h', 'minH', 'maxH',
|
||||
'minW', 'maxW',
|
||||
breakPoint, `${breakPoint}MinW`, `${breakPoint}MaxW`,
|
||||
'moved', 'static', 'isResizable', 'isDraggable'
|
||||
]);
|
||||
// Calculate the needed definition
|
||||
bpData = Object.assign(bpData, {
|
||||
...config,
|
||||
// Add default heights when none provided
|
||||
...{
|
||||
// Set the x to the calculated value or take from the
|
||||
// props if defined for controlled type
|
||||
x: _.isUndefined(child.props[`${breakPoint}X`]) ?
|
||||
rowCounter : child.props[`${breakPoint}X`],
|
||||
h: child.props[`${breakPoint}H`] || config.h || 1,
|
||||
minH: config.minH || (config.h || 1),
|
||||
maxH: config.maxH || (config.h || 1),
|
||||
},
|
||||
w: config[breakPoint] ||
|
||||
this._findClosestBreakpoint(breakPoint, child.props),
|
||||
// Set the y to the calculated value or take from the
|
||||
// props if defined for controlled type
|
||||
y: _.isUndefined(child.props[`${breakPoint}Y`]) ?
|
||||
y : child.props[`${breakPoint}Y`]
|
||||
});
|
||||
rowChildren = [...rowChildren, bpData];
|
||||
rowCounter += bpData.w;
|
||||
if (rowCounter + bpData.x > TOTAL_ROW) {
|
||||
// Increase by the largest found height
|
||||
y += _.max(_.map(rowChildren, 'h'));
|
||||
rowCounter = 0;
|
||||
rowChildren = [];
|
||||
}
|
||||
output = {
|
||||
...output,
|
||||
[breakPoint]: [...(output[breakPoint] || []), bpData]
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the calculated layout to a structure
|
||||
* which is provided by `layouts` props
|
||||
*/
|
||||
_transformForChangeHandler = (layouts) => {
|
||||
let output = { };
|
||||
for (let breakPoint of breakPointSteps) {
|
||||
const bpLayout = layouts[breakPoint];
|
||||
for (let element of bpLayout) {
|
||||
output[element.i] = {
|
||||
...(output[element.i]),
|
||||
...(element),
|
||||
[breakPoint]: element.w,
|
||||
[`${breakPoint}X`]: element.x,
|
||||
[`${breakPoint}Y`]: element.y,
|
||||
[`${breakPoint}H`]: element.h
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
3
app/components/FloatGrid/floatGridContext.js
Executable file
3
app/components/FloatGrid/floatGridContext.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
export const FloatGridContext = React.createContext();
|
16
app/components/FloatGrid/index.js
Executable file
16
app/components/FloatGrid/index.js
Executable file
@@ -0,0 +1,16 @@
|
||||
import { Col } from './Col';
|
||||
import { Row } from './Row';
|
||||
import { Grid } from './Grid';
|
||||
import { Ready } from './Ready';
|
||||
|
||||
Grid.Col = Col;
|
||||
Grid.Row = Row;
|
||||
Grid.Ready = Ready;
|
||||
|
||||
export const applyColumn = (columnId, layouts) => ({
|
||||
...layouts[columnId],
|
||||
i: columnId,
|
||||
key: columnId
|
||||
});
|
||||
|
||||
export default Grid;
|
23
app/components/HolderProvider/HolderIconProvider.js
Executable file
23
app/components/HolderProvider/HolderIconProvider.js
Executable file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { HolderTextProvider } from './HolderTextProvider';
|
||||
|
||||
const HolderIconProvider = (props) => {
|
||||
const { iconChar, children, ...otherProps } = props;
|
||||
return (
|
||||
<HolderTextProvider
|
||||
font='FontAwesome'
|
||||
text={ iconChar }
|
||||
{ ...otherProps }
|
||||
>
|
||||
{ children }
|
||||
</HolderTextProvider>
|
||||
);
|
||||
};
|
||||
HolderIconProvider.propTypes = {
|
||||
iconChar: PropTypes.string.isRequired,
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export { HolderIconProvider };
|
92
app/components/HolderProvider/HolderTextProvider.js
Executable file
92
app/components/HolderProvider/HolderTextProvider.js
Executable file
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import uid from 'uuid/v4';
|
||||
import qs from 'query-string';
|
||||
|
||||
import colors from './../../colors';
|
||||
|
||||
class HolderTextProvider extends React.Component {
|
||||
static propTypes = {
|
||||
bg: PropTypes.string,
|
||||
fg: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
width: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string
|
||||
]),
|
||||
height: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string
|
||||
]),
|
||||
font: PropTypes.string,
|
||||
align: PropTypes.string,
|
||||
outline: PropTypes.bool,
|
||||
lineWrap: PropTypes.number,
|
||||
children: PropTypes.node
|
||||
}
|
||||
static defaultProps = {
|
||||
width: '100p',
|
||||
height: 220,
|
||||
bg: colors['200'],
|
||||
fg: colors['500']
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.domId = `holderjs--${uid()}`;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initPlaceholder();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.onload = this.initPlaceholder.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.initPlaceholder();
|
||||
}
|
||||
|
||||
initPlaceholder() {
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
typeof document !== 'undefined' &&
|
||||
document.readyState === 'complete'
|
||||
) {
|
||||
const Holder = require('holderjs');
|
||||
const domElement = document.getElementById(this.domId);
|
||||
|
||||
if (domElement) {
|
||||
Holder.run({
|
||||
domain: 'holder.js',
|
||||
images: domElement,
|
||||
object: null,
|
||||
bgnodes: null,
|
||||
stylenodes: null
|
||||
})
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const onlyChild = React.Children.only(this.props.children);
|
||||
|
||||
const phProps = _.omit(this.props, ['children', 'width', 'height']);
|
||||
const phPropsQuery = qs.stringify(phProps);
|
||||
|
||||
return React.cloneElement(onlyChild, {
|
||||
'id': this.domId,
|
||||
'data-src': `holder.js/${this.props.width}x${this.props.height}?${phPropsQuery}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { HolderTextProvider };
|
||||
|
10
app/components/HolderProvider/index.js
Executable file
10
app/components/HolderProvider/index.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { HolderTextProvider } from './HolderTextProvider';
|
||||
import { HolderIconProvider } from './HolderIconProvider';
|
||||
|
||||
const HolderProvider = {
|
||||
Text: HolderTextProvider,
|
||||
Icon: HolderIconProvider
|
||||
};
|
||||
|
||||
export default HolderProvider;
|
||||
|
27
app/components/IconWithBadge/IconWithBadge.js
Executable file
27
app/components/IconWithBadge/IconWithBadge.js
Executable file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const IconWithBadge = (props) => {
|
||||
const { badge, children, className } = props;
|
||||
const adjustedBadge = React.cloneElement(badge, {
|
||||
className: classNames(
|
||||
badge.props.className,
|
||||
'icon-with-badge__badge'
|
||||
)
|
||||
});
|
||||
const wrapClass = classNames(className, 'icon-with-badge');
|
||||
return (
|
||||
<div className={ wrapClass }>
|
||||
{ children }
|
||||
{ adjustedBadge }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
IconWithBadge.propTypes = {
|
||||
badge: PropTypes.node,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
export { IconWithBadge };
|
3
app/components/IconWithBadge/index.js
Executable file
3
app/components/IconWithBadge/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { IconWithBadge } from './IconWithBadge';
|
||||
|
||||
export default IconWithBadge;
|
33
app/components/InputGroupAddon/InputGroupAddon.js
Executable file
33
app/components/InputGroupAddon/InputGroupAddon.js
Executable file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {
|
||||
InputGroupAddon as BsInputGroupAddon
|
||||
} from 'reactstrap';
|
||||
|
||||
const InputGroupAddon = (props) => {
|
||||
const { children, ...otherProps } = props;
|
||||
const childArr = React.Children.toArray(children);
|
||||
const isFa = _.some(childArr, (child) =>
|
||||
React.isValidElement(child) && child.props.className && _.includes(child.props.className, 'fa'));
|
||||
const isCheckRadio = _.some(childArr, (child) =>
|
||||
React.isValidElement(child) && (child.props.type === 'radio' || child.props.type === 'checkbox'));
|
||||
|
||||
let child = isFa || isCheckRadio ? (
|
||||
<div className="input-group-text">
|
||||
{ children }
|
||||
</div>
|
||||
) : children;
|
||||
|
||||
return (
|
||||
<BsInputGroupAddon { ...otherProps }>
|
||||
{ child }
|
||||
</BsInputGroupAddon>
|
||||
);
|
||||
}
|
||||
InputGroupAddon.propTypes = {
|
||||
...BsInputGroupAddon.propTypes
|
||||
};
|
||||
InputGroupAddon.defaultProps = BsInputGroupAddon.defaultProps;
|
||||
|
||||
export { InputGroupAddon };
|
3
app/components/InputGroupAddon/index.js
Executable file
3
app/components/InputGroupAddon/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { InputGroupAddon } from './InputGroupAddon';
|
||||
|
||||
export default InputGroupAddon;
|
275
app/components/Layout/Layout.js
Executable file
275
app/components/Layout/Layout.js
Executable 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 };
|
17
app/components/Layout/LayoutContent.js
Executable file
17
app/components/Layout/LayoutContent.js
Executable 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
|
||||
};
|
23
app/components/Layout/LayoutNavbar.js
Executable file
23
app/components/Layout/LayoutNavbar.js
Executable 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
|
||||
};
|
27
app/components/Layout/LayoutSidebar.js
Executable file
27
app/components/Layout/LayoutSidebar.js
Executable 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
|
||||
};
|
7
app/components/Layout/PageConfigContext.js
Executable file
7
app/components/Layout/PageConfigContext.js
Executable file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const PageConfigContext = React.createContext();
|
||||
|
||||
export {
|
||||
PageConfigContext
|
||||
};
|
17
app/components/Layout/index.js
Executable file
17
app/components/Layout/index.js
Executable 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 };
|
30
app/components/Layout/setupPage.js
Executable file
30
app/components/Layout/setupPage.js
Executable 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);
|
||||
};
|
13
app/components/Layout/withPageConfig.js
Executable file
13
app/components/Layout/withPageConfig.js
Executable 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;
|
||||
};
|
3
app/components/Nav/index.js
Executable file
3
app/components/Nav/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { Nav } from './nav';
|
||||
|
||||
export default Nav;
|
25
app/components/Nav/nav.js
Executable file
25
app/components/Nav/nav.js
Executable file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Nav as BsNav } from 'reactstrap';
|
||||
|
||||
const Nav = ({ accent, className, ...otherProps }) => {
|
||||
return (
|
||||
<BsNav
|
||||
className={
|
||||
classNames(className, 'nav', { 'nav-accent': accent })
|
||||
}
|
||||
{ ...otherProps }
|
||||
/>
|
||||
);
|
||||
};
|
||||
Nav.propTypes = {
|
||||
...BsNav.propTypes,
|
||||
accent: PropTypes.bool,
|
||||
};
|
||||
Nav.defaultProps = {
|
||||
accent: false
|
||||
};
|
||||
|
||||
export { Nav };
|
3
app/components/Navbar/index.js
Executable file
3
app/components/Navbar/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { Navbar } from './navbar';
|
||||
|
||||
export default Navbar;
|
63
app/components/Navbar/navbar.js
Executable file
63
app/components/Navbar/navbar.js
Executable file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Navbar as BSNavbar, Container } from 'reactstrap';
|
||||
|
||||
const Navbar = ({
|
||||
themed,
|
||||
fluid,
|
||||
shadow,
|
||||
className,
|
||||
children,
|
||||
dark,
|
||||
light,
|
||||
color,
|
||||
...otherProps
|
||||
}) => {
|
||||
let navbarClass = classNames({
|
||||
'navbar-themed': themed || !!color,
|
||||
'navbar-shadow': shadow,
|
||||
}, 'navbar-multi-collapse', className);
|
||||
|
||||
// When a combination of light or dark is present
|
||||
// with a color - use a custom class instead of bootstrap's
|
||||
if ((dark || light) && color) {
|
||||
navbarClass = classNames(navbarClass,
|
||||
`navbar-${light ? 'light' : '' }${dark ? 'dark' : ''}-${color}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<BSNavbar
|
||||
className={ navbarClass }
|
||||
/*
|
||||
Use the dark and light switches
|
||||
only when color is not set
|
||||
*/
|
||||
dark={ dark && !color }
|
||||
light={ light && !color }
|
||||
{ ...otherProps }
|
||||
>
|
||||
{
|
||||
<Container className="navbar-collapse-wrap" fluid={ fluid }>
|
||||
{ children }
|
||||
</Container>
|
||||
}
|
||||
</BSNavbar>
|
||||
)
|
||||
};
|
||||
Navbar.propTypes = {
|
||||
themed: PropTypes.bool,
|
||||
fluid: PropTypes.bool,
|
||||
shadow: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
color: PropTypes.string,
|
||||
dark: PropTypes.bool,
|
||||
light: PropTypes.bool
|
||||
}
|
||||
Navbar.defaultProps = {
|
||||
fluid: false,
|
||||
themed: false,
|
||||
}
|
||||
|
||||
export { Navbar };
|
36
app/components/NavbarThemeProvider/NavbarThemeProvider.js
Executable file
36
app/components/NavbarThemeProvider/NavbarThemeProvider.js
Executable file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const NavbarThemeProvider = ({ style, color, children, className }) => {
|
||||
const isSingleChild = React.Children.count(children) === 1;
|
||||
const themeClass = `navbar-${style}-${color}`;
|
||||
|
||||
if (isSingleChild) {
|
||||
const child = React.Children.only(children);
|
||||
|
||||
return React.cloneElement(child, {
|
||||
className: classNames(
|
||||
child.props.className,
|
||||
themeClass
|
||||
),
|
||||
});
|
||||
} else {
|
||||
return (
|
||||
<div className={ classNames(className, themeClass) }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
NavbarThemeProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
style: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
};
|
||||
NavbarThemeProvider.defaultProps = {
|
||||
style: 'light',
|
||||
color: 'primary',
|
||||
};
|
||||
|
||||
export { NavbarThemeProvider };
|
3
app/components/NavbarThemeProvider/index.js
Executable file
3
app/components/NavbarThemeProvider/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { NavbarThemeProvider } from './NavbarThemeProvider';
|
||||
|
||||
export default NavbarThemeProvider;
|
52
app/components/NestedDropdown/NestedDropdown.js
Executable file
52
app/components/NestedDropdown/NestedDropdown.js
Executable file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { UncontrolledDropdown } from 'reactstrap';
|
||||
|
||||
import { Provider } from './context';
|
||||
|
||||
export class NestedDropdown extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
openId: null
|
||||
}
|
||||
}
|
||||
|
||||
handleOpen(targetId) {
|
||||
this.setState({
|
||||
openId: targetId
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { tag: Tag, className, children, ...otherProps } = this.props;
|
||||
const dropdownClass = classNames(className, 'nested-dropdown');
|
||||
|
||||
return (
|
||||
<Tag { ...otherProps } className={ dropdownClass } >
|
||||
<Provider
|
||||
value={{
|
||||
openId: this.state.openId,
|
||||
onOpen: this.handleOpen.bind(this)
|
||||
}}
|
||||
>
|
||||
{ children }
|
||||
</Provider>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NestedDropdown.propTypes = {
|
||||
tag: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.func
|
||||
]),
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
};
|
||||
NestedDropdown.defaultProps = {
|
||||
tag: UncontrolledDropdown
|
||||
};
|
82
app/components/NestedDropdown/NestedDropdownSubmenu.js
Executable file
82
app/components/NestedDropdown/NestedDropdownSubmenu.js
Executable file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import uuid from 'uuid/v4';
|
||||
|
||||
import { Consumer } from './context';
|
||||
|
||||
class NestedDropdownSubmenu extends React.Component {
|
||||
componentDidMount() {
|
||||
this.id = uuid();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
tag: Tag,
|
||||
subMenuTag: SubMenuTag,
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
openId,
|
||||
onOpen
|
||||
} = this.props;
|
||||
const itemClass = classNames(className, 'nested-dropdown__submenu-item', {
|
||||
'nested-dropdown__submenu-item--open': openId === this.id
|
||||
});
|
||||
const linkClass = classNames('nested-dropdown__submenu-item__link', 'dropdown-item');
|
||||
|
||||
return (
|
||||
<Tag className={ itemClass }>
|
||||
<a
|
||||
href="javascript:;"
|
||||
className={ linkClass }
|
||||
onClick={ () => { onOpen(this.id) } }
|
||||
>
|
||||
{ title }
|
||||
</a>
|
||||
<div className="nested-dropdown__submenu-item__menu-wrap">
|
||||
<SubMenuTag className="nested-dropdown__submenu-item__menu dropdown-menu">
|
||||
{ children }
|
||||
</SubMenuTag>
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
}
|
||||
NestedDropdownSubmenu.propTypes = {
|
||||
children: PropTypes.node,
|
||||
title: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node
|
||||
]),
|
||||
tag: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.func
|
||||
]),
|
||||
subMenuTag: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.func
|
||||
]),
|
||||
className: PropTypes.string,
|
||||
// Context Provided:
|
||||
openId: PropTypes.string,
|
||||
onOpen: PropTypes.func.isRequired
|
||||
};
|
||||
NestedDropdownSubmenu.defaultProps = {
|
||||
tag: "div",
|
||||
subMenuTag: "div"
|
||||
};
|
||||
|
||||
const ContextNestedDropdownSubmenu = (props) => (
|
||||
<Consumer>
|
||||
{
|
||||
(contextProps) => (
|
||||
<NestedDropdownSubmenu { ...contextProps } { ...props } />
|
||||
)
|
||||
}
|
||||
</Consumer>
|
||||
);
|
||||
|
||||
export {
|
||||
ContextNestedDropdownSubmenu as NestedDropdownSubmenu
|
||||
};
|
8
app/components/NestedDropdown/context.js
Executable file
8
app/components/NestedDropdown/context.js
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const { Consumer, Provider } = React.createContext();
|
||||
|
||||
export {
|
||||
Consumer,
|
||||
Provider
|
||||
};
|
6
app/components/NestedDropdown/index.js
Executable file
6
app/components/NestedDropdown/index.js
Executable file
@@ -0,0 +1,6 @@
|
||||
import { NestedDropdown } from './NestedDropdown';
|
||||
import { NestedDropdownSubmenu } from './NestedDropdownSubmenu';
|
||||
|
||||
NestedDropdown.Submenu = NestedDropdownSubmenu;
|
||||
|
||||
export default NestedDropdown;
|
76
app/components/OuterClick/OuterClick.js
Executable file
76
app/components/OuterClick/OuterClick.js
Executable file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Safely gets the browser document object,
|
||||
// returns a simple mock for server rendering purposes
|
||||
const getDocument = () =>
|
||||
typeof document === 'undefined' ?
|
||||
{
|
||||
querySelector() { return null; }
|
||||
} : document
|
||||
|
||||
/*
|
||||
Calls an EventHandler when User clicks outside of the child element
|
||||
*/
|
||||
class OuterClick extends React.Component {
|
||||
static propTypes = {
|
||||
onClickOutside: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
excludedElements: PropTypes.array,
|
||||
active: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onClickOutside: () => { },
|
||||
excludedElements: [],
|
||||
active: true
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.rootElement = getDocument().querySelector('body');
|
||||
|
||||
if (this.rootElement) {
|
||||
this.rootElement.addEventListener('click', this.handleDocumentClick);
|
||||
this.rootElement.addEventListener('touchstart', this.handleDocumentClick);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.rootElement) {
|
||||
this.rootElement.removeEventListener('click', this.handleDocumentClick);
|
||||
this.rootElement.removeEventListener('touchstart', this.handleDocumentClick);
|
||||
}
|
||||
}
|
||||
|
||||
assignRef(elementRef) {
|
||||
this.elementRef = elementRef;
|
||||
}
|
||||
|
||||
handleDocumentClick = (evt) => {
|
||||
if(this.props.active) {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
const domElement = ReactDOM.findDOMNode(this.elementRef);
|
||||
|
||||
const isExcluded = _.some(this.props.excludedElements,
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
(element) => element && ReactDOM.findDOMNode(element).contains(evt.target));
|
||||
|
||||
if (!isExcluded && !domElement.contains(evt.target)) {
|
||||
this.props.onClickOutside(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const onlyChild = React.Children.only(this.props.children);
|
||||
|
||||
const updatedChild = React.isValidElement(onlyChild) ?
|
||||
React.cloneElement(onlyChild, { ref: this.assignRef.bind(this) }) : onlyChild;
|
||||
|
||||
return updatedChild;
|
||||
}
|
||||
}
|
||||
|
||||
export { OuterClick };
|
3
app/components/OuterClick/index.js
Executable file
3
app/components/OuterClick/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { OuterClick } from './OuterClick';
|
||||
|
||||
export default OuterClick;
|
9
app/components/PageLoader/PageLoader.js
Executable file
9
app/components/PageLoader/PageLoader.js
Executable file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import classes from './PageLoader.scss';
|
||||
|
||||
const PageLoader = () => (
|
||||
<div className={ classes.pageLoader }>Loading...</div>
|
||||
);
|
||||
|
||||
export default PageLoader;
|
9
app/components/PageLoader/PageLoader.scss
Executable file
9
app/components/PageLoader/PageLoader.scss
Executable file
@@ -0,0 +1,9 @@
|
||||
.pageLoader {
|
||||
background-color: cyan;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
}
|
3
app/components/PageLoader/index.js
Executable file
3
app/components/PageLoader/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import PageLoader from './PageLoader';
|
||||
|
||||
export default PageLoader;
|
30
app/components/Progress/Progress.js
Executable file
30
app/components/Progress/Progress.js
Executable file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {
|
||||
Progress as BsProgress
|
||||
} from 'reactstrap';
|
||||
|
||||
import classes from './Progress.scss';
|
||||
|
||||
const Progress = (props) => {
|
||||
const { children, slim, className, ...otherProps } = props;
|
||||
const progressClass = classNames(className, {
|
||||
[classes['slim']]: slim
|
||||
});
|
||||
|
||||
return (
|
||||
<BsProgress className={ progressClass } { ...otherProps }>
|
||||
{ !slim && children }
|
||||
</BsProgress>
|
||||
);
|
||||
};
|
||||
|
||||
Progress.propTypes = {
|
||||
slim: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export { Progress };
|
3
app/components/Progress/Progress.scss
Executable file
3
app/components/Progress/Progress.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.slim {
|
||||
height: 0.3rem;
|
||||
}
|
3
app/components/Progress/index.js
Executable file
3
app/components/Progress/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { Progress } from './Progress';
|
||||
|
||||
export default Progress;
|
12
app/components/Recharts/CartesianGrid.js
Executable file
12
app/components/Recharts/CartesianGrid.js
Executable file
@@ -0,0 +1,12 @@
|
||||
import { CartesianGrid } from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
class CustomCartesianGrid extends CartesianGrid {
|
||||
static defaultProps = {
|
||||
...CartesianGrid.defaultProps,
|
||||
...styleConfig.grid,
|
||||
}
|
||||
}
|
||||
|
||||
export { CustomCartesianGrid as CartesianGrid };
|
40
app/components/Recharts/DefAreaValueColor.js
Executable file
40
app/components/Recharts/DefAreaValueColor.js
Executable file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import colors from './../../colors';
|
||||
|
||||
const gradientOffset = (data) => {
|
||||
const dataMax = Math.max(...data.map((i) => i.uv));
|
||||
const dataMin = Math.min(...data.map((i) => i.uv));
|
||||
|
||||
if (dataMax <= 0){
|
||||
return 0
|
||||
}
|
||||
else if (dataMin >= 0){
|
||||
return 1
|
||||
}
|
||||
else{
|
||||
return dataMax / (dataMax - dataMin);
|
||||
}
|
||||
}
|
||||
|
||||
export const DefAreaValueColor = (props) => {
|
||||
const offset = gradientOffset(props.data);
|
||||
|
||||
return (
|
||||
<linearGradient id={ props.id } x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset={offset} stopColor={ props.positiveColor } stopOpacity={1}/>
|
||||
<stop offset={offset} stopColor={ props.negativeColor } stopOpacity={1}/>
|
||||
</linearGradient>
|
||||
);
|
||||
};
|
||||
DefAreaValueColor.propTypes = {
|
||||
positiveColor: PropTypes.string,
|
||||
negativeColor: PropTypes.string,
|
||||
id: PropTypes.string.isRequired,
|
||||
data: PropTypes.array.isRequired
|
||||
};
|
||||
DefAreaValueColor.defaultProps = {
|
||||
positiveColor: colors['success-07'],
|
||||
negativeColor: colors['danger-07']
|
||||
};
|
10
app/components/Recharts/Legend.js
Executable file
10
app/components/Recharts/Legend.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { Legend as RCLegend } from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
export class Legend extends RCLegend {
|
||||
static defaultProps = {
|
||||
...RCLegend.defaultProps,
|
||||
...styleConfig.legend
|
||||
}
|
||||
}
|
35
app/components/Recharts/PieValueLabel.js
Executable file
35
app/components/Recharts/PieValueLabel.js
Executable file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import config from './config';
|
||||
|
||||
const RADIAN = Math.PI / 180;
|
||||
|
||||
export const PieValueLabel = (props) => {
|
||||
const { cx, cy, midAngle, innerRadius, outerRadius, percent, color } = props;
|
||||
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
|
||||
const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
||||
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
||||
|
||||
return (
|
||||
<text
|
||||
x={ x }
|
||||
y={ y }
|
||||
textAnchor={ x > cx ? 'start' : 'end' }
|
||||
dominantBaseline="central"
|
||||
fill={ props.color || config.pieLabel.fill }
|
||||
fontSize={ config.pieLabel.fontSize }
|
||||
>
|
||||
{`${(percent * 100).toFixed(0)}%`}
|
||||
</text>
|
||||
);
|
||||
};
|
||||
PieValueLabel.propTypes = {
|
||||
cx: PropTypes.number,
|
||||
cy: PropTypes.number,
|
||||
midAngle: PropTypes.number,
|
||||
innerRadius: PropTypes.number,
|
||||
outerRadius: PropTypes.number,
|
||||
percent: PropTypes.number,
|
||||
color: PropTypes.string
|
||||
};
|
10
app/components/Recharts/PolarAngleAxis.js
Executable file
10
app/components/Recharts/PolarAngleAxis.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { PolarAngleAxis as RCPolarAngleAxis} from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
export class PolarAngleAxis extends RCPolarAngleAxis {
|
||||
static defaultProps = {
|
||||
...RCPolarAngleAxis.defaultProps,
|
||||
...styleConfig.polarAngleAxis
|
||||
}
|
||||
}
|
12
app/components/Recharts/PolarGrid.js
Executable file
12
app/components/Recharts/PolarGrid.js
Executable file
@@ -0,0 +1,12 @@
|
||||
import { PolarGrid } from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
class CustomPolarGrid extends PolarGrid {
|
||||
static defaultProps = {
|
||||
...PolarGrid.defaultProps,
|
||||
...styleConfig.polarGrid
|
||||
}
|
||||
}
|
||||
|
||||
export { CustomPolarGrid as PolarGrid };
|
10
app/components/Recharts/PolarRadiusAxis.js
Executable file
10
app/components/Recharts/PolarRadiusAxis.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { PolarRadiusAxis as RCPolarRadiusAxis} from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
export class PolarRadiusAxis extends RCPolarRadiusAxis {
|
||||
static defaultProps = {
|
||||
...RCPolarRadiusAxis.defaultProps,
|
||||
...styleConfig.polarRadiusAxis
|
||||
}
|
||||
}
|
10
app/components/Recharts/Tooltip.js
Executable file
10
app/components/Recharts/Tooltip.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { Tooltip as RCTooltip } from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
export class Tooltip extends RCTooltip {
|
||||
static defaultProps = {
|
||||
...RCTooltip.defaultProps,
|
||||
...styleConfig.tooltip
|
||||
}
|
||||
}
|
20
app/components/Recharts/ValueLabel.js
Executable file
20
app/components/Recharts/ValueLabel.js
Executable file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import config from './config';
|
||||
|
||||
export const ValueLabel = (props) =>
|
||||
<text
|
||||
x={ props.x }
|
||||
y={ props.y - config.label.fontSize }
|
||||
textAnchor="middle"
|
||||
{ ...config.label }
|
||||
>
|
||||
{ props.value }
|
||||
</text>;
|
||||
|
||||
ValueLabel.propTypes = {
|
||||
value: PropTypes.number,
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number
|
||||
};
|
10
app/components/Recharts/XAxis.js
Executable file
10
app/components/Recharts/XAxis.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { XAxis as RCXAxis } from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
export class XAxis extends RCXAxis {
|
||||
static defaultProps = {
|
||||
...RCXAxis.defaultProps,
|
||||
...styleConfig.axis
|
||||
}
|
||||
}
|
10
app/components/Recharts/YAxis.js
Executable file
10
app/components/Recharts/YAxis.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { YAxis as RCYAxis } from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
export class YAxis extends RCYAxis {
|
||||
static defaultProps = {
|
||||
...RCYAxis.defaultProps,
|
||||
...styleConfig.axis
|
||||
}
|
||||
}
|
10
app/components/Recharts/ZAxis.js
Executable file
10
app/components/Recharts/ZAxis.js
Executable file
@@ -0,0 +1,10 @@
|
||||
import { ZAxis as RCZAxis } from 'recharts';
|
||||
|
||||
import styleConfig from './config';
|
||||
|
||||
export class ZAxis extends RCZAxis {
|
||||
static defaultProps = {
|
||||
...RCZAxis.defaultProps,
|
||||
...styleConfig.axis
|
||||
}
|
||||
}
|
61
app/components/Recharts/config.js
Executable file
61
app/components/Recharts/config.js
Executable file
@@ -0,0 +1,61 @@
|
||||
// ReCharts styling configuration
|
||||
import colors from './../../colors';
|
||||
|
||||
export default {
|
||||
grid: {
|
||||
stroke: colors['400'],
|
||||
strokeWidth: 1,
|
||||
strokeDasharray: '1px'
|
||||
},
|
||||
polarGrid: {
|
||||
stroke: colors['400'],
|
||||
},
|
||||
axis: {
|
||||
stroke: colors['500'],
|
||||
strokeWidth: 1,
|
||||
style: {
|
||||
fontSize: '12px'
|
||||
},
|
||||
tick: {
|
||||
// Axis Labels color:
|
||||
fill: colors['900']
|
||||
}
|
||||
},
|
||||
polarRadiusAxis: {
|
||||
stroke: colors['400'],
|
||||
tick: {
|
||||
fill: colors['900']
|
||||
}
|
||||
},
|
||||
polarAngleAxis: {
|
||||
tick: {
|
||||
fill: colors['900']
|
||||
},
|
||||
style: {
|
||||
fontSize: '12px'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
fontSize: 11,
|
||||
fill: colors['900']
|
||||
},
|
||||
legend: {
|
||||
wrapperStyle: {
|
||||
color: colors['900']
|
||||
}
|
||||
},
|
||||
pieLabel: {
|
||||
fontSize: 12,
|
||||
fill: colors[100]
|
||||
},
|
||||
tooltip: {
|
||||
cursor: {
|
||||
fill: colors['primary-01']
|
||||
},
|
||||
contentStyle: {
|
||||
background: colors['900'],
|
||||
border: `1px solid ${colors['900']}`,
|
||||
color: colors['white']
|
||||
}
|
||||
}
|
||||
};
|
38
app/components/Sidebar/Sidebar.js
Executable file
38
app/components/Sidebar/Sidebar.js
Executable file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import OuterClick from './../OuterClick';
|
||||
import { withPageConfig } from './../Layout';
|
||||
import { SidebarContent } from './SidebarContent';
|
||||
|
||||
const Sidebar = (props) => (
|
||||
<React.Fragment>
|
||||
{ /* Enable OuterClick only in sidebar overlay mode */}
|
||||
<OuterClick
|
||||
active={
|
||||
!props.pageConfig.sidebarCollapsed && (
|
||||
props.pageConfig.screenSize === 'xs' ||
|
||||
props.pageConfig.screenSize === 'sm' ||
|
||||
props.pageConfig.screenSize === 'md'
|
||||
)
|
||||
}
|
||||
onClickOutside={ () => props.pageConfig.toggleSidebar() }
|
||||
>
|
||||
<SidebarContent { ...props } />
|
||||
</OuterClick>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
Sidebar.propTypes = {
|
||||
children: PropTypes.node,
|
||||
slim: PropTypes.bool,
|
||||
collapsed: PropTypes.bool,
|
||||
animationsDisabled: PropTypes.bool,
|
||||
pageConfig: PropTypes.object
|
||||
};
|
||||
|
||||
const cfgSidebar = withPageConfig(Sidebar);
|
||||
|
||||
export {
|
||||
cfgSidebar as Sidebar
|
||||
};
|
13
app/components/Sidebar/SidebarClose.js
Executable file
13
app/components/Sidebar/SidebarClose.js
Executable file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const SidebarClose = (props) => (
|
||||
<div className="sidebar__close">
|
||||
{ props.children }
|
||||
</div>
|
||||
);
|
||||
SidebarClose.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export { SidebarClose };
|
69
app/components/Sidebar/SidebarContent.js
Executable file
69
app/components/Sidebar/SidebarContent.js
Executable file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Common from './../../common';
|
||||
|
||||
export class SidebarContent extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
slim: PropTypes.bool,
|
||||
collapsed: PropTypes.bool,
|
||||
animationsDisabled: PropTypes.bool,
|
||||
pageConfig: PropTypes.object
|
||||
}
|
||||
|
||||
sidebarRef = React.createRef();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
entryAnimationFinished: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.sidebarEntryAnimate = new Common.SidebarEntryAnimate();
|
||||
this.slimSidebarAnimate = new Common.SlimSidebarAnimate();
|
||||
this.slimMenuAnimate = new Common.SlimMenuAnimate();
|
||||
|
||||
this.sidebarEntryAnimate.assignParentElement(this.sidebarRef.current);
|
||||
this.slimSidebarAnimate.assignParentElement(this.sidebarRef.current);
|
||||
this.slimMenuAnimate.assignSidebarElement(this.sidebarRef.current);
|
||||
|
||||
this.sidebarEntryAnimate.executeAnimation()
|
||||
.then(() => {
|
||||
this.setState({ entryAnimationFinished: true });
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.sidebarEntryAnimate.destroy();
|
||||
this.slimSidebarAnimate.destroy();
|
||||
this.slimMenuAnimate.destroy();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animationsDisabled,
|
||||
collapsed,
|
||||
pageConfig,
|
||||
slim,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
const sidebarClass = classNames('sidebar', 'sidebar--animations-enabled', {
|
||||
'sidebar--slim': slim || pageConfig.sidebarSlim,
|
||||
'sidebar--collapsed': collapsed || pageConfig.sidebarCollapsed,
|
||||
'sidebar--animations-disabled': animationsDisabled || pageConfig.animationsDisabled,
|
||||
'sidebar--animate-entry-complete': this.state.entryAnimationFinished,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={ sidebarClass } ref={ this.sidebarRef }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user