) => ({
isCollectionDropping: monitor.isOver(),
- isDraggingAnotherCollection: monitor.canDrop(),
+ isDraggingAnyCollection: monitor.getItemType() === "collection",
}),
});
@@ -194,8 +201,7 @@ function CollectionLink({
collection.sort,
]);
- const isDraggingAnyCollection =
- isDraggingAnotherCollection || isCollectionDragging;
+ const displayDocumentLinks = expanded && !isCollectionDragging;
React.useEffect(() => {
// If we're viewing a starred document through the starred menu then don't
@@ -204,21 +210,14 @@ function CollectionLink({
return;
}
- if (isDraggingAnyCollection) {
- setExpanded(false);
- } else {
- setExpanded(collection.id === ui.activeCollectionId);
+ if (collection.id === ui.activeCollectionId) {
+ setExpanded(true);
}
- }, [isDraggingAnyCollection, collection.id, ui.activeCollectionId, search]);
+ }, [collection.id, ui.activeCollectionId, search]);
return (
<>
-
+
{
+ event.preventDefault();
+ setExpanded((prev) => !prev);
+ }}
icon={
-
+
}
showActions={menuOpen}
isActiveDrop={isOver && canDrop}
@@ -242,12 +249,13 @@ function CollectionLink({
/>
}
exact={false}
- depth={0.5}
+ depth={0}
menu={
- !isEditing && (
+ !isEditing &&
+ !isDraggingAnyCollection && (
<>
- {can.update && (
-
- {expanded && manualSort && (
-
+
+
+ {openedOnce && (
+
+ {manualSort && (
+
+ )}
+ {collectionDocuments.map((node, index) => (
+
+ ))}
+
)}
{isDraggingAnyCollection && (
)}
-
- {expanded &&
- collectionDocuments.map((node, index) => (
-
- ))}
+
+
`
+ display: ${(props) => (props.$open ? "block" : "none")};
+`;
+
const Draggable = styled("div")<{ $isDragging: boolean; $isMoving: boolean }>`
opacity: ${(props) => (props.$isDragging || props.$isMoving ? 0.5 : 1)};
pointer-events: ${(props) => (props.$isMoving ? "none" : "auto")};
`;
-const CollectionSortMenuWithMargin = styled(CollectionSortMenu)`
- margin-right: 4px;
-`;
-
export default observer(CollectionLink);
diff --git a/app/components/Sidebar/components/Collections.tsx b/app/components/Sidebar/components/Collections.tsx
index b9cc9e937..270d61f3c 100644
--- a/app/components/Sidebar/components/Collections.tsx
+++ b/app/components/Sidebar/components/Collections.tsx
@@ -1,6 +1,5 @@
import fractionalIndex from "fractional-index";
import { observer } from "mobx-react";
-import { CollapsedIcon } from "outline-icons";
import * as React from "react";
import { useDrop } from "react-dnd";
import { useTranslation } from "react-i18next";
@@ -13,9 +12,10 @@ import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import CollectionLink from "./CollectionLink";
import DropCursor from "./DropCursor";
+import Header from "./Header";
import PlaceholderCollections from "./PlaceholderCollections";
import SidebarAction from "./SidebarAction";
-import SidebarLink, { DragObject } from "./SidebarLink";
+import { DragObject } from "./SidebarLink";
function Collections() {
const [isFetching, setFetching] = React.useState(false);
@@ -52,7 +52,10 @@ function Collections() {
load();
}, [collections, isFetching, showToast, fetchError, t]);
- const [{ isCollectionDropping }, dropToReorderCollection] = useDrop({
+ const [
+ { isCollectionDropping, isDraggingAnyCollection },
+ dropToReorderCollection,
+ ] = useDrop({
accept: "collection",
drop: async (item: DragObject) => {
collections.move(
@@ -65,16 +68,19 @@ function Collections() {
},
collect: (monitor) => ({
isCollectionDropping: monitor.isOver(),
+ isDraggingAnyCollection: monitor.getItemType() === "collection",
}),
});
const content = (
<>
-
+ {isDraggingAnyCollection && (
+
+ )}
{orderedCollections.map((collection: Collection, index: number) => (
))}
-
+
>
);
if (!collections.isLoaded || fetchError) {
return (
- }
- />
+
);
@@ -103,19 +106,18 @@ function Collections() {
return (
- setExpanded((prev) => !prev)}
- label={t("Collections")}
- icon={}
- />
- {expanded && (isPreloaded ? content : {content})}
+ setExpanded((prev) => !prev)} expanded={expanded}>
+ {t("Collections")}
+
+ {expanded && (
+ {isPreloaded ? content : {content}}
+ )}
);
}
-const Disclosure = styled(CollapsedIcon)<{ expanded?: boolean }>`
- transition: transform 100ms ease, fill 50ms !important;
- ${({ expanded }) => !expanded && "transform: rotate(-90deg);"};
+const Relative = styled.div`
+ position: relative;
`;
export default observer(Collections);
diff --git a/app/components/Sidebar/components/Disclosure.ts b/app/components/Sidebar/components/Disclosure.ts
deleted file mode 100644
index 4c17623a1..000000000
--- a/app/components/Sidebar/components/Disclosure.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { CollapsedIcon } from "outline-icons";
-import styled from "styled-components";
-
-const Disclosure = styled(CollapsedIcon)<{ expanded?: boolean }>`
- transition: transform 100ms ease, fill 50ms !important;
- position: absolute;
- left: -24px;
-
- ${({ expanded }) => !expanded && "transform: rotate(-90deg);"};
-`;
-
-export default Disclosure;
diff --git a/app/components/Sidebar/components/Disclosure.tsx b/app/components/Sidebar/components/Disclosure.tsx
new file mode 100644
index 000000000..c57ec5571
--- /dev/null
+++ b/app/components/Sidebar/components/Disclosure.tsx
@@ -0,0 +1,54 @@
+import { CollapsedIcon } from "outline-icons";
+import * as React from "react";
+import styled, { css } from "styled-components";
+import NudeButton from "~/components/NudeButton";
+
+type Props = {
+ onClick?: React.MouseEventHandler;
+ expanded: boolean;
+ root?: boolean;
+};
+
+function Disclosure({ onClick, root, expanded, ...rest }: Props) {
+ return (
+
+ );
+}
+
+const Button = styled(NudeButton)<{ $root?: boolean }>`
+ position: absolute;
+ left: -24px;
+ flex-shrink: 0;
+ color: ${(props) => props.theme.textSecondary};
+
+ &:hover {
+ color: ${(props) => props.theme.text};
+ background: ${(props) => props.theme.sidebarControlHoverBackground};
+ }
+
+ ${(props) =>
+ props.$root &&
+ css`
+ opacity: 0;
+ left: -16px;
+
+ &:hover {
+ opacity: 1;
+ background: none;
+ }
+ `}
+`;
+
+const StyledCollapsedIcon = styled(CollapsedIcon)<{
+ expanded?: boolean;
+}>`
+ transition: opacity 100ms ease, transform 100ms ease, fill 50ms !important;
+ ${(props) => !props.expanded && "transform: rotate(-90deg);"};
+`;
+
+// Enables identifying this component within styled components
+const StyledDisclosure = styled(Disclosure)``;
+
+export default StyledDisclosure;
diff --git a/app/components/Sidebar/components/DocumentLink.tsx b/app/components/Sidebar/components/DocumentLink.tsx
index 63aa5677c..2d4285428 100644
--- a/app/components/Sidebar/components/DocumentLink.tsx
+++ b/app/components/Sidebar/components/DocumentLink.tsx
@@ -16,7 +16,6 @@ import useStores from "~/hooks/useStores";
import DocumentMenu from "~/menus/DocumentMenu";
import { NavigationNode } from "~/types";
import { newDocumentPath } from "~/utils/routeHelpers";
-import Disclosure from "./Disclosure";
import DropCursor from "./DropCursor";
import DropToImport from "./DropToImport";
import EditableTitle from "./EditableTitle";
@@ -81,7 +80,9 @@ function DocumentLink(
isActiveDocument)
);
}, [hasChildDocuments, activeDocument, isActiveDocument, node, collection]);
+
const [expanded, setExpanded] = React.useState(showChildren);
+ const [openedOnce, setOpenedOnce] = React.useState(expanded);
React.useEffect(() => {
if (showChildren) {
@@ -89,6 +90,12 @@ function DocumentLink(
}
}, [showChildren]);
+ React.useEffect(() => {
+ if (expanded) {
+ setOpenedOnce(true);
+ }
+ }, [expanded]);
+
// when the last child document is removed,
// also close the local folder state to closed
React.useEffect(() => {
@@ -98,7 +105,7 @@ function DocumentLink(
}, [expanded, hasChildDocuments]);
const handleDisclosureClick = React.useCallback(
- (ev: React.SyntheticEvent) => {
+ (ev) => {
ev.preventDefault();
ev.stopPropagation();
setExpanded(!expanded);
@@ -270,6 +277,8 @@ function DocumentLink(
t("Untitled");
const can = policies.abilities(node.id);
+ const isExpanded = expanded && !isDragging;
+ const hasChildren = nodeChildren.length > 0;
return (
<>
@@ -283,6 +292,8 @@ function DocumentLink(
- {hasChildDocuments && (
-
- )}
-
- >
+
}
isActive={(match, location) =>
!!match && location.search !== "?starred"
@@ -351,26 +354,32 @@ function DocumentLink(
)}
- {expanded &&
- !isDragging &&
- nodeChildren.map((childNode, index) => (
-
- ))}
+ {openedOnce && (
+
+ {nodeChildren.map((childNode, index) => (
+
+ ))}
+
+ )}
>
);
}
+const Folder = styled.div<{ $open?: boolean }>`
+ display: ${(props) => (props.$open ? "block" : "none")};
+`;
+
const Relative = styled.div`
position: relative;
`;
diff --git a/app/components/Sidebar/components/DropCursor.tsx b/app/components/Sidebar/components/DropCursor.tsx
index b7410422e..c4b4bd443 100644
--- a/app/components/Sidebar/components/DropCursor.tsx
+++ b/app/components/Sidebar/components/DropCursor.tsx
@@ -23,7 +23,7 @@ const Cursor = styled.div<{ isOver?: boolean; position?: "top" }>`
width: 100%;
height: 14px;
background: transparent;
- ${(props) => (props.position === "top" ? "top: 25px;" : "bottom: -7px;")}
+ ${(props) => (props.position === "top" ? "top: -7px;" : "bottom: -7px;")}
::after {
background: ${(props) => props.theme.slateDark};
diff --git a/app/components/Sidebar/components/Header.ts b/app/components/Sidebar/components/Header.ts
deleted file mode 100644
index 77b0b1f7c..000000000
--- a/app/components/Sidebar/components/Header.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import styled from "styled-components";
-import Flex from "~/components/Flex";
-
-const Header = styled(Flex)`
- font-size: 11px;
- font-weight: 600;
- user-select: none;
- text-transform: uppercase;
- color: ${(props) => props.theme.sidebarText};
- letter-spacing: 0.04em;
- margin: 4px 12px;
-`;
-
-export default Header;
diff --git a/app/components/Sidebar/components/Header.tsx b/app/components/Sidebar/components/Header.tsx
new file mode 100644
index 000000000..32f361610
--- /dev/null
+++ b/app/components/Sidebar/components/Header.tsx
@@ -0,0 +1,64 @@
+import { CollapsedIcon } from "outline-icons";
+import * as React from "react";
+import styled from "styled-components";
+
+type Props = {
+ onClick?: React.MouseEventHandler;
+ expanded?: boolean;
+ children: React.ReactNode;
+};
+
+export function Header({ onClick, expanded, children }: Props) {
+ return (
+
+
+
+ );
+}
+
+const Button = styled.button`
+ display: inline-flex;
+ align-items: center;
+ font-size: 13px;
+ font-weight: 600;
+ user-select: none;
+ color: ${(props) => props.theme.textTertiary};
+ letter-spacing: 0.03em;
+ margin: 0;
+ padding: 4px 2px 4px 12px;
+ height: 22px;
+ border: 0;
+ background: none;
+ border-radius: 4px;
+ -webkit-appearance: none;
+ transition: all 100ms ease;
+
+ &:not(:disabled):hover,
+ &:not(:disabled):active {
+ color: ${(props) => props.theme.textSecondary};
+ cursor: pointer;
+ }
+`;
+
+const Disclosure = styled(CollapsedIcon)<{ expanded?: boolean }>`
+ transition: opacity 100ms ease, transform 100ms ease, fill 50ms !important;
+ ${({ expanded }) => !expanded && "transform: rotate(-90deg);"};
+ opacity: 0;
+`;
+
+const H3 = styled.h3`
+ margin: 0;
+
+ &:hover {
+ ${Disclosure} {
+ opacity: 1;
+ }
+ }
+`;
+
+export default Header;
diff --git a/app/components/Sidebar/components/SidebarButton.tsx b/app/components/Sidebar/components/SidebarButton.tsx
new file mode 100644
index 000000000..1307ea63e
--- /dev/null
+++ b/app/components/Sidebar/components/SidebarButton.tsx
@@ -0,0 +1,81 @@
+import { ExpandedIcon, MoreIcon } from "outline-icons";
+import * as React from "react";
+import styled from "styled-components";
+import Flex from "~/components/Flex";
+
+type Props = {
+ title: React.ReactNode;
+ image: React.ReactNode;
+ minHeight?: number;
+ rounded?: boolean;
+ showDisclosure?: boolean;
+ showMoreMenu?: boolean;
+ onClick: React.MouseEventHandler;
+};
+
+const SidebarButton = React.forwardRef(
+ (
+ {
+ showDisclosure,
+ showMoreMenu,
+ image,
+ title,
+ minHeight = 0,
+ ...rest
+ }: Props,
+ ref
+ ) => (
+
+
+ {image}
+ {title}
+
+ {showDisclosure && }
+ {showMoreMenu && }
+
+ )
+);
+
+const Title = styled(Flex)`
+ color: ${(props) => props.theme.text};
+ flex-shrink: 1;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+`;
+
+const Wrapper = styled(Flex)<{ minHeight: number }>`
+ padding: 8px 4px;
+ font-size: 15px;
+ font-weight: 500;
+ border-radius: 4px;
+ margin: 8px;
+ color: ${(props) => props.theme.textTertiary};
+ border: 0;
+ background: none;
+ flex-shrink: 0;
+ min-height: ${(props) => props.minHeight}px;
+
+ -webkit-appearance: none;
+ text-decoration: none;
+ text-align: left;
+ overflow: hidden;
+ user-select: none;
+ cursor: pointer;
+
+ &:active,
+ &:hover {
+ color: ${(props) => props.theme.sidebarText};
+ transition: background 100ms ease-in-out;
+ background: ${(props) => props.theme.sidebarActiveBackground};
+ }
+`;
+
+export default SidebarButton;
diff --git a/app/components/Sidebar/components/SidebarLink.tsx b/app/components/Sidebar/components/SidebarLink.tsx
index 2d36a5be0..21b57ea19 100644
--- a/app/components/Sidebar/components/SidebarLink.tsx
+++ b/app/components/Sidebar/components/SidebarLink.tsx
@@ -1,10 +1,10 @@
-import { transparentize } from "polished";
import * as React from "react";
import styled, { useTheme, css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import EventBoundary from "~/components/EventBoundary";
import NudeButton from "~/components/NudeButton";
import { NavigationNode } from "~/types";
+import Disclosure from "./Disclosure";
import NavLink, { Props as NavLinkProps } from "./NavLink";
export type DragObject = NavigationNode & {
@@ -19,11 +19,14 @@ type Props = Omit & {
innerRef?: (arg0: HTMLElement | null | undefined) => void;
onClick?: React.MouseEventHandler;
onMouseEnter?: React.MouseEventHandler;
+ onDisclosureClick?: React.MouseEventHandler;
icon?: React.ReactNode;
label?: React.ReactNode;
menu?: React.ReactNode;
showActions?: boolean;
active?: boolean;
+ /* If set, a disclosure will be rendered to the left of any icon */
+ expanded?: boolean;
isActiveDrop?: boolean;
isDraft?: boolean;
depth?: number;
@@ -50,6 +53,8 @@ function SidebarLink(
href,
depth,
className,
+ expanded,
+ onDisclosureClick,
...rest
}: Props,
ref: React.RefObject
@@ -66,10 +71,10 @@ function SidebarLink(
() => ({
fontWeight: 600,
color: theme.text,
- background: theme.sidebarItemBackground,
+ background: theme.sidebarActiveBackground,
...style,
}),
- [theme, style]
+ [theme.text, theme.sidebarActiveBackground, style]
);
return (
@@ -90,14 +95,34 @@ function SidebarLink(
ref={ref}
{...rest}
>
- {icon && {icon}}
-
+
+ {expanded !== undefined && (
+
+ )}
+ {icon && {icon}}
+
+
{menu && {menu}}
>
);
}
+const Content = styled.span`
+ display: flex;
+ align-items: start;
+ position: relative;
+ width: 100%;
+
+ ${Disclosure} {
+ margin-top: 2px;
+ }
+`;
+
// accounts for whitespace around icon
export const IconWrapper = styled.span`
margin-left: -4px;
@@ -112,6 +137,7 @@ const Actions = styled(EventBoundary)<{ showActions?: boolean }>`
position: absolute;
top: 4px;
right: 4px;
+ gap: 4px;
color: ${(props) => props.theme.textTertiary};
transition: opacity 50ms;
@@ -158,10 +184,8 @@ const Link = styled(NavLink)<{ $isActiveDrop?: boolean; $isDraft?: boolean }>`
transition: fill 50ms;
}
- &:focus {
- color: ${(props) => props.theme.text};
- background: ${(props) =>
- transparentize("0.25", props.theme.sidebarItemBackground)};
+ &:hover svg {
+ display: inline;
}
& + ${Actions} {
@@ -170,16 +194,9 @@ const Link = styled(NavLink)<{ $isActiveDrop?: boolean; $isDraft?: boolean }>`
}
}
- &:focus + ${Actions} {
- ${NudeButton} {
- background: ${(props) =>
- transparentize("0.25", props.theme.sidebarItemBackground)};
- }
- }
-
&[aria-current="page"] + ${Actions} {
${NudeButton} {
- background: ${(props) => props.theme.sidebarItemBackground};
+ background: ${(props) => props.theme.sidebarActiveBackground};
}
}
@@ -202,6 +219,12 @@ const Link = styled(NavLink)<{ $isActiveDrop?: boolean; $isDraft?: boolean }>`
props.$isActiveDrop ? props.theme.white : props.theme.text};
}
}
+
+ &:hover {
+ ${Disclosure} {
+ opacity: 1;
+ }
+ }
`;
const Label = styled.div`
@@ -209,6 +232,7 @@ const Label = styled.div`
width: 100%;
max-height: 4.8em;
line-height: 1.6;
+
* {
unicode-bidi: plaintext;
}
diff --git a/app/components/Sidebar/components/Starred.tsx b/app/components/Sidebar/components/Starred.tsx
index 96b66a41a..10cbbaa5e 100644
--- a/app/components/Sidebar/components/Starred.tsx
+++ b/app/components/Sidebar/components/Starred.tsx
@@ -1,6 +1,5 @@
import fractionalIndex from "fractional-index";
import { observer } from "mobx-react";
-import { CollapsedIcon } from "outline-icons";
import * as React from "react";
import { useDrop } from "react-dnd";
import { useTranslation } from "react-i18next";
@@ -10,8 +9,8 @@ import Flex from "~/components/Flex";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import DropCursor from "./DropCursor";
+import Header from "./Header";
import PlaceholderCollections from "./PlaceholderCollections";
-import Section from "./Section";
import SidebarLink from "./SidebarLink";
import StarredLink from "./StarredLink";
@@ -119,71 +118,64 @@ function Starred() {
}),
});
- const content = stars.orderedData.slice(0, upperBound).map((star) => {
- const document = documents.get(star.documentId);
-
- return document ? (
-
- ) : null;
- });
-
if (!stars.orderedData.length) {
return null;
}
return (
-
-
- }
- />
- {expanded && (
- <>
-
+
+ {expanded && (
+
+
+ {stars.orderedData.slice(0, upperBound).map((star) => {
+ const document = documents.get(star.documentId);
+
+ return document ? (
+
+ ) : null;
+ })}
+ {show === "More" && !isFetching && (
+
- {content}
- {show === "More" && !isFetching && (
-
- )}
- {show === "Less" && !isFetching && (
-
- )}
- {(isFetching || fetchError) && !stars.orderedData.length && (
-
-
-
- )}
- >
- )}
-
-
+ )}
+ {show === "Less" && !isFetching && (
+
+ )}
+ {(isFetching || fetchError) && !stars.orderedData.length && (
+
+
+
+ )}
+
+ )}
+
);
}
-const Disclosure = styled(CollapsedIcon)<{ expanded?: boolean }>`
- transition: transform 100ms ease, fill 50ms !important;
- ${({ expanded }) => !expanded && "transform: rotate(-90deg);"};
+const Relative = styled.div`
+ position: relative;
`;
export default observer(Starred);
diff --git a/app/components/Sidebar/components/StarredLink.tsx b/app/components/Sidebar/components/StarredLink.tsx
index 8ce6bb9bb..ebc022705 100644
--- a/app/components/Sidebar/components/StarredLink.tsx
+++ b/app/components/Sidebar/components/StarredLink.tsx
@@ -1,19 +1,18 @@
import fractionalIndex from "fractional-index";
import { observer } from "mobx-react";
+import { StarredIcon } from "outline-icons";
import * as React from "react";
import { useEffect, useState } from "react";
import { useDrag, useDrop } from "react-dnd";
-import { useTranslation } from "react-i18next";
-import styled from "styled-components";
-import { MAX_TITLE_LENGTH } from "@shared/constants";
+import styled, { useTheme } from "styled-components";
+import parseTitle from "@shared/utils/parseTitle";
import Star from "~/models/Star";
+import EmojiIcon from "~/components/EmojiIcon";
import Fade from "~/components/Fade";
import useBoolean from "~/hooks/useBoolean";
import useStores from "~/hooks/useStores";
import DocumentMenu from "~/menus/DocumentMenu";
-import Disclosure from "./Disclosure";
import DropCursor from "./DropCursor";
-import EditableTitle from "./EditableTitle";
import SidebarLink from "./SidebarLink";
type Props = {
@@ -27,24 +26,22 @@ type Props = {
function StarredLink({
depth,
- title,
to,
documentId,
+ title,
collectionId,
star,
}: Props) {
- const { t } = useTranslation();
- const { collections, documents, policies } = useStores();
+ const theme = useTheme();
+ const { collections, documents } = useStores();
const collection = collections.get(collectionId);
const document = documents.get(documentId);
const [expanded, setExpanded] = useState(false);
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
- const canUpdate = policies.abilities(documentId).update;
const childDocuments = collection
? collection.getDocumentChildren(documentId)
: [];
const hasChildDocuments = childDocuments.length > 0;
- const [isEditing, setIsEditing] = React.useState(false);
useEffect(() => {
async function load() {
@@ -57,7 +54,7 @@ function StarredLink({
}, [collection, collectionId, collections, document, documentId, documents]);
const handleDisclosureClick = React.useCallback(
- (ev: React.MouseEvent) => {
+ (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
setExpanded((prevExpanded) => !prevExpanded);
@@ -65,29 +62,6 @@ function StarredLink({
[]
);
- const handleTitleChange = React.useCallback(
- async (title: string) => {
- if (!document) {
- return;
- }
- await documents.update(
- {
- id: document.id,
- text: document.text,
- title,
- },
- {
- lastRevision: document.revision,
- }
- );
- },
- [documents, document]
- );
-
- const handleTitleEditing = React.useCallback((isEditing: boolean) => {
- setIsEditing(isEditing);
- }, []);
-
// Draggable
const [{ isDragging }, drag] = useDrag({
type: "star",
@@ -96,7 +70,7 @@ function StarredLink({
isDragging: !!monitor.isDragging(),
}),
canDrag: () => {
- return depth === 2;
+ return depth === 0;
},
});
@@ -116,36 +90,34 @@ function StarredLink({
}),
});
+ const { emoji } = parseTitle(title);
+ const label = emoji ? title.replace(emoji, "") : title;
+
return (
<>
+ ) : (
+
+ )
+ ) : undefined
+ }
isActive={(match, location) =>
!!match && location.search === "?starred"
}
- label={
- <>
- {hasChildDocuments && (
-
- )}
-
- >
- }
+ label={depth === 0 ? label : title}
exact={false}
showActions={menuOpen}
menu={
- document && !isEditing ? (
+ document ? (
(
;
- logoUrl: string;
-};
-
-const TeamButton = React.forwardRef(
- ({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => (
-
-
-
-
-
- {teamName} {showDisclosure && }
-
- {subheading}
-
-
-
- )
-);
-
-const Disclosure = styled(ExpandedIcon)`
- position: absolute;
- right: 0;
- top: 0;
-`;
-
-const Subheading = styled.div`
- padding-left: 10px;
- font-size: 11px;
- text-transform: uppercase;
- font-weight: 500;
- white-space: nowrap;
- color: ${(props) => props.theme.sidebarText};
-`;
-
-const TeamName = styled.div`
- position: relative;
- padding-left: 10px;
- padding-right: 24px;
- font-weight: 600;
- color: ${(props) => props.theme.text};
- white-space: nowrap;
- text-decoration: none;
- font-size: 16px;
-
- text-align: left;
- text-overflow: ellipsis;
- overflow: hidden;
- width: 100%;
-`;
-
-const Wrapper = styled.div`
- flex-shrink: 0;
- overflow: hidden;
-`;
-
-const Header = styled.button`
- display: flex;
- align-items: center;
- background: none;
- line-height: inherit;
- border: 0;
- padding: 8px;
- margin: 8px;
- border-radius: 4px;
- cursor: pointer;
- width: calc(100% - 16px);
-
- &:active,
- &:hover {
- transition: background 100ms ease-in-out;
- background: ${(props) => props.theme.sidebarItemBackground};
- }
-`;
-
-export default observer(TeamButton);
diff --git a/app/components/Sidebar/index.ts b/app/components/Sidebar/index.ts
index 3d6822466..cb972c423 100644
--- a/app/components/Sidebar/index.ts
+++ b/app/components/Sidebar/index.ts
@@ -1,3 +1,3 @@
-import Sidebar from "./Main";
+import Sidebar from "./App";
export default Sidebar;
diff --git a/app/components/TeamLogo.ts b/app/components/TeamLogo.ts
index 2eb5d1442..6d0efc0c3 100644
--- a/app/components/TeamLogo.ts
+++ b/app/components/TeamLogo.ts
@@ -6,7 +6,7 @@ const TeamLogo = styled.img<{ width?: number; height?: number; size?: string }>`
height: ${(props) =>
props.height ? `${props.height}px` : props.size || "38px"};
border-radius: 4px;
- background: ${(props) => props.theme.background};
+ background: white;
border: 1px solid ${(props) => props.theme.divider};
overflow: hidden;
flex-shrink: 0;
diff --git a/app/menus/AccountMenu.tsx b/app/menus/AccountMenu.tsx
index 4603fe23c..2c6eb30c9 100644
--- a/app/menus/AccountMenu.tsx
+++ b/app/menus/AccountMenu.tsx
@@ -2,13 +2,10 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { MenuButton, useMenuState } from "reakit/Menu";
-import styled from "styled-components";
import ContextMenu from "~/components/ContextMenu";
import Template from "~/components/ContextMenu/Template";
-import { createAction } from "~/actions";
-import { development } from "~/actions/definitions/debug";
import {
- navigateToSettings,
+ navigateToProfileSettings,
openKeyboardShortcuts,
openChangelog,
openAPIDocumentation,
@@ -17,9 +14,7 @@ import {
logout,
} from "~/actions/definitions/navigation";
import { changeTheme } from "~/actions/definitions/settings";
-import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePrevious from "~/hooks/usePrevious";
-import useSessions from "~/hooks/useSessions";
import useStores from "~/hooks/useStores";
import separator from "~/menus/separator";
@@ -28,15 +23,12 @@ type Props = {
};
function AccountMenu(props: Props) {
- const [sessions] = useSessions();
const menu = useMenuState({
- unstable_offset: [8, 0],
- placement: "bottom-start",
+ placement: "bottom-end",
modal: true,
});
const { ui } = useStores();
const { theme } = ui;
- const team = useCurrentTeam();
const previousTheme = usePrevious(theme);
const { t } = useTranslation();
@@ -47,39 +39,19 @@ function AccountMenu(props: Props) {
}, [menu, theme, previousTheme]);
const actions = React.useMemo(() => {
- const otherSessions = sessions.filter(
- (session) => session.teamId !== team.id && session.url !== team.url
- );
-
return [
- navigateToSettings,
openKeyboardShortcuts,
openAPIDocumentation,
separator(),
openChangelog,
openFeedbackUrl,
openBugReportUrl,
- development,
changeTheme,
+ navigateToProfileSettings,
separator(),
- ...(otherSessions.length
- ? [
- createAction({
- name: t("Switch team"),
- section: "account",
- children: otherSessions.map((session) => ({
- id: session.url,
- name: session.name,
- section: "account",
- icon: ,
- perform: () => (window.location.href = session.url),
- })),
- }),
- ]
- : []),
logout,
];
- }, [team.id, team.url, sessions, t]);
+ }, []);
return (
<>
@@ -91,10 +63,4 @@ function AccountMenu(props: Props) {
);
}
-const Logo = styled("img")`
- border-radius: 2px;
- width: 24px;
- height: 24px;
-`;
-
export default observer(AccountMenu);
diff --git a/app/menus/OrganizationMenu.tsx b/app/menus/OrganizationMenu.tsx
new file mode 100644
index 000000000..2b2031b6d
--- /dev/null
+++ b/app/menus/OrganizationMenu.tsx
@@ -0,0 +1,82 @@
+import { observer } from "mobx-react";
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import { MenuButton, useMenuState } from "reakit/Menu";
+import styled from "styled-components";
+import ContextMenu from "~/components/ContextMenu";
+import Template from "~/components/ContextMenu/Template";
+import { createAction } from "~/actions";
+import { navigateToSettings, logout } from "~/actions/definitions/navigation";
+import useCurrentTeam from "~/hooks/useCurrentTeam";
+import usePrevious from "~/hooks/usePrevious";
+import useSessions from "~/hooks/useSessions";
+import useStores from "~/hooks/useStores";
+import separator from "~/menus/separator";
+
+type Props = {
+ children: (props: any) => React.ReactNode;
+};
+
+function OrganizationMenu(props: Props) {
+ const [sessions] = useSessions();
+ const menu = useMenuState({
+ unstable_offset: [4, -4],
+ placement: "bottom-start",
+ modal: true,
+ });
+ const { ui } = useStores();
+ const { theme } = ui;
+ const team = useCurrentTeam();
+ const previousTheme = usePrevious(theme);
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ if (theme !== previousTheme) {
+ menu.hide();
+ }
+ }, [menu, theme, previousTheme]);
+
+ const actions = React.useMemo(() => {
+ const otherSessions = sessions.filter(
+ (session) => session.teamId !== team.id && session.url !== team.url
+ );
+
+ return [
+ navigateToSettings,
+ separator(),
+ ...(otherSessions.length
+ ? [
+ createAction({
+ name: t("Switch team"),
+ section: "account",
+ children: otherSessions.map((session) => ({
+ id: session.url,
+ name: session.name,
+ section: "account",
+ icon: ,
+ perform: () => (window.location.href = session.url),
+ })),
+ }),
+ ]
+ : []),
+ logout,
+ ];
+ }, [team.id, team.url, sessions, t]);
+
+ return (
+ <>
+ {props.children}
+
+
+
+ >
+ );
+}
+
+const Logo = styled("img")`
+ border-radius: 2px;
+ width: 24px;
+ height: 24px;
+`;
+
+export default observer(OrganizationMenu);
diff --git a/app/typings/styled-components.d.ts b/app/typings/styled-components.d.ts
index c04a74413..51fe0effe 100644
--- a/app/typings/styled-components.d.ts
+++ b/app/typings/styled-components.d.ts
@@ -150,7 +150,8 @@ declare module "styled-components" {
textTertiary: string;
placeholder: string;
sidebarBackground: string;
- sidebarItemBackground: string;
+ sidebarActiveBackground: string;
+ sidebarControlHoverBackground: string;
sidebarDraftBorder: string;
sidebarText: string;
backdrop: string;
diff --git a/app/utils/routeHelpers.ts b/app/utils/routeHelpers.ts
index 3b5b779f2..e0daa3926 100644
--- a/app/utils/routeHelpers.ts
+++ b/app/utils/routeHelpers.ts
@@ -14,10 +14,6 @@ export function templatesPath(): string {
return "/templates";
}
-export function settingsPath(): string {
- return "/settings";
-}
-
export function archivePath(): string {
return "/archive";
}
@@ -26,6 +22,18 @@ export function trashPath(): string {
return "/trash";
}
+export function settingsPath(): string {
+ return "/settings";
+}
+
+export function organizationSettingsPath(): string {
+ return "/settings/details";
+}
+
+export function profileSettingsPath(): string {
+ return "/settings/profile";
+}
+
export function groupSettingsPath(): string {
return "/settings/groups";
}
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index c08836c85..ff820c50b 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -31,6 +31,7 @@
"Archive": "Archive",
"Trash": "Trash",
"Settings": "Settings",
+ "Profile": "Profile",
"API documentation": "API documentation",
"Send us feedback": "Send us feedback",
"Report a bug": "Report a bug",
@@ -149,7 +150,6 @@
"Delete {{ documentName }}": "Delete {{ documentName }}",
"Return to App": "Back to App",
"Account": "Account",
- "Profile": "Profile",
"Notifications": "Notifications",
"API Tokens": "API Tokens",
"Team": "Team",
@@ -227,7 +227,6 @@
"Warning": "Warning",
"Warning notice": "Warning notice",
"Could not import file": "Could not import file",
- "Switch team": "Switch team",
"Show path to document": "Show path to document",
"Path to document": "Path to document",
"Group member options": "Group member options",
@@ -264,6 +263,7 @@
"New child document": "New child document",
"New document in {{ collectionName }}": "New document in {{ collectionName }}",
"New template": "New template",
+ "Switch team": "Switch team",
"Link copied": "Link copied",
"Revision options": "Revision options",
"Restore version": "Restore version",
diff --git a/shared/theme.ts b/shared/theme.ts
index f1c029852..3cea1be5d 100644
--- a/shared/theme.ts
+++ b/shared/theme.ts
@@ -134,7 +134,8 @@ export const light = {
textTertiary: colors.slate,
placeholder: "#a2b2c3",
sidebarBackground: colors.warmGrey,
- sidebarItemBackground: "#d7e0ea",
+ sidebarActiveBackground: "#d7e0ea",
+ sidebarControlHoverBackground: "rgba(0,0,0,0.1)",
sidebarDraftBorder: darken("0.25", colors.warmGrey),
sidebarText: "rgb(78, 92, 110)",
backdrop: "rgba(0, 0, 0, 0.2)",
@@ -184,7 +185,8 @@ export const dark = {
textTertiary: colors.slate,
placeholder: colors.slateDark,
sidebarBackground: colors.veryDarkBlue,
- sidebarItemBackground: lighten(0.015, colors.almostBlack),
+ sidebarActiveBackground: lighten(0.02, colors.almostBlack),
+ sidebarControlHoverBackground: "rgba(255,255,255,0.1)",
sidebarDraftBorder: darken("0.35", colors.slate),
sidebarText: colors.slate,
backdrop: "rgba(255, 255, 255, 0.3)",