From a515631e2142bbda63ec8d5e438775a48205adc4 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 13 Jul 2019 10:15:38 -0700 Subject: [PATCH] feat: document menu available in sidebar (#986) * feat: document menu available in sidebar * fix: more accessible blue * feat: accessible blue feat: clearer new doc button closes #983 * lint --- app/components/Actions.js | 2 +- app/components/Button.js | 27 +++++---- app/components/Collaborators.js | 16 +++--- app/components/DropdownMenu/DropdownMenu.js | 2 +- .../Sidebar/components/CollectionLink.js | 18 ++++-- .../Sidebar/components/Collections.js | 1 + .../Sidebar/components/DocumentLink.js | 56 +++++++++++++------ .../Sidebar/components/SidebarLink.js | 39 ++++++------- app/menus/CollectionMenu.js | 8 ++- app/menus/DocumentMenu.js | 23 +++++++- app/menus/NewDocumentMenu.js | 11 +++- app/scenes/Collection.js | 9 +-- app/scenes/Dashboard.js | 3 +- app/scenes/Document/components/Header.js | 50 ++++++++++------- app/scenes/Drafts.js | 3 +- app/scenes/Starred.js | 3 +- app/scenes/UserProfile.js | 3 +- shared/styles/theme.js | 10 ++-- 18 files changed, 176 insertions(+), 108 deletions(-) diff --git a/app/components/Actions.js b/app/components/Actions.js index 89bc091b5..f2c57f45b 100644 --- a/app/components/Actions.js +++ b/app/components/Actions.js @@ -19,7 +19,7 @@ export const Action = styled(Flex)` export const Separator = styled.div` margin-left: 12px; width: 1px; - height: 20px; + height: 28px; background: ${props => props.theme.divider}; `; diff --git a/app/components/Button.js b/app/components/Button.js index aef597472..6600e4a7d 100644 --- a/app/components/Button.js +++ b/app/components/Button.js @@ -13,11 +13,10 @@ const RealButton = styled.button` color: ${props => props.theme.buttonText}; box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 2px; border-radius: 4px; - font-size: 12px; + font-size: 14px; font-weight: 500; - height: ${props => (props.small ? 24 : 36)}px; + height: 32px; text-decoration: none; - text-transform: uppercase; flex-shrink: 0; outline: none; cursor: pointer; @@ -39,7 +38,7 @@ const RealButton = styled.button` &:disabled { cursor: default; pointer-events: none; - color: ${props => props.theme.textTertiary}; + color: ${props => props.theme.white50}; } ${props => @@ -58,6 +57,10 @@ const RealButton = styled.button` background: ${darken(0.05, props.theme.buttonNeutralBackground)}; border: 1px solid ${darken(0.15, props.theme.buttonNeutralBackground)}; } + + &:disabled { + color: ${props.theme.textTertiary}; + } `} ${props => props.danger && ` @@ -80,15 +83,13 @@ const Label = styled.span` const Inner = styled.span` display: flex; - padding: 0 ${props => (props.small ? 8 : 12)}px; - padding-right: ${props => (props.disclosure ? 2 : props.small ? 8 : 12)}px; - line-height: ${props => (props.small ? 24 : 28)}px; + padding: 0 8px; + padding-right: ${props => (props.disclosure ? 2 : 8)}px; + line-height: ${props => (props.hasIcon ? 24 : 32)}px; justify-content: center; align-items: center; - ${props => - props.hasIcon && - (props.small ? 'padding-left: 6px;' : 'padding-left: 8px;')}; + ${props => props.hasIcon && 'padding-left: 4px;'}; `; export type Props = { @@ -97,7 +98,6 @@ export type Props = { icon?: React.Node, className?: string, children?: React.Node, - small?: boolean, disclosure?: boolean, }; @@ -107,15 +107,14 @@ export default function Button({ children, value, disclosure, - small, ...rest }: Props) { const hasText = children !== undefined || value !== undefined; const hasIcon = icon !== undefined; return ( - - + + {hasIcon && icon} {hasText && } {disclosure && } diff --git a/app/components/Collaborators.js b/app/components/Collaborators.js index fa975b120..b6d066ba6 100644 --- a/app/components/Collaborators.js +++ b/app/components/Collaborators.js @@ -60,9 +60,8 @@ class Collaborators extends React.Component { {overflow > 0 && +{overflow}} {mostRecentViewers.map(({ lastViewedAt, user }) => ( - + {user.name} @@ -76,6 +75,7 @@ class Collaborators extends React.Component { this.handleOpenProfile(user.id)} + size={32} /> @@ -87,9 +87,8 @@ class Collaborators extends React.Component { ))} {collaborators.map(user => ( - + {user.name} @@ -105,6 +104,7 @@ class Collaborators extends React.Component { this.handleOpenProfile(user.id)} + size={32} /> @@ -133,14 +133,14 @@ const AvatarPile = styled(Tooltip)` `; const Viewer = styled.div` - width: 24px; - height: 24px; + width: 32px; + height: 32px; opacity: 0.75; `; const Collaborator = styled.div` - width: 24px; - height: 24px; + width: 32px; + height: 32px; `; const More = styled.div` diff --git a/app/components/DropdownMenu/DropdownMenu.js b/app/components/DropdownMenu/DropdownMenu.js index c49ee8069..f96ffee3d 100644 --- a/app/components/DropdownMenu/DropdownMenu.js +++ b/app/components/DropdownMenu/DropdownMenu.js @@ -119,7 +119,7 @@ const Label = styled(Flex).attrs({ `; const Position = styled.div` - position: absolute; + position: fixed; ${({ left }) => (left !== undefined ? `left: ${left}px` : '')}; ${({ right }) => (right !== undefined ? `right: ${right}px` : '')}; top: ${({ top }) => top}px; diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.js index 73df55fe7..ec5b53bb8 100644 --- a/app/components/Sidebar/components/CollectionLink.js +++ b/app/components/Sidebar/components/CollectionLink.js @@ -7,6 +7,7 @@ import Collection from 'models/Collection'; import Document from 'models/Document'; import CollectionMenu from 'menus/CollectionMenu'; import UiStore from 'stores/UiStore'; +import DocumentsStore from 'stores/DocumentsStore'; import SidebarLink from './SidebarLink'; import DocumentLink from './DocumentLink'; import DropToImport from 'components/DropToImport'; @@ -15,6 +16,7 @@ import Flex from 'shared/components/Flex'; type Props = { collection: Collection, ui: UiStore, + documents: DocumentsStore, activeDocument: ?Document, prefetchDocument: (id: string) => *, }; @@ -24,7 +26,13 @@ class CollectionLink extends React.Component { @observable menuOpen = false; render() { - const { collection, activeDocument, prefetchDocument, ui } = this.props; + const { + collection, + documents, + activeDocument, + prefetchDocument, + ui, + } = this.props; const expanded = collection.id === ui.activeCollectionId; return ( @@ -54,6 +62,7 @@ class CollectionLink extends React.Component { exact={false} menu={ (this.menuOpen = true)} onClose={() => (this.menuOpen = false)} @@ -61,10 +70,11 @@ class CollectionLink extends React.Component { } > - {collection.documents.map(document => ( + {collection.documents.map(node => ( { {collections.orderedData.map(collection => ( *, @@ -20,17 +25,20 @@ type Props = { @observer class DocumentLink extends React.Component { + @observable menuOpen = false; + handleMouseEnter = (ev: SyntheticEvent<*>) => { - const { document, prefetchDocument } = this.props; + const { node, prefetchDocument } = this.props; ev.stopPropagation(); ev.preventDefault(); - prefetchDocument(document.id); + prefetchDocument(node.id); }; render() { const { - document, + node, + documents, collection, activeDocument, activeDocumentRef, @@ -38,44 +46,60 @@ class DocumentLink extends React.Component { depth, } = this.props; - const isActiveDocument = - activeDocument && activeDocument.id === document.id; + const isActiveDocument = activeDocument && activeDocument.id === node.id; const showChildren = !!( activeDocument && collection && (collection .pathToDocument(activeDocument) .map(entry => entry.id) - .includes(document.id) || + .includes(node.id) || isActiveDocument) ); - const hasChildren = !!document.children.length; + const hasChildren = !!node.children.length; + const document = documents.get(node.id); return ( - + + (this.menuOpen = true)} + onClose={() => (this.menuOpen = false)} + /> + + ) : ( + undefined + ) + } > {hasChildren && ( - {document.children.map(childDocument => ( + {node.children.map(childNode => ( { }; return ( - + { )} {label} + {menu && {menu}} {this.expanded && children} - {menu && {menu}} ); } @@ -107,23 +107,8 @@ const IconWrapper = styled.span` height: 24px; `; -const StyledNavLink = styled(NavLink)` - display: flex; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - padding: 4px 16px; - border-radius: 4px; - color: ${props => props.theme.sidebarText}; - font-size: 15px; - cursor: pointer; - - &:hover { - color: ${props => props.theme.text}; - } -`; - const Action = styled.span` + display: ${props => (props.menuOpen ? 'inline' : 'none')}; position: absolute; top: 4px; right: 4px; @@ -140,11 +125,19 @@ const Action = styled.span` } `; -const Wrapper = styled(Flex)` +const StyledNavLink = styled(NavLink)` + display: flex; position: relative; + overflow: hidden; + text-overflow: ellipsis; + padding: 4px 16px; + border-radius: 4px; + color: ${props => props.theme.sidebarText}; + font-size: 15px; + cursor: pointer; - > ${Action} { - display: ${props => (props.menuOpen ? 'inline' : 'none')}; + &:hover { + color: ${props => props.theme.text}; } &:hover { @@ -154,6 +147,10 @@ const Wrapper = styled(Flex)` } `; +const Wrapper = styled(Flex)` + position: relative; +`; + const Label = styled.div` position: relative; width: 100%; diff --git a/app/menus/CollectionMenu.js b/app/menus/CollectionMenu.js index f3e8970c8..c98c12c76 100644 --- a/app/menus/CollectionMenu.js +++ b/app/menus/CollectionMenu.js @@ -18,12 +18,13 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; type Props = { label?: React.Node, - onOpen?: () => *, - onClose?: () => *, + position?: 'left' | 'right' | 'center', ui: UiStore, documents: DocumentsStore, collection: Collection, history: Object, + onOpen?: () => void, + onClose?: () => void, }; @observer @@ -88,7 +89,7 @@ class CollectionMenu extends React.Component { }; render() { - const { collection, label, onOpen, onClose } = this.props; + const { collection, label, position, onOpen, onClose } = this.props; return ( @@ -112,6 +113,7 @@ class CollectionMenu extends React.Component { label={label || } onOpen={onOpen} onClose={onClose} + position={position} > {collection && ( diff --git a/app/menus/DocumentMenu.js b/app/menus/DocumentMenu.js index e3af7a4a9..a90955583 100644 --- a/app/menus/DocumentMenu.js +++ b/app/menus/DocumentMenu.js @@ -20,12 +20,15 @@ type Props = { ui: UiStore, auth: AuthStore, label?: React.Node, + position?: 'left' | 'right' | 'center', document: Document, collections: CollectionStore, className: string, showPrint?: boolean, showToggleEmbeds?: boolean, showPin?: boolean, + onOpen?: () => void, + onClose?: () => void, }; @observer @@ -102,7 +105,17 @@ class DocumentMenu extends React.Component { render() { if (this.redirectTo) return ; - const { document, label, className, showPrint, showPin, auth } = this.props; + const { + document, + position, + label, + className, + showPrint, + showPin, + auth, + onOpen, + onClose, + } = this.props; const canShareDocuments = auth.team && auth.team.sharing; if (document.isArchived) { @@ -119,7 +132,13 @@ class DocumentMenu extends React.Component { } return ( - } className={className}> + } + className={className} + position={position} + onOpen={onOpen} + onClose={onClose} + > {!document.isDraft ? ( {showPin && diff --git a/app/menus/NewDocumentMenu.js b/app/menus/NewDocumentMenu.js index 550d01b39..a8100cb3f 100644 --- a/app/menus/NewDocumentMenu.js +++ b/app/menus/NewDocumentMenu.js @@ -3,11 +3,12 @@ import * as React from 'react'; import { observable } from 'mobx'; import { inject, observer } from 'mobx-react'; import { Redirect } from 'react-router-dom'; -import { MoreIcon, CollectionIcon, PrivateCollectionIcon } from 'outline-icons'; +import { PlusIcon, CollectionIcon, PrivateCollectionIcon } from 'outline-icons'; import { newDocumentUrl } from 'utils/routeHelpers'; import CollectionsStore from 'stores/CollectionsStore'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; +import Button from 'components/Button'; type Props = { label?: React.Node, @@ -41,7 +42,13 @@ class NewDocumentMenu extends React.Component { return ( } + label={ + label || ( + + ) + } onOpen={this.onOpen} {...rest} > diff --git a/app/scenes/Collection.js b/app/scenes/Collection.js index cfcc3f7c3..50761113b 100644 --- a/app/scenes/Collection.js +++ b/app/scenes/Collection.js @@ -9,6 +9,7 @@ import { CollectionIcon, PrivateCollectionIcon, NewDocumentIcon, + PlusIcon, PinIcon, } from 'outline-icons'; import RichMarkdownEditor from 'rich-markdown-editor'; @@ -103,13 +104,13 @@ class CollectionScene extends React.Component { return ( - + - - - + ); diff --git a/app/scenes/Dashboard.js b/app/scenes/Dashboard.js index ca5567eae..ea69a55b3 100644 --- a/app/scenes/Dashboard.js +++ b/app/scenes/Dashboard.js @@ -2,7 +2,6 @@ import * as React from 'react'; import { Switch, Route } from 'react-router-dom'; import { observer, inject } from 'mobx-react'; -import { NewDocumentIcon } from 'outline-icons'; import DocumentsStore from 'stores/DocumentsStore'; import AuthStore from 'stores/AuthStore'; @@ -67,7 +66,7 @@ class Dashboard extends React.Component { - } /> + diff --git a/app/scenes/Document/components/Header.js b/app/scenes/Document/components/Header.js index 7b7871d92..f8cc390bc 100644 --- a/app/scenes/Document/components/Header.js +++ b/app/scenes/Document/components/Header.js @@ -6,7 +6,7 @@ import { observer, inject } from 'mobx-react'; import { Redirect } from 'react-router-dom'; import styled from 'styled-components'; import breakpoint from 'styled-components-breakpoint'; -import { NewDocumentIcon } from 'outline-icons'; +import { EditIcon, PlusIcon } from 'outline-icons'; import { transparentize, darken } from 'polished'; import Document from 'models/Document'; import AuthStore from 'stores/AuthStore'; @@ -182,32 +182,42 @@ class Header extends React.Component { )} {canEdit && ( - )} - {!isEditing && ( - - - - )} {canEdit && !isDraft && ( - - - - } - /> - - + + } neutral> + New doc + + } + /> + )} + + {!isEditing && ( + + + + + + + )} ); diff --git a/app/scenes/Drafts.js b/app/scenes/Drafts.js index de0f7f32a..a25a978fa 100644 --- a/app/scenes/Drafts.js +++ b/app/scenes/Drafts.js @@ -1,7 +1,6 @@ // @flow import * as React from 'react'; import { observer, inject } from 'mobx-react'; -import { NewDocumentIcon } from 'outline-icons'; import Heading from 'components/Heading'; import CenteredContent from 'components/CenteredContent'; @@ -44,7 +43,7 @@ class Drafts extends React.Component { )} - } /> + diff --git a/app/scenes/Starred.js b/app/scenes/Starred.js index 7363b7d73..7f02e430d 100644 --- a/app/scenes/Starred.js +++ b/app/scenes/Starred.js @@ -1,7 +1,6 @@ // @flow import * as React from 'react'; import { observer, inject } from 'mobx-react'; -import { NewDocumentIcon } from 'outline-icons'; import CenteredContent from 'components/CenteredContent'; import { ListPlaceholder } from 'components/LoadingPlaceholder'; @@ -64,7 +63,7 @@ class Starred extends React.Component { {showLoading && } - } /> + diff --git a/app/scenes/UserProfile.js b/app/scenes/UserProfile.js index 5bb8a248b..2eaf32a44 100644 --- a/app/scenes/UserProfile.js +++ b/app/scenes/UserProfile.js @@ -4,6 +4,7 @@ import styled from 'styled-components'; import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'; import { inject, observer } from 'mobx-react'; import { Link } from 'react-router-dom'; +import { EditIcon } from 'outline-icons'; import Flex from 'shared/components/Flex'; import HelpText from 'components/HelpText'; import Modal from 'components/Modal'; @@ -50,7 +51,7 @@ class UserProfile extends React.Component { {user.isSuspended && Suspended} {isCurrentUser && ( - diff --git a/shared/styles/theme.js b/shared/styles/theme.js index eb6a61d80..ee22f0b1a 100644 --- a/shared/styles/theme.js +++ b/shared/styles/theme.js @@ -16,11 +16,12 @@ const colors = { white: '#FFF', white10: 'rgba(255, 255, 255, 0.1)', + white50: 'rgba(255, 255, 255, 0.5)', black: '#000', black05: 'rgba(0, 0, 0, 0.05)', black10: 'rgba(0, 0, 0, 0.1)', black50: 'rgba(0, 0, 0, 0.50)', - primary: '#1AB6FF', + primary: '#0366d6', yellow: '#FBCA04', warmGrey: '#EDF2F7', @@ -47,6 +48,9 @@ export const base = { fontWeight: 400, backgroundTransition: 'background 100ms ease-in-out', zIndex: 100, + + buttonBackground: colors.primary, + buttonText: colors.white, }; export const light = { @@ -80,8 +84,6 @@ export const light = { tableSelected: colors.primary, tableSelectedBackground: '#E5F7FF', - buttonBackground: colors.lightBlack, - buttonText: colors.white, buttonNeutralBackground: colors.white, buttonNeutralText: colors.almostBlack, @@ -131,8 +133,6 @@ export const dark = { tableSelected: colors.primary, tableSelectedBackground: '#002333', - buttonBackground: colors.white, - buttonText: colors.lightBlack, buttonNeutralBackground: colors.almostBlack, buttonNeutralText: colors.white,