feat: implement memo detail sidebar

This commit is contained in:
Steven 2024-07-01 23:06:10 +08:00
parent 05c6edfe2f
commit 291b815653
8 changed files with 170 additions and 62 deletions

View File

@ -26,7 +26,7 @@ const ExploreSidebarDrawer = () => {
<Icon.Search className="w-5 h-auto dark:text-gray-400" /> <Icon.Search className="w-5 h-auto dark:text-gray-400" />
</IconButton> </IconButton>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}> <Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
<div className="w-full h-full px-5 bg-zinc-100 dark:bg-zinc-900"> <div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
<ExploreSidebar className="py-4" /> <ExploreSidebar className="py-4" />
</div> </div>
</Drawer> </Drawer>

View File

@ -26,7 +26,7 @@ const HomeSidebarDrawer = () => {
<Icon.Search className="w-5 h-auto dark:text-gray-400" /> <Icon.Search className="w-5 h-auto dark:text-gray-400" />
</IconButton> </IconButton>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}> <Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
<div className="w-full h-full px-5 bg-zinc-100 dark:bg-zinc-900"> <div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
<HomeSidebar className="py-4" /> <HomeSidebar className="py-4" />
</div> </div>
</Drawer> </Drawer>

View File

@ -0,0 +1,48 @@
import clsx from "clsx";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
import Icon from "../Icon";
interface Props {
memo: Memo;
className?: string;
}
const MemoDetailSidebar = ({ memo, className }: Props) => {
const t = useTranslate();
if (!memo.property) {
return;
}
return (
<aside
className={clsx(
"relative w-full h-auto max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start",
className,
)}
>
<div className="flex flex-col justify-start items-start w-full mt-1 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
<div className="flex flex-row justify-start items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none">
<span>{t("common.tags")}</span>
{memo.property.tags.length > 0 && <span className="shrink-0">({memo.property.tags.length})</span>}
</div>
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
{memo.property.tags.map((tag) => (
<div
key={tag}
className="shrink-0 w-auto max-w-full text-sm rounded-md leading-6 flex flex-row justify-start items-center select-none hover:opacity-80 text-gray-600 dark:text-gray-400 dark:border-zinc-800"
>
<Icon.Hash className="group-hover:hidden w-4 h-auto shrink-0 opacity-40" />
<div className={clsx("inline-flex flex-nowrap ml-0.5 gap-0.5 cursor-pointer max-w-[calc(100%-16px)]")}>
<span className="truncate dark:opacity-80">{tag}</span>
</div>
</div>
))}
</div>
</div>
</aside>
);
};
export default MemoDetailSidebar;

View File

@ -0,0 +1,41 @@
import { Drawer, IconButton } from "@mui/joy";
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { Memo } from "@/types/proto/api/v1/memo_service";
import Icon from "../Icon";
import MemoDetailSidebar from "./MemoDetailSidebar";
interface Props {
memo: Memo;
}
const MemoDetailSidebarDrawer = ({ memo }: Props) => {
const location = useLocation();
const [open, setOpen] = useState(false);
useEffect(() => {
setOpen(false);
}, [location.pathname]);
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
return;
}
setOpen(inOpen);
};
return (
<>
<IconButton onClick={toggleDrawer(true)}>
<Icon.GanttChart className="w-5 h-auto dark:text-gray-400" />
</IconButton>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
<MemoDetailSidebar className="py-4" memo={memo} />
</div>
</Drawer>
</>
);
};
export default MemoDetailSidebarDrawer;

View File

@ -0,0 +1,4 @@
import MemoDetailSidebar from "./MemoDetailSidebar";
import MemoDetailSidebarDrawer from "./MemoDetailSidebarDrawer";
export { MemoDetailSidebar, MemoDetailSidebarDrawer };

View File

@ -26,7 +26,7 @@ const TimelineSidebarDrawer = () => {
<Icon.Search className="w-5 h-auto dark:text-gray-400" /> <Icon.Search className="w-5 h-auto dark:text-gray-400" />
</IconButton> </IconButton>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}> <Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
<div className="w-full h-full px-5 bg-zinc-100 dark:bg-zinc-900"> <div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
<TimelineSidebar className="py-4" /> <TimelineSidebar className="py-4" />
</div> </div>
</Drawer> </Drawer>

View File

