diff --git a/app/actions/definitions/collections.tsx b/app/actions/definitions/collections.tsx
index 387f31569e..07bc64406b 100644
--- a/app/actions/definitions/collections.tsx
+++ b/app/actions/definitions/collections.tsx
@@ -163,7 +163,7 @@ export const importDocument = createActionV2({
const { documents } = stores;
const input = document.createElement("input");
input.type = "file";
- input.accept = documents.importFileTypes.join(", ");
+ input.accept = documents.importFileTypesString;
input.onchange = async (ev) => {
const files = getEventFiles(ev);
diff --git a/app/actions/definitions/documents.tsx b/app/actions/definitions/documents.tsx
index 6292dd9986..924e9654b9 100644
--- a/app/actions/definitions/documents.tsx
+++ b/app/actions/definitions/documents.tsx
@@ -870,7 +870,7 @@ export const importDocument = createActionV2({
const { documents } = stores;
const input = document.createElement("input");
input.type = "file";
- input.accept = documents.importFileTypes.join(", ");
+ input.accept = documents.importFileTypesString;
input.onchange = async (ev) => {
const files = getEventFiles(ev);
diff --git a/app/components/DocumentBreadcrumb.tsx b/app/components/DocumentBreadcrumb.tsx
index dcd3360869..bdc9077dfa 100644
--- a/app/components/DocumentBreadcrumb.tsx
+++ b/app/components/DocumentBreadcrumb.tsx
@@ -99,7 +99,12 @@ function DocumentBreadcrumb(
return createInternalLinkActionV2({
name: node.icon ? (
<>
- {title}
+ {" "}
+ {title}
>
) : (
title
diff --git a/app/components/EditableTitle.tsx b/app/components/EditableTitle.tsx
index 9357e57fd4..b6f39c8e22 100644
--- a/app/components/EditableTitle.tsx
+++ b/app/components/EditableTitle.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import { toast } from "sonner";
import styled from "styled-components";
-import { s, truncateMultiline } from "@shared/styles";
+import { s, ellipsis } from "@shared/styles";
type Props = Omit, "onSubmit"> & {
/** A callback when the title is submitted. */
@@ -168,8 +168,8 @@ function EditableTitle(
);
}
-const Text = styled.span`
- ${truncateMultiline(3)}
+const Text = styled.div`
+ ${ellipsis()}
`;
const Input = styled.input`
diff --git a/app/components/Menu/ContextMenu.tsx b/app/components/Menu/ContextMenu.tsx
index ca6d680a7c..5155f308d0 100644
--- a/app/components/Menu/ContextMenu.tsx
+++ b/app/components/Menu/ContextMenu.tsx
@@ -30,15 +30,13 @@ export const ContextMenu = observer(
isMenu: true,
});
- const menuItems = useComputed(() => {
- if (!open) {
- return [];
- }
-
- return ((action?.children as ActionV2Variant[]) ?? []).map(
- (childAction) => actionV2ToMenuItem(childAction, actionContext)
- );
- }, [open, action?.children, actionContext]);
+ const menuItems = useComputed(
+ () =>
+ ((action?.children as ActionV2Variant[]) ?? []).map((childAction) =>
+ actionV2ToMenuItem(childAction, actionContext)
+ ),
+ [action?.children, actionContext]
+ );
const handleOpenChange = React.useCallback(
(open: boolean) => {
@@ -48,7 +46,7 @@ export const ContextMenu = observer(
onClose?.();
}
},
- [onOpen, onClose]
+ [open, onOpen, onClose]
);
const enablePointerEvents = React.useCallback(() => {
diff --git a/app/components/Sidebar/components/CollectionLink.tsx b/app/components/Sidebar/components/CollectionLink.tsx
index 97546a1fa9..6d5894e463 100644
--- a/app/components/Sidebar/components/CollectionLink.tsx
+++ b/app/components/Sidebar/components/CollectionLink.tsx
@@ -32,7 +32,7 @@ import CollectionLinkChildren from "./CollectionLinkChildren";
type Props = {
collection: Collection;
expanded?: boolean;
- onDisclosureClick: (ev?: React.MouseEvent) => void;
+ onDisclosureClick: (ev?: React.MouseEvent) => void;
activeDocument: Document | undefined;
isDraggingAnyCollection?: boolean;
depth?: number;
diff --git a/app/components/Sidebar/components/DocumentLink.tsx b/app/components/Sidebar/components/DocumentLink.tsx
index 2dd4b03de0..117091dd38 100644
--- a/app/components/Sidebar/components/DocumentLink.tsx
+++ b/app/components/Sidebar/components/DocumentLink.tsx
@@ -130,17 +130,13 @@ function InnerDocumentLink(
}
}, [setCollapsed, expanded, hasChildDocuments]);
- const handleDisclosureClick = React.useCallback(
- (ev) => {
- ev?.preventDefault();
- if (expanded) {
- setCollapsed();
- } else {
- setExpanded();
- }
- },
- [setCollapsed, setExpanded, expanded]
- );
+ const handleDisclosureClick = React.useCallback(() => {
+ if (expanded) {
+ setCollapsed();
+ } else {
+ setExpanded();
+ }
+ }, [setCollapsed, setExpanded, expanded]);
const handlePrefetch = React.useCallback(() => {
void prefetchDocument?.(node.id);
@@ -418,7 +414,7 @@ function InnerDocumentLink(
onKeyDown={handleKeyDown}
>
-
+
setPreRendered(true));
const canCollection = usePolicy(collectionId);
const canDocument = usePolicy(documentId);
@@ -42,6 +45,7 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
if (
disabled ||
+ !prerender ||
(collectionId && !canCollection.createDocument) ||
(documentId && !canDocument.createChildDocument)
) {
@@ -50,7 +54,7 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
return (
= ({ expanded, children }: Props) => {
- const [openedOnce, setOpenedOnce] = React.useState(expanded);
-
- // allows us to avoid rendering all children when the folder hasn't been opened
- React.useEffect(() => {
- if (expanded) {
- setOpenedOnce(true);
- }
- }, [expanded]);
-
- if (!openedOnce) {
+ if (!expanded) {
return null;
}
- return {children};
+ return <>{children}>;
};
-const Wrapper = styled.div<{ $expanded?: boolean }>`
- display: ${(props) => (props.$expanded ? "block" : "none")};
-`;
-
export default Folder;
diff --git a/app/components/Sidebar/components/SharedCollectionLink.tsx b/app/components/Sidebar/components/SharedCollectionLink.tsx
index 22b8cfdb47..9ceb01f89a 100644
--- a/app/components/Sidebar/components/SharedCollectionLink.tsx
+++ b/app/components/Sidebar/components/SharedCollectionLink.tsx
@@ -40,7 +40,7 @@ function CollectionLink({ node, shareId, hideRootNode }: Props) {
& {
to?: LocationDescriptor;
innerRef?: (ref: HTMLElement | null | undefined) => void;
onClick?: React.MouseEventHandler;
/** Callback when we expect the user to click on the link. Used for prefetching data. */
- onClickIntent?: () => void;
- onDisclosureClick?: React.MouseEventHandler;
+ onClickIntent?: React.MouseEventHandler;
+ onDisclosureClick?: React.MouseEventHandler;
icon?: React.ReactNode;
label?: React.ReactNode;
menu?: React.ReactNode;
@@ -45,6 +44,7 @@ const activeDropStyle = {
const preventDefault = (ev: React.MouseEvent) => {
ev.preventDefault();
+ ev.stopPropagation();
};
function SidebarLink(
@@ -77,10 +77,10 @@ function SidebarLink(
const { handleMouseEnter, handleMouseLeave } = useClickIntent(onClickIntent);
const style = React.useMemo(
() => ({
- paddingLeft: `${(depth || 0) * 16 + 12}px`,
+ paddingLeft: `${(depth || 0) * 16 + (icon ? -8 : 12)}px`,
paddingRight: unreadBadge ? "32px" : undefined,
}),
- [depth]
+ [depth, icon, unreadBadge]
);
const unreadStyle = React.useMemo(
@@ -99,81 +99,71 @@ function SidebarLink(
[theme.text, theme.sidebarActiveBackground, style]
);
- const hoverStyle = React.useMemo(
- () => ({
- color: theme.text,
- ...style,
- }),
- [theme.text, style]
- );
+ const handleClick = React.useCallback(
+ (ev: React.MouseEvent) => {
+ onDisclosureClick?.(ev);
- const [openContextMenu, setOpen, setClosed] = useBoolean(false);
- const DisclosureComponent = depth === 0 ? HiddenDisclosure : Disclosure;
-
- const handleClickCapture = React.useCallback(
- (event: React.MouseEvent) => {
- if (event.altKey && onDisclosureClick && expanded !== undefined) {
- event.preventDefault();
- event.stopPropagation();
- onDisclosureClick(
- event as unknown as React.MouseEvent
- );
+ if (onClick && !disabled && ev.isDefaultPrevented() === false) {
+ onClick(ev);
}
},
- [onDisclosureClick, expanded]
+ [onClick, disabled, expanded]
);
+ const handleDisclosureClick = React.useCallback(
+ (ev: React.MouseEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ onDisclosureClick?.(ev);
+ },
+ [onDisclosureClick]
+ );
+
+ const DisclosureComponent = icon ? HiddenDisclosure : Disclosure;
+
return (
- <>
-
+
-
-
- {expanded !== undefined && (
-
- )}
- {icon && {icon}}
-
- {unreadBadge && }
-
-
-
- {menu && {menu}}
- >
+
+ {expanded !== undefined && (
+
+ )}
+ {icon && {icon}}
+
+ {unreadBadge && }
+
+ {menu && {menu}}
+
+
);
}
// accounts for whitespace around icon
export const IconWrapper = styled.span`
margin-left: -4px;
- margin-right: 4px;
height: 24px;
overflow: hidden;
flex-shrink: 0;
@@ -191,12 +181,13 @@ const Actions = styled(EventBoundary)<{ showActions?: boolean }>`
display: inline-flex;
visibility: ${(props) => (props.showActions ? "visible" : "hidden")};
position: absolute;
- top: 4px;
+ top: 3px;
right: 4px;
gap: 4px;
color: ${s("textTertiary")};
transition: opacity 50ms;
height: 24px;
+ background: var(--background);
svg {
color: ${s("textSecondary")};
@@ -226,16 +217,28 @@ const Link = styled(NavLink)<{
$isDraft?: boolean;
$disabled?: boolean;
}>`
+ &:hover,
+ &:active {
+ --background: ${s("sidebarHoverBackground")};
+ }
+
+ &[aria-current="page"] ${Actions} {
+ --background: ${s("sidebarActiveBackground")};
+ }
+
+ ${(props) => props.$isActiveDrop && `--background: ${props.theme.slateDark};`}
+
display: flex;
position: relative;
text-overflow: ellipsis;
font-weight: 475;
padding: ${isMobile() ? 12 : 6}px 16px;
border-radius: 4px;
- min-height: 32px;
+ min-height: 30px;
user-select: none;
- background: ${(props) =>
- props.$isActiveDrop ? props.theme.slateDark : "inherit"};
+ white-space: nowrap;
+ margin-top: 1px;
+ background: var(--background);
color: ${(props) =>
props.$isActiveDrop ? props.theme.white : props.theme.sidebarText};
font-size: 16px;
@@ -282,30 +285,13 @@ const Link = styled(NavLink)<{
}
}
- & + ${Actions} {
- background: ${s("sidebarBackground")};
-
- ${NudeButton} {
- background: transparent;
-
- &:hover,
- &[aria-expanded="true"] {
- background: ${s("sidebarControlHoverBackground")};
- }
- }
- }
-
- &[aria-current="page"] + ${Actions} {
- background: ${s("sidebarActiveBackground")};
- }
-
${breakpoint("tablet")`
- padding: 4px 8px 4px 16px;
+ padding: 3px 8px 3px 12px;
font-size: 14px;
`}
@media (hover: hover) {
- &:hover + ${Actions}, &:active + ${Actions} {
+ &:hover ${Actions}, &:active ${Actions} {
visibility: visible;
svg {
@@ -318,12 +304,25 @@ const Link = styled(NavLink)<{
props.$isActiveDrop ? props.theme.white : props.theme.text};
}
}
+
+ & ${Actions} {
+ ${NudeButton} {
+ background: transparent;
+
+ &:hover,
+ &[aria-expanded="true"] {
+ background: ${s("sidebarControlHoverBackground")};
+ }
+ }
+ }
`;
-const Label = styled.div`
+const Label = styled.div<{ $ellipsis: boolean }>`
position: relative;
width: 100%;
line-height: 24px;
+ margin-left: 2px;
+ ${(props) => props.$ellipsis && ellipsis()}
* {
unicode-bidi: plaintext;
diff --git a/app/components/Sidebar/components/StarredLink.tsx b/app/components/Sidebar/components/StarredLink.tsx
index eca6379777..df5e18a67c 100644
--- a/app/components/Sidebar/components/StarredLink.tsx
+++ b/app/components/Sidebar/components/StarredLink.tsx
@@ -41,7 +41,7 @@ type StarredDocumentLinkProps = {
expanded: boolean;
sidebarContext: SidebarContextType;
isDragging: boolean;
- handleDisclosureClick: (ev?: React.MouseEvent) => void;
+ handleDisclosureClick: React.MouseEventHandler;
handlePrefetch: () => void;
icon: React.ReactNode;
label: React.ReactNode;
@@ -234,7 +234,7 @@ function StarredLink({ star }: Props) {
}, [documentId, documents]);
const handleDisclosureClick = React.useCallback(
- (ev?: React.MouseEvent) => {
+ (ev?: React.MouseEvent) => {
ev?.preventDefault();
ev?.stopPropagation();
setExpanded((prevExpanded) => !prevExpanded);
diff --git a/app/hooks/useClickIntent.ts b/app/hooks/useClickIntent.ts
index 71710d9f23..a01a833e6b 100644
--- a/app/hooks/useClickIntent.ts
+++ b/app/hooks/useClickIntent.ts
@@ -7,7 +7,7 @@ import useUnmount from "./useUnmount";
* and clears the timer on mouse leave or component unmount.
*/
export default function useClickIntent(
- onClickIntent?: () => void,
+ onClickIntent?: React.MouseEventHandler,
delay = 100
) {
const timer = React.useRef();
diff --git a/app/scenes/Collection/index.tsx b/app/scenes/Collection/index.tsx
index 64cab2847a..5428b50c97 100644
--- a/app/scenes/Collection/index.tsx
+++ b/app/scenes/Collection/index.tsx
@@ -197,7 +197,7 @@ const CollectionScene = observer(function _CollectionScene() {
}
>
diff --git a/app/stores/DocumentsStore.ts b/app/stores/DocumentsStore.ts
index 30d19abd61..9560eefe17 100644
--- a/app/stores/DocumentsStore.ts
+++ b/app/stores/DocumentsStore.ts
@@ -69,6 +69,11 @@ export default class DocumentsStore extends Store {
super(rootStore, Document);
}
+ @computed
+ get importFileTypesString(): string {
+ return this.importFileTypes.join(",");
+ }
+
@computed
get all(): Document[] {
return filter(
diff --git a/app/typings/styled-components.d.ts b/app/typings/styled-components.d.ts
index 56e1896e79..d33cd94082 100644
--- a/app/typings/styled-components.d.ts
+++ b/app/typings/styled-components.d.ts
@@ -145,6 +145,7 @@ declare module "styled-components" {
placeholder: string;
commentMarkBackground: string;
sidebarBackground: string;
+ sidebarHoverBackground: string;
sidebarActiveBackground: string;
sidebarControlHoverBackground: string;
sidebarDraftBorder: string;
diff --git a/shared/styles/theme.ts b/shared/styles/theme.ts
index 5529b8c7b4..3276adf557 100644
--- a/shared/styles/theme.ts
+++ b/shared/styles/theme.ts
@@ -129,7 +129,8 @@ export const buildLightTheme = (input: Partial): DefaultTheme => {
textDiffDeletedBackground: "#ffebe9",
placeholder: "#a2b2c3",
sidebarBackground: colors.warmGrey,
- sidebarActiveBackground: "#d7e0ea",
+ sidebarHoverBackground: "hsl(212 31% 90% / 1)",
+ sidebarActiveBackground: "hsl(212 31% 85% / 1)",
sidebarControlHoverBackground: "rgb(138 164 193 / 20%)",
sidebarDraftBorder: darken("0.25", colors.warmGrey),
sidebarText: "rgb(78, 92, 110)",
@@ -192,6 +193,7 @@ export const buildDarkTheme = (input: Partial): DefaultTheme => {
textDiffDeletedBackground: "rgba(248,81,73,0.15)",
placeholder: "#596673",
sidebarBackground: colors.veryDarkBlue,
+ sidebarHoverBackground: lighten(0.05, colors.veryDarkBlue),
sidebarActiveBackground: lighten(0.09, colors.veryDarkBlue),
sidebarControlHoverBackground: colors.white10,
sidebarDraftBorder: darken("0.35", colors.slate),