From f0de382367c37a72cb2018e78fbfe8cefac069e0 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 25 Jun 2019 23:21:04 -0700 Subject: [PATCH] fix: Deeply nested document breadcrumb menu --- app/components/DropdownMenu/DropdownMenu.js | 51 +++++++++++-------- app/scenes/Search/components/FilterOptions.js | 2 +- shared/components/Breadcrumb.js | 38 +++++++++++--- shared/components/BreadcrumbMenu.js | 25 +++++++++ 4 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 shared/components/BreadcrumbMenu.js diff --git a/app/components/DropdownMenu/DropdownMenu.js b/app/components/DropdownMenu/DropdownMenu.js index 849b5b8c..c49ee806 100644 --- a/app/components/DropdownMenu/DropdownMenu.js +++ b/app/components/DropdownMenu/DropdownMenu.js @@ -21,7 +21,7 @@ type Props = { children?: Children, className?: string, style?: Object, - leftAlign?: boolean, + position?: 'left' | 'right' | 'center', }; @observer @@ -44,8 +44,10 @@ class DropdownMenu extends React.Component { const targetRect = currentTarget.getBoundingClientRect(); this.top = targetRect.bottom - bodyRect.top; - if (this.props.leftAlign) { + if (this.props.position === 'left') { this.left = targetRect.left; + } else if (this.props.position === 'center') { + this.left = targetRect.left + targetRect.width / 2; } else { this.right = bodyRect.width - targetRect.left - targetRect.width; } @@ -61,7 +63,7 @@ class DropdownMenu extends React.Component { }; render() { - const { className, label, children } = this.props; + const { className, label, position, children } = this.props; return (
@@ -77,24 +79,28 @@ class DropdownMenu extends React.Component { {label} {portal( - { - ev.stopPropagation(); - closePortal(); - } - } - style={this.props.style} + - {typeof children === 'function' - ? children({ closePortal }) - : children} - + { + ev.stopPropagation(); + closePortal(); + } + } + style={this.props.style} + > + {typeof children === 'function' + ? children({ closePortal }) + : children} + + )} )} @@ -112,16 +118,19 @@ const Label = styled(Flex).attrs({ cursor: pointer; `; -const Menu = styled.div` - animation: ${fadeAndScaleIn} 200ms ease; - transform-origin: ${({ left }) => (left !== undefined ? '25%' : '75%')} 0; - +const Position = styled.div` position: absolute; ${({ left }) => (left !== undefined ? `left: ${left}px` : '')}; ${({ right }) => (right !== undefined ? `right: ${right}px` : '')}; top: ${({ top }) => top}px; z-index: 1000; + transform: ${props => + props.position === 'center' ? 'translateX(-50%)' : 'initial'}; +`; +const Menu = styled.div` + animation: ${fadeAndScaleIn} 200ms ease; + transform-origin: ${props => (props.left !== undefined ? '25%' : '75%')} 0; background: ${props => props.theme.menuBackground}; border-radius: 2px; padding: 0.5em 0; diff --git a/app/scenes/Search/components/FilterOptions.js b/app/scenes/Search/components/FilterOptions.js index 51352848..7ab5b589 100644 --- a/app/scenes/Search/components/FilterOptions.js +++ b/app/scenes/Search/components/FilterOptions.js @@ -79,7 +79,7 @@ const SearchFilter = props => { {props.label} } - leftAlign + position="left" > {({ closePortal }) => ( diff --git a/shared/components/Breadcrumb.js b/shared/components/Breadcrumb.js index 9c7f43bb..30e039ff 100644 --- a/shared/components/Breadcrumb.js +++ b/shared/components/Breadcrumb.js @@ -4,12 +4,18 @@ import { observer, inject } from 'mobx-react'; import breakpoint from 'styled-components-breakpoint'; import styled from 'styled-components'; import { Link } from 'react-router-dom'; -import { CollectionIcon, PrivateCollectionIcon, GoToIcon } from 'outline-icons'; +import { + CollectionIcon, + PrivateCollectionIcon, + GoToIcon, + MoreIcon, +} from 'outline-icons'; import Document from 'models/Document'; import CollectionsStore from 'stores/CollectionsStore'; import { collectionUrl } from 'utils/routeHelpers'; import Flex from 'shared/components/Flex'; +import BreadcrumbMenu from './BreadcrumbMenu'; type Props = { document: Document, @@ -37,6 +43,10 @@ const Breadcrumb = observer(({ document, collections, onlyText }: Props) => { ); } + const isNestedDocument = path.length > 1; + const lastPath = path.length ? path[path.length - 1] : undefined; + const menuPath = isNestedDocument ? path.slice(0, -1) : []; + return ( @@ -47,14 +57,19 @@ const Breadcrumb = observer(({ document, collections, onlyText }: Props) => { )}{' '} {collection.name} - {path.map(n => ( - + {isNestedDocument && ( + + } path={menuPath} /> + + )} + {lastPath && ( + {' '} - - {n.title} + + {lastPath.title} - ))} + )} ); }); @@ -80,6 +95,17 @@ const Slash = styled(GoToIcon)` opacity: 0.25; `; +const Overflow = styled(MoreIcon)` + flex-shrink: 0; + opacity: 0.25; + transition: opacity 100ms ease-in-out; + + &:hover, + &:active { + opacity: 1; + } +`; + const Crumb = styled(Link)` color: ${props => props.theme.text}; font-size: 15px; diff --git a/shared/components/BreadcrumbMenu.js b/shared/components/BreadcrumbMenu.js new file mode 100644 index 00000000..be2c6112 --- /dev/null +++ b/shared/components/BreadcrumbMenu.js @@ -0,0 +1,25 @@ +// @flow +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; + +type Props = { + label: React.Node, + path: Array, +}; + +export default class BreadcrumbMenu extends React.Component { + render() { + const { path } = this.props; + + return ( + + {path.map(item => ( + + {item.title} + + ))} + + ); + } +}