Responsive nav (#225)

* Allow specifying debounce options

* responsive nav styles

* animate sub items in skinny nav
This commit is contained in:
Mike Stone 2016-09-22 09:20:13 -04:00 committed by GitHub
parent 26b1e70ac3
commit c4924be4d4
4 changed files with 142 additions and 41 deletions

View file

@ -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 (
<div>
<StyleRoot>
<Style rules={globalStyles} />
{children}
<Footer />
</div>
</StyleRoot>
);
}
}

View file

@ -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 (
<ul style={subItemsStyles}>
{subItems.map(subItem => {
return renderSubItem(subItem);
})}
</ul>
<div style={subItemsStyles(showSubItems)}>
<ul style={subItemListStyles(showSubItems)}>
{subItems.map(subItem => {
return renderSubItem(subItem);
})}
</ul>
{renderCollapseSubItems()}
</div>
);
}
renderCollapseSubItems = () => {
const { toggleShowSubItems } = this;
const { showSubItems } = this.state;
const { collapseSubItemsWrapper } = componentStyles;
const iconName = showSubItems ? 'kolidecon-chevron-bold-left' : 'kolidecon-chevron-bold-right';
return (
<div style={collapseSubItemsWrapper}>
<i className={iconName} style={{ color: '#FFF' }} onClick={toggleShowSubItems(!showSubItems)} />
</div>
);
}
@ -166,4 +200,4 @@ class SidePanel extends Component {
}
}
export default SidePanel;
export default radium(SidePanel);

View file

@ -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;

View file

@ -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,
});
};