@ -62,10 +62,10 @@ const UserStatisticsView = () => {
}; };
return ( return (
<div className="w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800"> <div className="group w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800">
<div className="w-full mb-1 flex flex-row justify-between items-center"> <div className="w-full mb-1 flex flex-row justify-between items-center">
<p className="text-sm font-medium leading-6 dark:text-gray-500">{t("common.statistics")}</p> <p className="text-sm font-medium leading-6 dark:text-gray-500">{t("common.statistics")}</p>
<div className=""> <div className="group-hover:block hidden">
<Tooltip title={"Refresh"} placement="top"> <Tooltip title={"Refresh"} placement="top">
<Icon.RefreshCcw <Icon.RefreshCcw
className="text-gray-400 w-4 h-auto cursor-pointer opacity-60 hover:opacity-100" className="text-gray-400 w-4 h-auto cursor-pointer opacity-60 hover:opacity-100"

View File

@ -1,14 +1,17 @@
import { Button } from "@mui/joy"; import { Button } from "@mui/joy";
import clsx from "clsx";
import { ClientError } from "nice-grpc-web"; import { ClientError } from "nice-grpc-web";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import { MemoDetailSidebar, MemoDetailSidebarDrawer } from "@/components/MemoDetailSidebar";
import showMemoEditorDialog from "@/components/MemoEditor/MemoEditorDialog"; import showMemoEditorDialog from "@/components/MemoEditor/MemoEditorDialog";
import MemoView from "@/components/MemoView"; import MemoView from "@/components/MemoView";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { useMemoStore } from "@/store/v1"; import { useMemoStore } from "@/store/v1";
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service"; import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
@ -16,6 +19,7 @@ import { useTranslate } from "@/utils/i18n";
const MemoDetail = () => { const MemoDetail = () => {
const t = useTranslate(); const t = useTranslate();
const { md } = useResponsiveWidth();
const params = useParams(); const params = useParams();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
@ -77,66 +81,77 @@ const MemoDetail = () => {
return ( return (
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8"> <section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<MobileHeader /> {!md && (
<div className="w-full px-4 sm:px-6"> <MobileHeader>
{parentMemo && ( <MemoDetailSidebarDrawer memo={memo} />
<div className="w-auto inline-block mb-2"> </MobileHeader>
<Link )}
className="px-3 py-1 border rounded-lg max-w-xs w-auto text-sm flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-400 dark:border-gray-500 hover:shadow hover:opacity-80" <div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
to={`/m/${parentMemo.uid}`} <div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
unstable_viewTransition {parentMemo && (
> <div className="w-auto inline-block mb-2">
<Icon.ArrowUpLeftFromCircle className="w-4 h-auto shrink-0 opacity-60 mr-2" /> <Link
<span className="truncate">{parentMemo.content}</span> className="px-3 py-1 border rounded-lg max-w-xs w-auto text-sm flex flex-row justify-start items-center flex-nowrap text-gray-600 dark:text-gray-400 dark:border-gray-500 hover:shadow hover:opacity-80"
</Link> to={`/m/${parentMemo.uid}`}
</div> unstable_viewTransition
)} >
<MemoView <Icon.ArrowUpLeftFromCircle className="w-4 h-auto shrink-0 opacity-60 mr-2" />
key={`${memo.name}-${memo.displayTime}`} <span className="truncate">{parentMemo.content}</span>
className="shadow hover:shadow-xl transition-all" </Link>
memo={memo} </div>
compact={false} )}
showCreator <MemoView
showVisibility key={`${memo.name}-${memo.displayTime}`}
showPinned className="shadow hover:shadow-md transition-all"
/> memo={memo}
<div className="pt-8 pb-16 w-full"> compact={false}
<h2 id="comments" className="sr-only"> showCreator
{t("memo.comment.self")} showVisibility
</h2> showPinned
<div className="relative mx-auto flex-grow w-full min-h-full flex flex-col justify-start items-start gap-y-1"> />
{comments.length === 0 ? ( <div className="pt-8 pb-16 w-full">
currentUser && ( <h2 id="comments" className="sr-only">
<div className="w-full flex flex-row justify-center items-center py-6"> {t("memo.comment.self")}
<Button </h2>
variant="plain" <div className="relative mx-auto flex-grow w-full min-h-full flex flex-col justify-start items-start gap-y-1">
color="neutral" {comments.length === 0 ? (
endDecorator={<Icon.MessageCircle className="w-5 h-auto text-gray-500" />} currentUser && (
onClick={handleShowCommentEditor} <div className="w-full flex flex-row justify-center items-center py-6">
> <Button
<span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span> variant="plain"
</Button> color="neutral"
</div> endDecorator={<Icon.MessageCircle className="w-5 h-auto text-gray-500" />}
) onClick={handleShowCommentEditor}
) : ( >
<> <span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span>
<div className="w-full flex flex-row justify-between items-center px-3 mb-2"> </Button>
<div className="flex flex-row justify-start items-center">
<Icon.MessageCircle className="w-5 h-auto text-gray-400 mr-1" />
<span className="text-gray-400 text-sm">{t("memo.comment.self")}</span>
<span className="text-gray-400 text-sm ml-1">({comments.length})</span>
</div> </div>
<Button variant="plain" color="neutral" onClick={handleShowCommentEditor}> )
<span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span> ) : (
</Button> <>
</div> <div className="w-full flex flex-row justify-between items-center px-3 mb-2">
{comments.map((comment) => ( <div className="flex flex-row justify-start items-center">
<MemoView key={`${comment.name}-${comment.displayTime}`} memo={comment} showCreator compact /> <Icon.MessageCircle className="w-5 h-auto text-gray-400 mr-1" />
))} <span className="text-gray-400 text-sm">{t("memo.comment.self")}</span>
</> <span className="text-gray-400 text-sm ml-1">({comments.length})</span>
)} </div>
<Button variant="plain" color="neutral" onClick={handleShowCommentEditor}>
<span className="font-normal text-gray-500">{t("memo.comment.write-a-comment")}</span>
</Button>
</div>
{comments.map((comment) => (
<MemoView key={`${comment.name}-${comment.displayTime}`} memo={comment} showCreator compact />
))}
</>
)}
</div>
</div> </div>
</div> </div>
{md && (
<div className="sticky top-0 left-0 shrink-0 -mt-6 w-56 h-full">
<MemoDetailSidebar className="py-6" memo={memo} />
</div>
)}
</div> </div>
</section> </section>
); );