From 6912567122cf68ad8782c659499f87e728d226f4 Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Fri, 26 Feb 2016 20:14:53 -0700 Subject: [PATCH] Add search bar and test entry item --- app/components/EntrySelector.jsx | 52 ++- app/components/Item.jsx | 691 ++++++++++++++++++++++++++++++ app/components/LibraryNav.jsx | 66 +-- app/components/SelectableList.jsx | 35 ++ style.css | 12 + 5 files changed, 804 insertions(+), 52 deletions(-) create mode 100644 app/components/Item.jsx create mode 100644 app/components/SelectableList.jsx diff --git a/app/components/EntrySelector.jsx b/app/components/EntrySelector.jsx index c7cd9f1..2693688 100644 --- a/app/components/EntrySelector.jsx +++ b/app/components/EntrySelector.jsx @@ -5,21 +5,22 @@ import getMuiTheme from 'material-ui/lib/styles/getMuiTheme' import List from 'material-ui/lib/lists/list' import ListItem from 'material-ui/lib/lists/list-item' -import ActionGrade from 'material-ui/lib/svg-icons/action/grade' -import History from 'material-ui/lib/svg-icons/action/history' -import AddCircleOutline from 'material-ui/lib/svg-icons/content/add-circle-outline' -import Folder from 'material-ui/lib/svg-icons/file/folder' -import Delete from 'material-ui/lib/svg-icons/action/delete' -import Divider from 'material-ui/lib/divider' +import Description from 'material-ui/lib/svg-icons/action/description' +import Add from 'material-ui/lib/svg-icons/content/add' +import Search from 'material-ui/lib/svg-icons/action/search' +import SelectableList from 'SelectableList' +import Item from 'Item' const {AppBar, AppCanvas, FontIcon, IconButton, EnhancedButton, + AutoComplete, NavigationClose, Menu, + MenuItem, Mixins, RaisedButton, FlatButton, @@ -27,6 +28,7 @@ const {AppBar, Styles, Tab, Tabs, + TextField, Paper} = mui const colors = Styles.Colors @@ -56,9 +58,41 @@ export default class EntrySelector extends React.Component { render(){ return ( -
- - + + + } + rightIconButton={ + + }> + {}} + onEnterKeyDown={()=>{}} + /> + + + + } + primaryText="Today's Notes" + secondaryText={ +

+ I did things -- + I did so many things today, it's not even funny. You think it's funny? + Well it's not! +

+ } + secondaryTextLines={2} + /> +
+
) } } diff --git a/app/components/Item.jsx b/app/components/Item.jsx new file mode 100644 index 0000000..d33cca2 --- /dev/null +++ b/app/components/Item.jsx @@ -0,0 +1,691 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import ColorManipulator from 'material-ui/lib/utils/color-manipulator'; +import StylePropable from 'material-ui/lib/mixins/style-propable'; +import Colors from 'material-ui/lib/styles/colors'; +import Transitions from 'material-ui/lib/styles/transitions'; +import Typography from 'material-ui/lib/styles/typography'; +import EnhancedButton from 'material-ui/lib/enhanced-button'; +import IconButton from 'material-ui/lib/icon-button'; +import OpenIcon from 'material-ui/lib/svg-icons/navigation/arrow-drop-up'; +import CloseIcon from 'material-ui/lib/svg-icons/navigation/arrow-drop-down'; +import NestedList from 'material-ui/lib/lists/nested-list'; +import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'; + +const Item = React.createClass({ + + propTypes: { + /** + * Generate a nested list indicator icon when + * nested list items are detected. Set to false + * if you do not want an indicator auto-generated. + * Note that an indicator will not be created if a + * rightIcon/Button has been specified. + */ + autoGenerateNestedIndicator: React.PropTypes.bool, + + /** + * Children passed into the ListItem. + */ + children: React.PropTypes.node, + + /** + * Does not allow the element to be focused by the keyboard. + */ + disableKeyboardFocus: React.PropTypes.bool, + + /** + * If true, the list-item will not be clickable + * and will not display hover affects. + * This is automatically disabled if leftCheckbox + * or rightToggle is set. + */ + disabled: React.PropTypes.bool, + + /** + * Controls whether or not the child ListItems are initially displayed. + */ + initiallyOpen: React.PropTypes.bool, + + /** + * Style prop for the innder div element. + */ + innerDivStyle: React.PropTypes.object, + + /** + * If true, the children will be indented by 72px. + * Only needed if there is no left avatar or left icon. + */ + insetChildren: React.PropTypes.bool, + + /** + * This is the Avatar element to be displayed on the left side. + */ + leftAvatar: React.PropTypes.element, + + /** + * This is the Checkbox element to be displayed on the left side. + */ + leftCheckbox: React.PropTypes.element, + + /** + * This is the SvgIcon or FontIcon to be displayed on the left side. + */ + leftIcon: React.PropTypes.element, + + /** + * An array of ListItems to nest underneath the current ListItem. + */ + nestedItems: React.PropTypes.arrayOf(React.PropTypes.element), + + /** + * Controls how deep a ListItem appears. + * This property is automatically managed so modify at your own risk. + */ + nestedLevel: React.PropTypes.number, + + /** + * Override the inline-styles of the nestedItems NestedList. + */ + nestedListStyle: React.PropTypes.object, + + /** + * Called when the ListItem has keyboard focus. + */ + onKeyboardFocus: React.PropTypes.func, + + /** + * Called when the mouse is over the ListItem. + */ + onMouseEnter: React.PropTypes.func, + + /** + * Called when the mouse is no longer over the ListItem. + */ + onMouseLeave: React.PropTypes.func, + + /** + * Called when the ListItem toggles its nested ListItems. + */ + onNestedListToggle: React.PropTypes.func, + + /** + * Called when touches start. + */ + onTouchStart: React.PropTypes.func, + + /** + * Called when a touch tap event occures on the component. + */ + onTouchTap: React.PropTypes.func, + + /** + * This is the block element that contains the primary text. + * If a string is passed in, a div tag will be rendered. + */ + primaryText: React.PropTypes.node, + + /** + * If provided, tapping on the primary text + * of the item toggles the nested list. + */ + primaryTogglesNestedList: React.PropTypes.bool, + + /** + * This is the avatar element to be displayed on the right side. + */ + rightAvatar: React.PropTypes.element, + + /** + * This is the SvgIcon or FontIcon to be displayed on the right side. + */ + rightIcon: React.PropTypes.element, + + /** + * This is the IconButton to be displayed on the right side. + * Hovering over this button will remove the ListItem hover. + * Also, clicking on this button will not trigger a + * ListItem ripple. The event will be stopped and prevented + * from bubbling up to cause a ListItem click. + */ + rightIconButton: React.PropTypes.element, + + /** + * This is the Toggle element to display on the right side. + */ + rightToggle: React.PropTypes.element, + + /** + * This is the block element that contains the secondary text. + * If a string is passed in, a div tag will be rendered. + */ + secondaryText: React.PropTypes.node, + + /** + * Can be 1 or 2. This is the number of secondary + * text lines before ellipsis will show. + */ + secondaryTextLines: React.PropTypes.oneOf([1, 2]), + + /** + * Override the inline-styles of the root element. + */ + style: React.PropTypes.object, + }, + + contextTypes: { + muiTheme: React.PropTypes.object, + }, + + //for passing default theme context to children + childContextTypes: { + muiTheme: React.PropTypes.object, + }, + + mixins: [ + PureRenderMixin, + StylePropable, + ], + + getDefaultProps() { + return { + autoGenerateNestedIndicator: true, + disableKeyboardFocus: false, + disabled: false, + initiallyOpen: false, + insetChildren: false, + nestedItems: [], + nestedLevel: 0, + onKeyboardFocus: () => {}, + onMouseEnter: () => {}, + onMouseLeave: () => {}, + onNestedListToggle: () => {}, + onTouchStart: () => {}, + primaryTogglesNestedList: false, + secondaryTextLines: 1, + }; + }, + + getInitialState() { + return { + hovered: false, + isKeyboardFocused: false, + open: this.props.initiallyOpen, + rightIconButtonHovered: false, + rightIconButtonKeyboardFocused: false, + touch: false, + muiTheme: this.context.muiTheme || getMuiTheme(), + }; + }, + + getChildContext() { + return { + muiTheme: this.state.muiTheme, + }; + }, + + //to update theme inside state whenever a new theme is passed down + //from the parent / owner using context + componentWillReceiveProps(nextProps, nextContext) { + let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; + this.setState({muiTheme: newMuiTheme}); + }, + + applyFocusState(focusState) { + const button = this.refs.enhancedButton; + const buttonEl = ReactDOM.findDOMNode(button); + + if (button) { + switch (focusState) { + case 'none': + buttonEl.blur(); + break; + case 'focused': + buttonEl.focus(); + break; + case 'keyboard-focused': + button.setKeyboardFocus(); + buttonEl.focus(); + break; + } + } + }, + + _createDisabledElement(styles, contentChildren, additionalProps) { + const { + innerDivStyle, + style, + } = this.props; + + const mergedDivStyles = this.mergeStyles( + styles.root, + styles.innerDiv, + innerDivStyle, + style + ); + + return ( +
+ {contentChildren} +
+ ); + }, + + _createLabelElement(styles, contentChildren, additionalProps) { + const { + innerDivStyle, + style, + } = this.props; + + const mergedLabelStyles = this.mergeStyles( + styles.root, + styles.innerDiv, + innerDivStyle, + styles.label, + style + ); + + return ( + + ); + }, + + _createTextElement(styles, data, key) { + const isAnElement = React.isValidElement(data); + const mergedStyles = isAnElement ? + this.mergeStyles(styles, data.props.style) : null; + + return isAnElement ? ( + React.cloneElement(data, { + key: key, + style: this.prepareStyles(mergedStyles), + }) + ) : ( +
+ {data} +
+ ); + }, + + _handleKeyboardFocus(e, isKeyboardFocused) { + this.setState({isKeyboardFocused: isKeyboardFocused}); + this.props.onKeyboardFocus(e, isKeyboardFocused); + }, + + _handleMouseEnter(e) { + this.props.onMouseEnter(e); + }, + + _handleMouseLeave(e) { + this.props.onMouseLeave(e); + }, + + _handleNestedListToggle(e) { + e.stopPropagation(); + this.setState({open: !this.state.open}); + this.props.onNestedListToggle(this); + }, + + _handleRightIconButtonKeyboardFocus(e, isKeyboardFocused) { + const iconButton = this.props.rightIconButton; + let newState = {}; + + newState.rightIconButtonKeyboardFocused = isKeyboardFocused; + if (isKeyboardFocused) newState.isKeyboardFocused = false; + this.setState(newState); + + if (iconButton && iconButton.props.onKeyboardFocus) iconButton.props.onKeyboardFocus(e, isKeyboardFocused); + }, + + _handleRightIconButtonMouseDown(e) { + const iconButton = this.props.rightIconButton; + e.stopPropagation(); + if (iconButton && iconButton.props.onMouseDown) iconButton.props.onMouseDown(e); + }, + + _handleRightIconButtonMouseLeave(e) { + const iconButton = this.props.rightIconButton; + this.setState({rightIconButtonHovered: false}); + if (iconButton && iconButton.props.onMouseLeave) iconButton.props.onMouseLeave(e); + }, + + _handleRightIconButtonMouseEnter(e) { + const iconButton = this.props.rightIconButton; + this.setState({rightIconButtonHovered: true}); + if (iconButton && iconButton.props.onMouseEnter) iconButton.props.onMouseEnter(e); + }, + + _handleRightIconButtonMouseUp(e) { + const iconButton = this.props.rightIconButton; + e.stopPropagation(); + if (iconButton && iconButton.props.onMouseUp) iconButton.props.onMouseUp(e); + }, + + _handleRightIconButtonTouchTap(e) { + const iconButton = this.props.rightIconButton; + + //Stop the event from bubbling up to the list-item + e.stopPropagation(); + if (iconButton && iconButton.props.onTouchTap) iconButton.props.onTouchTap(e); + }, + + _handleTouchStart(e) { + this.setState({touch: true}); + this.props.onTouchStart(e); + }, + + _pushElement(children, element, baseStyles, additionalProps) { + if (element) { + const styles = this.mergeStyles(baseStyles, element.props.style); + children.push( + React.cloneElement(element, { + key: children.length, + style: styles, + ...additionalProps, + }) + ); + } + }, + + render() { + const { + autoGenerateNestedIndicator, + children, + disabled, + disableKeyboardFocus, + innerDivStyle, + insetChildren, + leftAvatar, + leftCheckbox, + leftIcon, + nestedItems, + nestedLevel, + nestedListStyle, + onKeyboardFocus, + onMouseLeave, + onMouseEnter, + onTouchStart, + onTouchTap, + rightAvatar, + rightIcon, + rightIconButton, + leftIconButton, + rightToggle, + primaryText, + primaryTogglesNestedList, + secondaryText, + secondaryTextLines, + style, + ...other, + } = this.props; + + const textColor = this.state.muiTheme.rawTheme.palette.textColor; + const hoverColor = ColorManipulator.fade(textColor, 0.1); + const singleAvatar = !secondaryText && (leftAvatar || rightAvatar); + const singleNoAvatar = !secondaryText && !(leftAvatar || rightAvatar); + const twoLine = secondaryText && secondaryTextLines === 1; + const threeLine = secondaryText && secondaryTextLines > 1; + const hasCheckbox = true; + + const styles = { + root: { + backgroundColor: (this.state.isKeyboardFocused || this.state.hovered) && + !this.state.leftIconButtonHovered && + !this.state.leftIconButtonHovered ? hoverColor : null && + !this.state.rightIconButtonHovered && + !this.state.rightIconButtonKeyboardFocused ? hoverColor : null, + color: textColor, + display: 'block', + fontSize: 16, + lineHeight: '16px', + position: 'relative', + transition: Transitions.easeOut(), + }, + + //This inner div is needed so that ripples will span the entire container + innerDiv: { + marginLeft: nestedLevel * this.state.muiTheme.listItem.nestedLevelDepth, + paddingLeft: leftIcon || leftAvatar || leftIconButton || leftCheckbox || insetChildren ? 72 : 16, + paddingRight: rightIcon || rightAvatar || rightIconButton ? 56 : rightToggle ? 72 : 16, + paddingBottom: singleAvatar ? 20 : 16, + paddingTop: singleNoAvatar || threeLine ? 16 : 20, + position: 'relative', + }, + + icons: { + height: 24, + width: 24, + display: 'block', + position: 'absolute', + top: twoLine ? 12 : singleAvatar ? 4 : 0, + margin: 12, + }, + + leftIcon: { + color: Colors.grey600, + fill: Colors.grey600, + left: 4, + }, + + rightIcon: { + color: Colors.grey400, + fill: Colors.grey400, + right: 4, + }, + + avatars: { + position: 'absolute', + top: singleAvatar ? 8 : 16, + }, + + label: { + cursor: 'pointer', + }, + + leftAvatar: { + left: 16, + }, + + rightAvatar: { + right: 16, + }, + + leftCheckbox: { + position: 'absolute', + display: 'block', + width: 24, + top: twoLine ? 24 : singleAvatar ? 16 : 12, + left: 16, + }, + + primaryText: { + }, + + rightIconButton: { + position: 'absolute', + display: 'block', + top: twoLine ? 12 : singleAvatar ? 4 : 0, + right: 4, + }, + + leftIconButton: { + position: 'absolute', + display: 'block', + top: twoLine ? 12 : singleAvatar ? 4 : 0, + right: 4, + }, + + rightToggle: { + position: 'absolute', + display: 'block', + width: 54, + top: twoLine ? 25 : singleAvatar ? 17 : 13, + right: 8, + }, + + secondaryText: { + fontSize: 14, + lineHeight: threeLine ? '18px' : '16px', + height: threeLine ? 36 : 16, + margin: 0, + marginTop: 4, + color: Typography.textLightBlack, + + //needed for 2 and 3 line ellipsis + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: threeLine ? null : 'nowrap', + display: threeLine ? '-webkit-box' : null, + WebkitLineClamp: threeLine ? 2 : null, + WebkitBoxOrient: threeLine ? 'vertical' : null, + }, + }; + + let contentChildren = [children]; + + if (leftIcon) { + this._pushElement( + contentChildren, + leftIcon, + this.mergeStyles(styles.icons, styles.leftIcon) + ); + } + + if (rightIcon) { + this._pushElement( + contentChildren, + rightIcon, + this.mergeStyles(styles.icons, styles.rightIcon) + ); + } + + if (leftAvatar) { + this._pushElement( + contentChildren, + leftAvatar, + this.mergeStyles(styles.avatars, styles.leftAvatar) + ); + } + + if (rightAvatar) { + this._pushElement( + contentChildren, + rightAvatar, + this.mergeStyles(styles.avatars, styles.rightAvatar) + ); + } + + if (leftCheckbox) { + this._pushElement( + contentChildren, + leftCheckbox, + this.mergeStyles(styles.leftCheckbox) + ); + } + + //RightIconButtonElement + const hasNestListItems = nestedItems.length; + const hasRightElement = rightAvatar || rightIcon || rightIconButton || rightToggle; + const needsNestedIndicator = hasNestListItems && autoGenerateNestedIndicator && !hasRightElement; + + if (rightIconButton || needsNestedIndicator) { + let rightIconButtonElement = rightIconButton; + let rightIconButtonHandlers = { + onKeyboardFocus: this._handleRightIconButtonKeyboardFocus, + onMouseEnter: this._handleRightIconButtonMouseEnter, + onMouseLeave: this._handleRightIconButtonMouseLeave, + onTouchTap: this._handleRightIconButtonTouchTap, + onMouseDown: this._handleRightIconButtonMouseUp, + onMouseUp: this._handleRightIconButtonMouseUp, + }; + + // Create a nested list indicator icon if we don't have an icon on the right + if (needsNestedIndicator) { + rightIconButtonElement = this.state.open ? + : + ; + rightIconButtonHandlers.onTouchTap = this._handleNestedListToggle; + } + + this._pushElement( + contentChildren, + rightIconButtonElement, + this.mergeStyles(styles.rightIconButton), + rightIconButtonHandlers + ); + } + + if (rightToggle) { + this._pushElement( + contentChildren, + rightToggle, + this.mergeStyles(styles.rightToggle) + ); + } + + if (primaryText) { + const secondaryTextElement = this._createTextElement( + styles.primaryText, + primaryText, + 'primaryText' + ); + contentChildren.push(secondaryTextElement); + } + + if (secondaryText) { + const secondaryTextElement = this._createTextElement( + styles.secondaryText, + secondaryText, + 'secondaryText' + ); + contentChildren.push(secondaryTextElement); + } + + const nestedList = nestedItems.length ? ( + + {nestedItems} + + ) : undefined; + + return ( +
+ { + hasCheckbox ? this._createLabelElement(styles, contentChildren, other) : + disabled ? this._createDisabledElement(styles, contentChildren, other) : ( + +
+ {contentChildren} +
+
+ ) + } + {nestedList} +
+ ); + }, + +}); + +export default Item; + diff --git a/app/components/LibraryNav.jsx b/app/components/LibraryNav.jsx index b3ef80a..f89b7d7 100644 --- a/app/components/LibraryNav.jsx +++ b/app/components/LibraryNav.jsx @@ -15,7 +15,7 @@ import Folder from 'material-ui/lib/svg-icons/file/folder' import Edit from 'material-ui/lib/svg-icons/editor/mode-edit' import Delete from 'material-ui/lib/svg-icons/action/delete' import Divider from 'material-ui/lib/divider' -import { SelectableContainerEnhance } from '../enhance/SelectableEnhance' +import SelectableList from 'SelectableList' import uuid from 'node-uuid' import path from 'path-extra' @@ -64,36 +64,6 @@ const { LeftNav, Paper} = mui -let SelectableList = SelectableContainerEnhance(List) - -function wrapState(ComposedComponent) { - const StateWrapper = React.createClass({ - getInitialState() { - return {selectedIndex: -1}; - }, - setIndex(i, func){ - this.setState({ - selectedIndex: i, - }, func); - }, - handleUpdateSelectedIndex(e, index) { - this.setIndex(index); - }, - render() { - return ( - - ); - }, - }); - return StateWrapper; -} - -SelectableList = wrapState(SelectableList) - const DefaultRawTheme = Styles.LightRawTheme export default class LibraryNav extends React.Component { @@ -432,7 +402,12 @@ export default class LibraryNav extends React.Component { render(){ return (
- + {this.state.navItems.map((item, i) => { return - -
NoteBooks
- - - -
}> + +
NoteBooks
+ + + + }> {this.notebookList()}
diff --git a/app/components/SelectableList.jsx b/app/components/SelectableList.jsx new file mode 100644 index 0000000..dc62dd9 --- /dev/null +++ b/app/components/SelectableList.jsx @@ -0,0 +1,35 @@ +import React from 'react' + +import styles from 'material-ui/lib/styles' +import List from 'material-ui/lib/lists/list' +import { SelectableContainerEnhance } from '../enhance/SelectableEnhance' + +let SelectableList = SelectableContainerEnhance(List) + +function wrapState(ComposedComponent) { + const StateWrapper = React.createClass({ + getInitialState() { + return {selectedIndex: -1}; + }, + setIndex(i, func){ + this.setState({ + selectedIndex: i, + }, func); + }, + handleUpdateSelectedIndex(e, index) { + this.setIndex(index); + }, + render() { + return ( + + ); + }, + }); + return StateWrapper; +} + +export default SelectableList = wrapState(SelectableList) diff --git a/style.css b/style.css index 817ae3b..352e80b 100644 --- a/style.css +++ b/style.css @@ -12,6 +12,14 @@ body, html{ -webkit-user-select: none; } +#search-bar { + padding-top: 0px !important; + padding-bottom: 0px !important; +} + +.list { + background-color: #EFEFEF !important; +} .list > div:first-child{ -webkit-user-select: none; } @@ -25,9 +33,13 @@ body, html{ .right{ float: right; } +#entry-list{ + padding-top: 0px !important; +} #entry-selector{ border-right: solid 1px #d9d9d9; min-width: 250px; + max-width: 390px !important; } #library-nav{ border-right: solid 1px #d9d9d9;