Initial Commit Rework

This commit is contained in:
Qstick 2017-09-03 22:20:56 -04:00
parent 74a4cc048c
commit 95051cbd63
2483 changed files with 101351 additions and 111396 deletions

View file

@ -0,0 +1,9 @@
.filterMenu {
composes: menu from './Menu.css';
}
@media only screen and (max-width: $breakpointSmall) {
.filterMenu {
margin-right: 10px;
}
}

View file

@ -0,0 +1,44 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import Menu from 'Components/Menu/Menu';
import ToolbarMenuButton from 'Components/Menu/ToolbarMenuButton';
import styles from './FilterMenu.css';
class FilterMenu extends Component {
//
// Render
render() {
const {
className,
children,
...otherProps
} = this.props;
return (
<Menu
className={className}
{...otherProps}
>
<ToolbarMenuButton
iconName={icons.FILTER}
text="Filter"
/>
{children}
</Menu>
);
}
}
FilterMenu.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired
};
FilterMenu.defaultProps = {
className: styles.filterMenu
};
export default FilterMenu;

View file

@ -0,0 +1,57 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import SelectedMenuItem from './SelectedMenuItem';
class FilterMenuItem extends Component {
//
// Listeners
onPress = () => {
const {
name,
value,
onPress
} = this.props;
onPress(name, value);
}
//
// Render
render() {
const {
name,
value,
filterKey,
filterValue,
...otherProps
} = this.props;
const isSelected = name === filterKey && value === filterValue;
return (
<SelectedMenuItem
isSelected={isSelected}
{...otherProps}
onPress={this.onPress}
/>
);
}
}
FilterMenuItem.propTypes = {
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
filterKey: PropTypes.string,
filterValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
onPress: PropTypes.func.isRequired
};
FilterMenuItem.defaultProps = {
name: null,
value: null
};
export default FilterMenuItem;

View file

@ -0,0 +1,7 @@
.tether {
z-index: 2000;
}
.menu {
position: relative;
}

View file

@ -0,0 +1,203 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import TetherComponent from 'react-tether';
import { align } from 'Helpers/Props';
import styles from './Menu.css';
const baseTetherOptions = {
skipMoveElement: true,
constraints: [
{
to: 'window',
attachment: 'together',
pin: true
}
]
};
const tetherOptions = {
[align.RIGHT]: {
...baseTetherOptions,
attachment: 'top right',
targetAttachment: 'bottom right'
},
[align.LEFT]: {
...baseTetherOptions,
attachment: 'top left',
targetAttachment: 'bottom left'
}
};
class Menu extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isMenuOpen: false,
maxHeight: 0
};
}
componentDidMount() {
this.setMaxHeight();
}
//
// Control
getMaxHeight() {
if (!this.props.enforceMaxHeight) {
return;
}
const menu = ReactDOM.findDOMNode(this.refs.menu);
if (!menu) {
return;
}
const { bottom } = menu.getBoundingClientRect();
const maxHeight = window.innerHeight - bottom;
return maxHeight;
}
setMaxHeight() {
this.setState({
maxHeight: this.getMaxHeight()
});
}
_addListener() {
// Listen to resize events on the window and scroll events
// on all elements to ensure the menu is the best size possible.
// Listen for click events on the window to support closing the
// menu on clicks outside.
window.addEventListener('resize', this.onWindowResize);
window.addEventListener('scroll', this.onWindowScroll, { capture: true });
window.addEventListener('click', this.onWindowClick);
}
_removeListener() {
window.removeEventListener('resize', this.onWindowResize);
window.removeEventListener('scroll', this.onWindowScroll, { capture: true });
window.removeEventListener('click', this.onWindowClick);
}
//
// Listeners
onWindowClick = (event) => {
const menu = ReactDOM.findDOMNode(this.refs.menu);
const menuContent = ReactDOM.findDOMNode(this.refs.menuContent);
if (!menu) {
return;
}
if ((!menu.contains(event.target) || menuContent.contains(event.target)) && this.state.isMenuOpen) {
this.setState({ isMenuOpen: false });
this._removeListener();
}
}
onWindowResize = () => {
this.setMaxHeight();
}
onWindowScroll = () => {
this.setMaxHeight();
}
onMenuButtonPress = () => {
const state = {
isMenuOpen: !this.state.isMenuOpen
};
if (this.state.isMenuOpen) {
this._removeListener();
} else {
state.maxHeight = this.getMaxHeight();
this._addListener();
}
this.setState(state);
}
//
// Render
render() {
const {
className,
children,
alignMenu
} = this.props;
const {
maxHeight,
isMenuOpen
} = this.state;
const childrenArray = React.Children.toArray(children);
const button = React.cloneElement(
childrenArray[0],
{
onPress: this.onMenuButtonPress
}
);
const content = React.cloneElement(
childrenArray[1],
{
ref: 'menuContent',
alignMenu,
maxHeight,
isOpen: isMenuOpen
}
);
return (
<TetherComponent
classes={{
element: styles.tether
}}
{...tetherOptions[alignMenu]}
>
<div
ref="menu"
className={className}
>
{button}
</div>
{
isMenuOpen &&
content
}
</TetherComponent>
);
}
}
Menu.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired,
alignMenu: PropTypes.oneOf([align.LEFT, align.RIGHT]),
enforceMaxHeight: PropTypes.bool.isRequired
};
Menu.defaultProps = {
className: styles.menu,
alignMenu: align.LEFT,
enforceMaxHeight: true
};
export default Menu;

