diff --git a/frontend/components/App/App.jsx b/frontend/components/App/App.jsx
index 30569012a5..c39b61f683 100644
--- a/frontend/components/App/App.jsx
+++ b/frontend/components/App/App.jsx
@@ -1,7 +1,7 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { noop } from 'lodash';
-import { Style } from 'radium';
+import { Style, StyleRoot } from 'radium';
import { fetchCurrentUser } from '../../redux/nodes/auth/actions';
import Footer from './Footer';
import globalStyles from '../../styles/global';
@@ -32,11 +32,11 @@ export class App extends Component {
const { children } = this.props;
return (
-
+
{children}
-
+
);
}
}
diff --git a/frontend/components/SidePanel/SidePanel.jsx b/frontend/components/SidePanel/SidePanel.jsx
index c34bbfd748..67fe0f12cc 100644
--- a/frontend/components/SidePanel/SidePanel.jsx
+++ b/frontend/components/SidePanel/SidePanel.jsx
@@ -1,4 +1,5 @@
import React, { Component, PropTypes } from 'react';
+import radium from 'radium';
import { isEqual, last } from 'lodash';
import componentStyles from './styles';
import kolideLogo from '../../../assets/images/kolide-logo.svg';
@@ -15,15 +16,7 @@ class SidePanel extends Component {
this.state = {
activeTab: 'Hosts',
activeSubItem: 'Add Hosts',
- };
- }
-
- setActiveTab = (activeTab) => {
- return (evt) => {
- evt.preventDefault();
-
- this.setState({ activeTab });
- return false;
+ showSubItems: false,
};
}
@@ -36,6 +29,28 @@ class SidePanel extends Component {
};
}
+ setActiveTab = (activeTab) => {
+ return (evt) => {
+ evt.preventDefault();
+
+ this.setState({
+ activeTab,
+ });
+
+ return false;
+ };
+ }
+
+ toggleShowSubItems = (showSubItems) => {
+ return (evt) => {
+ evt.preventDefault();
+
+ this.setState({ showSubItems });
+
+ return false;
+ };
+ }
+
renderHeader = () => {
const {
user: {
@@ -141,15 +156,34 @@ class SidePanel extends Component {
}
renderSubItems = (subItems) => {
- const { subItemsStyles } = componentStyles;
- const { renderSubItem } = this;
+ const { subItemListStyles, subItemsStyles } = componentStyles;
+ const { renderCollapseSubItems, renderSubItem } = this;
+ const { showSubItems } = this.state;
+
+ if (!subItems.length) return false;
return (
-
- {subItems.map(subItem => {
- return renderSubItem(subItem);
- })}
-
+
+
+ {subItems.map(subItem => {
+ return renderSubItem(subItem);
+ })}
+
+ {renderCollapseSubItems()}
+
+ );
+ }
+
+ renderCollapseSubItems = () => {
+ const { toggleShowSubItems } = this;
+ const { showSubItems } = this.state;
+ const { collapseSubItemsWrapper } = componentStyles;
+ const iconName = showSubItems ? 'kolidecon-chevron-bold-left' : 'kolidecon-chevron-bold-right';
+
+ return (
+
+
+
);
}
@@ -166,4 +200,4 @@ class SidePanel extends Component {
}
}
-export default SidePanel;
+export default radium(SidePanel);
diff --git a/frontend/components/SidePanel/styles.js b/frontend/components/SidePanel/styles.js
index 78ae79f802..d1477d4e19 100644
--- a/frontend/components/SidePanel/styles.js
+++ b/frontend/components/SidePanel/styles.js
@@ -2,12 +2,15 @@ import Styles from '../../styles';
const { border, color, font, padding } = Styles;
-export default {
+const componentStyles = {
companyLogoStyles: {
position: 'absolute',
left: '16px',
height: '44px',
marginRight: '10px',
+ '@media (max-width: 760px)': {
+ left: '4px',
+ },
},
headerStyles: {
borderBottomColor: color.accentLight,
@@ -23,6 +26,10 @@ export default {
fontSize: '22px',
marginRight: '16px',
top: '4px',
+ left: 0,
+ '@media (max-width: 760px)': {
+ left: '5px',
+ },
},
navItemBeforeStyles: {
content: '',
@@ -33,6 +40,9 @@ export default {
top: '2px',
bottom: 0,
backgroundColor: '#9a61c6',
+ '@media (max-width: 760px)': {
+ left: 0,
+ },
},
navItemListStyles: {
listStyle: 'none',
@@ -42,10 +52,19 @@ export default {
navItemNameStyles: {
display: 'inline-block',
textDecoration: 'none',
+ '@media (max-width: 760px)': {
+ display: 'none',
+ },
},
navItemStyles: (active) => {
const activeStyles = {
color: color.brand,
+ borderBottom: 'none',
+ transition: 'none',
+ '@media (max-width: 760px)': {
+ borderBottom: '8px solid #9a61c6',
+ textAlign: 'center',
+ },
};
const baseStyles = {
@@ -56,9 +75,10 @@ export default {
fontSize: font.small,
textTransform: 'uppercase',
paddingTop: padding.half,
- WebkitTransition: 'all 0.2s ease-in-out',
- MozTransition: 'all 0.2s ease-in-out',
transition: 'all 0.2s ease-in-out',
+ '@media (max-width: 760px)': {
+ textAlign: 'center',
+ },
};
if (active) {
@@ -80,6 +100,9 @@ export default {
borderTopWidth: '1px',
marginRight: '16px',
marginTop: '5px',
+ '@media (max-width: 760px)': {
+ marginRight: 0,
+ },
};
if (lastChild) {
@@ -104,6 +127,10 @@ export default {
position: 'fixed',
top: 0,
width: '216px',
+ '@media (max-width: 760px)': {
+ paddingLeft: 0,
+ width: '54px',
+ },
},
orgNameStyles: {
fontSize: font.medium,
@@ -115,6 +142,9 @@ export default {
textOverflow: 'ellipsis',
top: '3px',
whiteSpace: 'nowrap',
+ '@media (max-width: 760px)': {
+ display: 'none',
+ },
},
subItemBeforeStyles: {
backgroundColor: color.white,
@@ -152,8 +182,6 @@ export default {
paddingLeft: padding.most,
paddingTop: padding.xSmall,
position: 'relative',
- WebkitTransition: 'all 0.2s ease-in-out',
- MozTransition: 'all 0.2s ease-in-out',
textTransform: 'none',
transition: 'all 0.2s ease-in-out',
};
@@ -167,17 +195,46 @@ export default {
return baseStyles;
},
- subItemsStyles: {
- backgroundColor: color.brand,
- boxShadow: 'inset 0 5px 8px 0 rgba(0, 0, 0, 0.12), inset 0 -5px 8px 0 rgba(0, 0, 0, 0.12)',
- listStyle: 'none',
- marginBottom: 0,
- marginLeft: '-24px',
- marginRight: 0,
- marginTop: padding.medium,
- minHeight: '6px',
- paddingBottom: padding.half,
- paddingTop: padding.half,
+ subItemsStyles: (expanded) => {
+ return {
+ backgroundColor: color.brand,
+ boxShadow: 'inset 0 5px 8px 0 rgba(0, 0, 0, 0.12), inset 0 -5px 8px 0 rgba(0, 0, 0, 0.12)',
+ marginBottom: 0,
+ marginRight: 0,
+ minHeight: '87px',
+ paddingBottom: padding.half,
+ paddingTop: padding.half,
+ marginLeft: '-24px',
+ marginTop: padding.medium,
+ transition: 'width 0.1s ease-in-out',
+ '@media (max-width: 760px)': {
+ bottom: '-8px',
+ left: '54px',
+ marginLeft: 0,
+ position: 'absolute',
+ width: expanded ? '251px' : '18px',
+ },
+ };
+ },
+ subItemListStyles: (expanded) => {
+ return {
+ listStyle: 'none',
+ '@media (max-width: 760px)': {
+ borderRight: '1px solid rgba(0,0,0,0.16)',
+ display: expanded ? 'inline-block' : 'none',
+ padding: 0,
+ textAlign: 'left',
+ width: '211px',
+ },
+ };
+ },
+ collapseSubItemsWrapper: {
+ position: 'absolute',
+ right: '3px',
+ top: '41%',
+ '@media (min-width: 761px)': {
+ display: 'none',
+ },
},
usernameStyles: {
position: 'relative',
@@ -187,6 +244,9 @@ export default {
padding: 0,
fontSize: font.small,
textTransform: 'uppercase',
+ '@media (max-width: 760px)': {
+ display: 'none',
+ },
},
userStatusStyles: (enabled) => {
const backgroundColor = enabled ? color.success : color.warning;
@@ -202,6 +262,11 @@ export default {
position: 'relative',
top: '6px',
width: size,
+ '@media (max-width: 760px)': {
+ display: 'none',
+ },
};
},
};
+
+export default componentStyles;
diff --git a/frontend/utilities/debounce/index.js b/frontend/utilities/debounce/index.js
index b552156efd..e985d9efd5 100644
--- a/frontend/utilities/debounce/index.js
+++ b/frontend/utilities/debounce/index.js
@@ -1,10 +1,12 @@
import { debounce } from 'lodash';
-const TIMEOUT = 1000; // only allow 1 click per second
+const DEFAULT_TIMEOUT = 1000; // 1 function execution per second by default
-export default (func) => {
- return debounce(func, TIMEOUT, {
- leading: true,
- trailing: false,
+export default (func, options = {}) => {
+ const { leading = true, trailing = false, timeout = DEFAULT_TIMEOUT } = options;
+
+ return debounce(func, timeout, {
+ leading,
+ trailing,
});
};