diff --git a/frontend/components/DropdownMenu/DropdownMenu.js b/frontend/components/DropdownMenu/DropdownMenu.js index d36da02c..e4ba99ac 100644 --- a/frontend/components/DropdownMenu/DropdownMenu.js +++ b/frontend/components/DropdownMenu/DropdownMenu.js @@ -1,80 +1,98 @@ // @flow import React from 'react'; +import invariant from 'invariant'; import { observable } from 'mobx'; import { observer } from 'mobx-react'; -import keydown from 'react-keydown'; import styled from 'styled-components'; +import Portal from 'react-portal'; import Flex from 'components/Flex'; import { color } from 'styles/constants'; import { fadeAndScaleIn } from 'styles/animations'; type DropdownMenuProps = { label: React.Element, + onShow?: Function, + onClose?: Function, children?: React.Element, style?: Object, }; @observer class DropdownMenu extends React.Component { props: DropdownMenuProps; + actionRef: Object; @observable open: boolean = false; + @observable top: number; + @observable left: number; + @observable right: number; - handleClick = () => { - this.open = !this.open; + handleClick = (ev: SyntheticEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + invariant(document.body, 'why you not here'); + const bodyRect = document.body.getBoundingClientRect(); + // $FlowIssue it's there + const targetRect = ev.currentTarget.getBoundingClientRect(); + this.open = true; + this.top = targetRect.bottom - bodyRect.top; + this.right = bodyRect.width - targetRect.left - targetRect.width; + if (this.props.onShow) this.props.onShow(); }; - @keydown('esc') - handleEscape() { - this.open = false; - } - - handleClickOutside = (ev: SyntheticEvent) => { - ev.stopPropagation(); + handleClose = (ev: SyntheticEvent) => { this.open = false; + if (this.props.onClose) this.props.onClose(); }; render() { + const openAction = ( + + ); + return ( - - {this.open && } - - {this.open && - +
+ {openAction} + + {this.props.children} - } - +
+ + ); } } -const Backdrop = styled.div` - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 999; -`; - const Label = styled(Flex).attrs({ justify: 'center', align: 'center', })` + width: 22px; + height: 22px; z-index: 1000; cursor: pointer; `; -const MenuContainer = styled.div` - position: relative; -`; - const Menu = styled.div` animation: ${fadeAndScaleIn} 250ms ease; transform-origin: 75% 0; position: absolute; - right: 0; + right: ${({ right }) => right}px; + top: ${({ top }) => top}px; z-index: 1000; border: ${color.slateLight}; background: ${color.white}; diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index ae63413a..c3ab6d95 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -14,11 +14,9 @@ import { LoadingIndicatorBar } from 'components/LoadingIndicator'; import Scrollable from 'components/Scrollable'; import Icon from 'components/Icon'; import Toasts from 'components/Toasts'; -import CollectionMenu from 'menus/CollectionMenu'; import AccountMenu from 'menus/AccountMenu'; -import SidebarCollection from './components/SidebarCollection'; -import SidebarCollectionList from './components/SidebarCollectionList'; +import SidebarCollections from './components/SidebarCollections'; import SidebarLink from './components/SidebarLink'; import HeaderBlock from './components/HeaderBlock'; import Modals from './components/Modals'; @@ -84,7 +82,7 @@ type Props = { }; render() { - const { auth, documents, collections, ui } = this.props; + const { auth, documents, ui } = this.props; const { user, team } = auth; return ( @@ -130,20 +128,11 @@ type Props = { - {collections.active - ? - - - : - - } - {collections.active - ? - : } + @@ -160,18 +149,6 @@ type Props = { } } -const CollectionAction = styled.a` - position: absolute; - top: -4px; - right: ${layout.hpadding}; - color: ${color.slate}; - svg { opacity: .75; } - - &:hover { - svg { opacity: 1; } - } -`; - const Container = styled(Flex)` position: relative; width: 100%; diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js deleted file mode 100644 index 4ada3fa9..00000000 --- a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js +++ /dev/null @@ -1,84 +0,0 @@ -// @flow -import React from 'react'; -import { observer } from 'mobx-react'; -import Flex from 'components/Flex'; -import styled from 'styled-components'; -import { color, layout } from 'styles/constants'; -import SidebarLink from '../SidebarLink'; -import DropToImport from 'components/DropToImport'; - -import Collection from 'models/Collection'; -import Document from 'models/Document'; -import type { NavigationNode } from 'types'; - -type Props = { - collection: ?Collection, - document: ?Document, - history: Object, -}; - -const activeStyle = { - color: color.black, - background: color.slateDark, -}; - -@observer class SidebarCollection extends React.Component { - props: Props; - - renderDocuments(documentList: Array, depth: number = 0) { - const { document, history } = this.props; - const canDropToImport = depth === 0; - - if (document) { - return documentList.map(doc => ( - - {canDropToImport && - - {doc.title} - } - {!canDropToImport && - {doc.title}} - - {(document.pathToDocument.map(entry => entry.id).includes(doc.id) || - document.id === doc.id) && - - {doc.children && this.renderDocuments(doc.children, depth + 1)} - } - - )); - } - } - - render() { - const { collection } = this.props; - - if (collection) { - return ( - -
{collection.name}
- {this.renderDocuments(collection.documents)} -
- ); - } - return null; - } -} - -const Header = styled(Flex)` - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - color: ${color.slate}; - letter-spacing: 0.04em; - padding: 0 ${layout.hpadding}; -`; - -const Children = styled(Flex)` - margin-left: 20px; -`; - -export default SidebarCollection; diff --git a/frontend/components/Layout/components/SidebarCollection/index.js b/frontend/components/Layout/components/SidebarCollection/index.js deleted file mode 100644 index 80b2a046..00000000 --- a/frontend/components/Layout/components/SidebarCollection/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import SidebarCollection from './SidebarCollection'; -export default SidebarCollection; diff --git a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js deleted file mode 100644 index 9318e437..00000000 --- a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow -import React from 'react'; -import { observer, inject } from 'mobx-react'; -import Flex from 'components/Flex'; -import styled from 'styled-components'; -import { color, layout, fontWeight } from 'styles/constants'; - -import SidebarLink from '../SidebarLink'; -import DropToImport from 'components/DropToImport'; - -import CollectionsStore from 'stores/CollectionsStore'; - -type Props = { - history: Object, - collections: CollectionsStore, -}; - -const activeStyle = { - color: color.black, - background: color.slateDark, -}; - -const SidebarCollectionList = observer(({ history, collections }: Props) => { - return ( - -
Collections
- {collections.data.map(collection => ( - - - {collection.name} - - - ))} -
- ); -}); - -const Header = styled(Flex)` - font-size: 11px; - font-weight: ${fontWeight.semiBold}; - text-transform: uppercase; - color: ${color.slate}; - letter-spacing: 0.04em; - padding: 0 ${layout.hpadding}; -`; - -export default inject('collections')(SidebarCollectionList); diff --git a/frontend/components/Layout/components/SidebarCollectionList/index.js b/frontend/components/Layout/components/SidebarCollectionList/index.js deleted file mode 100644 index ed4ba40c..00000000 --- a/frontend/components/Layout/components/SidebarCollectionList/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import SidebarCollectionList from './SidebarCollectionList'; -export default SidebarCollectionList; diff --git a/frontend/components/Layout/components/SidebarCollections.js b/frontend/components/Layout/components/SidebarCollections.js new file mode 100644 index 00000000..62ec4eea --- /dev/null +++ b/frontend/components/Layout/components/SidebarCollections.js @@ -0,0 +1,171 @@ +// @flow +import React from 'react'; +import { observable } from 'mobx'; +import { observer, inject } from 'mobx-react'; +import Flex from 'components/Flex'; +import styled from 'styled-components'; +import { color, layout, fontWeight } from 'styles/constants'; + +import SidebarLink from './SidebarLink'; +import DropToImport from 'components/DropToImport'; +import Icon from 'components/Icon'; +import CollectionMenu from 'menus/CollectionMenu'; + +import CollectionsStore from 'stores/CollectionsStore'; +import UiStore from 'stores/UiStore'; +import Document from 'models/Document'; +import { type NavigationNode } from 'types'; + +type Props = { + history: Object, + collections: CollectionsStore, + activeDocument: ?Document, + onCreateCollection: Function, + ui: UiStore, +}; + +const activeStyle = { + color: color.black, + background: color.slateDark, +}; + +@observer class SidebarCollections extends React.PureComponent { + props: Props; + + render() { + const { collections, activeDocument, ui } = this.props; + + return ( + +
Collections
+ {collections.data.map(collection => ( + + ))} + + {collections.isLoaded && + + Add new collection + } +
+ ); + } +} + +@observer class CollectionLink extends React.Component { + @observable isHovering = false; + @observable menuOpen = false; + + handleHover = () => (this.isHovering = true); + handleBlur = () => { + if (!this.menuOpen) this.isHovering = false; + }; + + render() { + const { collection, activeDocument, ui } = this.props; + return ( + + + + {collection.name} + + {(this.isHovering || this.menuOpen) && + + (this.menuOpen = true)} + onClose={() => (this.menuOpen = false)} + /> + } + + {collection.id === ui.activeCollectionId && + collection.documents.map(document => ( + + ))} + + + ); + } +} + +type DocumentLinkProps = { + document: NavigationNode, + activeDocument: ?Document, + depth: number, +}; + +const DocumentLink = observer((props: DocumentLinkProps) => { + const { document, activeDocument, depth } = props; + const canDropToImport = depth === 0; + + return ( + + {canDropToImport && + + {document.title} + } + {!canDropToImport && + {document.title}} + + {activeDocument && + (activeDocument.pathToDocument + .map(entry => entry.id) + .includes(document.id) || + activeDocument.id === document.id) && + + {document.children && + document.children.map(childDocument => ( + + ))} + } + + ); +}); + +const Header = styled(Flex)` + font-size: 11px; + font-weight: ${fontWeight.semiBold}; + text-transform: uppercase; + color: ${color.slate}; + letter-spacing: 0.04em; + padding: 0 ${layout.hpadding}; +`; + +const CollectionAction = styled.a` + color: ${color.slate}; + svg { opacity: .75; } + + &:hover { + svg { opacity: 1; } + } +`; + +const Children = styled(Flex)` + margin-left: 20px; +`; + +export default inject('collections', 'ui')(SidebarCollections); diff --git a/frontend/components/Layout/components/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink.js similarity index 70% rename from frontend/components/Layout/components/SidebarLink/SidebarLink.js rename to frontend/components/Layout/components/SidebarLink.js index cb6f2b0e..b8017198 100644 --- a/frontend/components/Layout/components/SidebarLink/SidebarLink.js +++ b/frontend/components/Layout/components/SidebarLink.js @@ -9,21 +9,24 @@ const activeStyle = { fontWeight: fontWeight.semiBold, }; -function SidebarLink(props: Object) { - return ; -} - -const StyledNavLink = styled(NavLink)` +// $FlowFixMe :/ +const styleComponent = component => styled(component)` display: block; overflow: hidden; text-overflow: ellipsis; margin: 5px ${layout.hpadding}; color: ${color.slateDark}; font-size: 15px; + cursor: pointer; &:hover { color: ${color.text}; } `; +function SidebarLink(props: Object) { + const Component = styleComponent(props.to ? NavLink : 'div'); + return ; +} + export default SidebarLink; diff --git a/frontend/components/Layout/components/SidebarLink/index.js b/frontend/components/Layout/components/SidebarLink/index.js deleted file mode 100644 index eabfd77f..00000000 --- a/frontend/components/Layout/components/SidebarLink/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import SidebarLink from './SidebarLink'; -export default SidebarLink; diff --git a/frontend/components/Layout/components/Title/Title.js b/frontend/components/Layout/components/Title.js similarity index 100% rename from frontend/components/Layout/components/Title/Title.js rename to frontend/components/Layout/components/Title.js diff --git a/frontend/components/Layout/components/Title/index.js b/frontend/components/Layout/components/Title/index.js deleted file mode 100644 index 8dffbce8..00000000 --- a/frontend/components/Layout/components/Title/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import Title from './Title'; -export default Title; diff --git a/frontend/menus/CollectionMenu.js b/frontend/menus/CollectionMenu.js index 9fdff8ac..ded53d91 100644 --- a/frontend/menus/CollectionMenu.js +++ b/frontend/menus/CollectionMenu.js @@ -1,6 +1,5 @@ // @flow import React, { Component } from 'react'; -import { withRouter } from 'react-router-dom'; import { inject, observer } from 'mobx-react'; import Collection from 'models/Collection'; import UiStore from 'stores/UiStore'; @@ -10,6 +9,8 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; @observer class CollectionMenu extends Component { props: { label?: React$Element, + onShow?: Function, + onClose?: Function, history: Object, ui: UiStore, collection: Collection, @@ -26,11 +27,15 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; }; render() { - const { collection, label } = this.props; + const { collection, label, onShow, onClose } = this.props; const { allowDelete } = collection; return ( - }> + } + onShow={onShow} + onClose={onClose} + > {collection && Edit} {allowDelete && @@ -40,4 +45,4 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; } } -export default withRouter(inject('ui')(CollectionMenu)); +export default inject('ui')(CollectionMenu); diff --git a/frontend/scenes/Collection/Collection.js b/frontend/scenes/Collection/Collection.js index 813e2b5f..f5984c78 100644 --- a/frontend/scenes/Collection/Collection.js +++ b/frontend/scenes/Collection/Collection.js @@ -29,12 +29,17 @@ type Props = { @observable redirectUrl; componentDidMount = () => { - this.fetchDocument(); + this.fetchDocument(this.props.match.params.id); }; - fetchDocument = async () => { + componentWillReceiveProps(nextProps) { + if (nextProps.match.params.id !== this.props.match.params.id) { + this.fetchDocument(nextProps.match.params.id); + } + } + + fetchDocument = async (id: string) => { const { collections } = this.props; - const { id } = this.props.match.params; this.collection = await collections.getById(id);