View file

@ -0,0 +1,15 @@
.menuButton {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
&::after {
margin-left: 5px;
content: '\25BE';
}
&:hover {
color: $toobarButtonHoverColor;
}
}

View file

@ -0,0 +1,41 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Link from 'Components/Link/Link';
import styles from './MenuButton.css';
class MenuButton extends Component {
//
// Render
render() {
const {
className,
children,
onPress,
...otherProps
} = this.props;
return (
<Link
className={className}
onPress={onPress}
{...otherProps}
>
{children}
</Link>
);
}
}
MenuButton.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired,
onPress: PropTypes.func
};
MenuButton.defaultProps = {
className: styles.menuButton
};
export default MenuButton;

View file

@ -0,0 +1,11 @@
.menuContent {
display: flex;
flex-direction: column;
background-color: $toolbarMenuItemBackgroundColor;
line-height: 20px;
}
.scroller {
display: flex;
flex-direction: column;
}

View file

@ -0,0 +1,43 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Scroller from 'Components/Scroller/Scroller';
import styles from './MenuContent.css';
class MenuContent extends Component {
//
// Render
render() {
const {
className,
children,
maxHeight
} = this.props;
return (
<div
className={className}
style={{
maxHeight: maxHeight ? `${maxHeight}px` : undefined
}}
>
<Scroller className={styles.scroller}>
{children}
</Scroller>
</div>
);
}
}
MenuContent.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired,
maxHeight: PropTypes.number
};
MenuContent.defaultProps = {
className: styles.menuContent
};
export default MenuContent;

View file

@ -0,0 +1,19 @@
.menuItem {
composes: truncate from 'Styles/mixins/truncate.css';
display: block;
flex-shrink: 0;
padding: 10px 20px;
min-width: 150px;
max-width: 250px;
background-color: $toolbarMenuItemBackgroundColor;
color: $menuItemColor;
line-height: 20px;
&:hover,
&:focus {
background-color: $toolbarMenuItemHoverBackgroundColor;
color: $menuItemHoverColor;
text-decoration: none;
}
}

View file

@ -0,0 +1,38 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Link from 'Components/Link/Link';
import styles from './MenuItem.css';
class MenuItem extends Component {
//
// Render
render() {
const {
className,
children,
...otherProps
} = this.props;
return (
<Link
className={className}
{...otherProps}
>
{children}
</Link>
);
}
}
MenuItem.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired
};
MenuItem.defaultProps = {
className: styles.menuItem
};
export default MenuItem;

View file

@ -0,0 +1,15 @@
.item {
display: flex;
justify-content: space-between;
white-space: nowrap;
}
.isSelected {
visibility: visible;
margin-left: 20px;
}
.isNotSelected {
visibility: hidden;
margin-left: 20px;
}

View file

