mirror of
https://github.com/docmost/docmost.git
synced 2025-11-17 23:01:09 +10:00
refactor page breadcrumbs
This commit is contained in:
@ -2,6 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--mantine-color-default-color);
|
color: var(--mantine-color-default-color);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts";
|
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { findBreadcrumbPath } from "@/features/page/tree/utils";
|
import { findBreadcrumbPath } from "@/features/page/tree/utils";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -9,14 +9,16 @@ import {
|
|||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Text,
|
Text,
|
||||||
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconDots } from "@tabler/icons-react";
|
import { IconCornerLeftDownDouble, IconDots } from "@tabler/icons-react";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import classes from "./breadcrumb.module.css";
|
import classes from "./breadcrumb.module.css";
|
||||||
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
||||||
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
||||||
import { usePageQuery } from "@/features/page/queries/page-query.ts";
|
import { usePageQuery } from "@/features/page/queries/page-query.ts";
|
||||||
import { extractPageSlugId } from "@/lib";
|
import { extractPageSlugId } from "@/lib";
|
||||||
|
import { useMediaQuery } from "@mantine/hooks";
|
||||||
|
|
||||||
function getTitle(name: string, icon: string) {
|
function getTitle(name: string, icon: string) {
|
||||||
if (icon) {
|
if (icon) {
|
||||||
@ -34,6 +36,7 @@ export default function Breadcrumb() {
|
|||||||
const { data: currentPage } = usePageQuery({
|
const { data: currentPage } = usePageQuery({
|
||||||
pageId: extractPageSlugId(pageSlug),
|
pageId: extractPageSlugId(pageSlug),
|
||||||
});
|
});
|
||||||
|
const isMobile = useMediaQuery("(max-width: 48em)");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (treeData?.length > 0 && currentPage) {
|
if (treeData?.length > 0 && currentPage) {
|
||||||
@ -43,7 +46,7 @@ export default function Breadcrumb() {
|
|||||||
}, [currentPage?.id, treeData]);
|
}, [currentPage?.id, treeData]);
|
||||||
|
|
||||||
const HiddenNodesTooltipContent = () =>
|
const HiddenNodesTooltipContent = () =>
|
||||||
breadcrumbNodes?.slice(1, -2).map((node) => (
|
breadcrumbNodes?.slice(1, -1).map((node) => (
|
||||||
<Button.Group orientation="vertical" key={node.id}>
|
<Button.Group orientation="vertical" key={node.id}>
|
||||||
<Button
|
<Button
|
||||||
justify="start"
|
justify="start"
|
||||||
@ -59,17 +62,39 @@ export default function Breadcrumb() {
|
|||||||
</Button.Group>
|
</Button.Group>
|
||||||
));
|
));
|
||||||
|
|
||||||
const renderAnchor = (node: SpaceTreeNode) => (
|
const MobileHiddenNodesTooltipContent = () =>
|
||||||
|
breadcrumbNodes?.map((node) => (
|
||||||
|
<Button.Group orientation="vertical" key={node.id}>
|
||||||
|
<Button
|
||||||
|
justify="start"
|
||||||
|
component={Link}
|
||||||
|
to={buildPageUrl(spaceSlug, node.slugId, node.name)}
|
||||||
|
variant="default"
|
||||||
|
style={{ border: "none" }}
|
||||||
|
>
|
||||||
|
<Text fz={"sm"} className={classes.truncatedText}>
|
||||||
|
{getTitle(node.name, node.icon)}
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
));
|
||||||
|
|
||||||
|
const renderAnchor = useCallback(
|
||||||
|
(node: SpaceTreeNode) => (
|
||||||
|
<Tooltip label={node.name}>
|
||||||
<Anchor
|
<Anchor
|
||||||
component={Link}
|
component={Link}
|
||||||
to={buildPageUrl(spaceSlug, node.slugId, node.name)}
|
to={buildPageUrl(spaceSlug, node.slugId, node.name)}
|
||||||
underline="never"
|
underline="never"
|
||||||
fz={"sm"}
|
fz="sm"
|
||||||
key={node.id}
|
key={node.id}
|
||||||
className={classes.truncatedText}
|
className={classes.truncatedText}
|
||||||
>
|
>
|
||||||
{getTitle(node.name, node.icon)}
|
{getTitle(node.name, node.icon)}
|
||||||
</Anchor>
|
</Anchor>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
[spaceSlug],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getBreadcrumbItems = () => {
|
const getBreadcrumbItems = () => {
|
||||||
@ -77,7 +102,7 @@ export default function Breadcrumb() {
|
|||||||
|
|
||||||
if (breadcrumbNodes.length > 3) {
|
if (breadcrumbNodes.length > 3) {
|
||||||
const firstNode = breadcrumbNodes[0];
|
const firstNode = breadcrumbNodes[0];
|
||||||
const secondLastNode = breadcrumbNodes[breadcrumbNodes.length - 2];
|
//const secondLastNode = breadcrumbNodes[breadcrumbNodes.length - 2];
|
||||||
const lastNode = breadcrumbNodes[breadcrumbNodes.length - 1];
|
const lastNode = breadcrumbNodes[breadcrumbNodes.length - 1];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -98,7 +123,7 @@ export default function Breadcrumb() {
|
|||||||
<HiddenNodesTooltipContent />
|
<HiddenNodesTooltipContent />
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>,
|
</Popover>,
|
||||||
renderAnchor(secondLastNode),
|
//renderAnchor(secondLastNode),
|
||||||
renderAnchor(lastNode),
|
renderAnchor(lastNode),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -106,11 +131,40 @@ export default function Breadcrumb() {
|
|||||||
return breadcrumbNodes.map(renderAnchor);
|
return breadcrumbNodes.map(renderAnchor);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMobileBreadcrumbItems = () => {
|
||||||
|
if (!breadcrumbNodes) return [];
|
||||||
|
|
||||||
|
if (breadcrumbNodes.length > 0) {
|
||||||
|
return [
|
||||||
|
<Popover
|
||||||
|
width={250}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
shadow="xl"
|
||||||
|
key="hidden-nodes"
|
||||||
|
>
|
||||||
|
<Popover.Target>
|
||||||
|
<Tooltip label="Breadcrumbs">
|
||||||
|
<ActionIcon color="gray" variant="transparent">
|
||||||
|
<IconCornerLeftDownDouble size={20} stroke={2} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover.Target>
|
||||||
|
<Popover.Dropdown>
|
||||||
|
<MobileHiddenNodesTooltipContent />
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return breadcrumbNodes.map(renderAnchor);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ overflow: "hidden" }}>
|
<div style={{ overflow: "hidden" }}>
|
||||||
{breadcrumbNodes && (
|
{breadcrumbNodes && (
|
||||||
<Breadcrumbs className={classes.breadcrumbs}>
|
<Breadcrumbs className={classes.breadcrumbs}>
|
||||||
{getBreadcrumbItems()}
|
{isMobile ? getMobileBreadcrumbItems() : getBreadcrumbItems()}
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Anchor,
|
Anchor,
|
||||||
|
Button,
|
||||||
Group,
|
Group,
|
||||||
Indicator,
|
Indicator,
|
||||||
Popover,
|
Popover,
|
||||||
@ -94,18 +95,23 @@ export default function ShareModal({ readOnly }: ShareModalProps) {
|
|||||||
return (
|
return (
|
||||||
<Popover width={350} position="bottom" withArrow shadow="md">
|
<Popover width={350} position="bottom" withArrow shadow="md">
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Tooltip label={t("Share")} openDelay={250} withArrow>
|
<Button
|
||||||
|
style={{ border: "none" }}
|
||||||
|
size="compact-sm"
|
||||||
|
leftSection={
|
||||||
<Indicator
|
<Indicator
|
||||||
color="green"
|
color="green"
|
||||||
offset={7}
|
offset={5}
|
||||||
disabled={!isPagePublic}
|
disabled={!isPagePublic}
|
||||||
withBorder
|
withBorder
|
||||||
>
|
>
|
||||||
<ActionIcon variant="default" style={{ border: "none" }}>
|
|
||||||
<IconWorld size={20} stroke={1.5} />
|
<IconWorld size={20} stroke={1.5} />
|
||||||
</ActionIcon>
|
|
||||||
</Indicator>
|
</Indicator>
|
||||||
</Tooltip>
|
}
|
||||||
|
variant="default"
|
||||||
|
>
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
<Popover.Dropdown style={{ userSelect: "none" }}>
|
<Popover.Dropdown style={{ userSelect: "none" }}>
|
||||||
{isDescendantShared ? (
|
{isDescendantShared ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user