@ -0,0 +1,63 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import MenuItem from './MenuItem';
import styles from './SelectedMenuItem.css';
class SelectedMenuItem extends Component {
//
// Listeners
onPress = () => {
const {
name,
onPress
} = this.props;
onPress(name);
}
//
// Render
render() {
const {
children,
selectedIconName,
isSelected,
...otherProps
} = this.props;
return (
<MenuItem
{...otherProps}
onPress={this.onPress}
>
<div className={styles.item}>
{children}
<Icon
className={isSelected ? styles.isSelected : styles.isNotSelected}
name={selectedIconName}
/>
</div>
</MenuItem>
);
}
}
SelectedMenuItem.propTypes = {
name: PropTypes.string,
children: PropTypes.node.isRequired,
selectedIconName: PropTypes.string.isRequired,
isSelected: PropTypes.bool.isRequired,
onPress: PropTypes.func.isRequired
};
SelectedMenuItem.defaultProps = {
selectedIconName: icons.CHECK
};
export default SelectedMenuItem;

View file

@ -0,0 +1,39 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import Menu from 'Components/Menu/Menu';
import ToolbarMenuButton from 'Components/Menu/ToolbarMenuButton';
class SortMenu extends Component {
//
// Render
render() {
const {
className,
children,
...otherProps
} = this.props;
return (
<Menu
className={className}
{...otherProps}
>
<ToolbarMenuButton
iconName={icons.SORT}
text="Sort"
/>
{children}
</Menu>
);
}
}
SortMenu.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired
};
export default SortMenu;

View file

@ -0,0 +1,37 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons, sortDirections } from 'Helpers/Props';
import SelectedMenuItem from './SelectedMenuItem';
function SortMenuItem(props) {
const {
name,
sortKey,
sortDirection,
...otherProps
} = props;
const isSelected = name === sortKey;
return (
<SelectedMenuItem
name={name}
selectedIconName={sortDirection === sortDirections.ASCENDING ? icons.SORT_ASCENDING : icons.SORT_DESCENDING}
isSelected={isSelected}
{...otherProps}
/>
);
}
SortMenuItem.propTypes = {
name: PropTypes.string,
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
onPress: PropTypes.func.isRequired
};
SortMenuItem.defaultProps = {
name: null
};
export default SortMenuItem;

View file

@ -0,0 +1,13 @@
.menuButton {
composes: menuButton from './MenuButton.css';
width: $toolbarButtonWidth;
height: $toolbarHeight;
text-align: center;
}
.label {
height: 14px;
color: $toolbarLabelColor;
font-size: $extraSmallFontSize;
}

View file

@ -0,0 +1,38 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import MenuButton from 'Components/Menu/MenuButton';
import styles from './ToolbarMenuButton.css';
function ToolbarMenuButton(props) {
const {
iconName,
text,
...otherProps
} = props;
return (
<MenuButton
className={styles.menuButton}
{...otherProps}
>
<div>
<Icon
name={iconName}
size={22}
/>
<div className={styles.label}>
{text}
</div>
</div>
</MenuButton>
);
}
ToolbarMenuButton.propTypes = {
iconName: PropTypes.string.isRequired,
text: PropTypes.string
};
export default ToolbarMenuButton;

View file

@ -0,0 +1,30 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons } from 'Helpers/Props';
import Menu from 'Components/Menu/Menu';
import ToolbarMenuButton from 'Components/Menu/ToolbarMenuButton';
function ViewMenu(props) {
const {
children,
...otherProps
} = props;
return (
<Menu
{...otherProps}
>
<ToolbarMenuButton
iconName={icons.VIEW}
text="View"
/>
{children}
</Menu>
);
}
ViewMenu.propTypes = {
children: PropTypes.node.isRequired
};
export default ViewMenu;

View file

@ -0,0 +1,28 @@
import PropTypes from 'prop-types';
import React from 'react';
import SelectedMenuItem from './SelectedMenuItem';
function ViewMenuItem(props) {
const {
name,
selectedView,
...otherProps
} = props;
const isSelected = name === selectedView;
return (
<SelectedMenuItem
name={name}
isSelected={isSelected}
{...otherProps}
/>
);
}
ViewMenuItem.propTypes = {
name: PropTypes.string,
selectedView: PropTypes.string.isRequired
};
export default ViewMenuItem;