mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: update navigation
This commit is contained in:
@@ -84,7 +84,7 @@ const ActivityCalendar = (props: Props) => {
|
|||||||
const isToday = dayjs().format("YYYY-MM-DD") === date;
|
const isToday = dayjs().format("YYYY-MM-DD") === date;
|
||||||
const tooltipText =
|
const tooltipText =
|
||||||
count === 0
|
count === 0
|
||||||
? t("memo.no-memos")
|
? date
|
||||||
: t("memo.count-memos-in-date", {
|
: t("memo.count-memos-in-date", {
|
||||||
count: count,
|
count: count,
|
||||||
memos: count === 1 ? t("common.memo") : t("common.memos"),
|
memos: count === 1 ? t("common.memo") : t("common.memos"),
|
||||||
|
@@ -1,22 +1,55 @@
|
|||||||
|
import { Globe2Icon, HomeIcon, LogInIcon } from "lucide-react";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
import useDebounce from "react-use/lib/useDebounce";
|
import useDebounce from "react-use/lib/useDebounce";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
import { Routes } from "@/router";
|
||||||
import { useMemoList, useUserStatsStore } from "@/store/v1";
|
import { useMemoList, useUserStatsStore } from "@/store/v1";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import MemoFilters from "../MemoFilters";
|
import MemoFilters from "../MemoFilters";
|
||||||
import StatisticsView from "../StatisticsView";
|
import StatisticsView from "../StatisticsView";
|
||||||
import ShortcutsSection from "./ShortcutsSection";
|
import ShortcutsSection from "./ShortcutsSection";
|
||||||
import TagsSection from "./TagsSection";
|
import TagsSection from "./TagsSection";
|
||||||
|
|
||||||
|
interface NavLinkItem {
|
||||||
|
id: string;
|
||||||
|
path: string;
|
||||||
|
title: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HomeSidebar = (props: Props) => {
|
const HomeSidebar = (props: Props) => {
|
||||||
|
const t = useTranslate();
|
||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const memoList = useMemoList();
|
const memoList = useMemoList();
|
||||||
const userStatsStore = useUserStatsStore();
|
const userStatsStore = useUserStatsStore();
|
||||||
|
|
||||||
|
const homeNavLink: NavLinkItem = {
|
||||||
|
id: "header-home",
|
||||||
|
path: Routes.ROOT,
|
||||||
|
title: t("common.home"),
|
||||||
|
icon: <HomeIcon className="w-4 h-auto opacity-70 shrink-0" />,
|
||||||
|
};
|
||||||
|
const exploreNavLink: NavLinkItem = {
|
||||||
|
id: "header-explore",
|
||||||
|
path: Routes.EXPLORE,
|
||||||
|
title: t("common.explore"),
|
||||||
|
icon: <Globe2Icon className="w-4 h-auto opacity-70 shrink-0" />,
|
||||||
|
};
|
||||||
|
const signInNavLink: NavLinkItem = {
|
||||||
|
id: "header-auth",
|
||||||
|
path: Routes.AUTH,
|
||||||
|
title: t("common.sign-in"),
|
||||||
|
icon: <LogInIcon className="w-4 h-auto opacity-70 shrink-0" />,
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLinks: NavLinkItem[] = currentUser ? [homeNavLink, exploreNavLink] : [exploreNavLink, signInNavLink];
|
||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
async () => {
|
async () => {
|
||||||
await userStatsStore.listUserStats(currentUser.name);
|
await userStatsStore.listUserStats(currentUser.name);
|
||||||
@@ -26,12 +59,32 @@ const HomeSidebar = (props: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={cn("relative w-full h-full overflow-auto hide-scrollbar flex flex-col justify-start items-start", props.className)}>
|
<aside className={cn("relative w-full h-full overflow-auto flex flex-col justify-start items-start", props.className)}>
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
|
<div className="mt-2 w-full space-y-1">
|
||||||
|
{navLinks.map((navLink) => (
|
||||||
|
<NavLink
|
||||||
|
key={navLink.id}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
|
"w-full px-2 rounded-xl border flex flex-row items-center text-sm text-zinc-600 dark:text-gray-400 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-700 dark:hover:bg-zinc-800",
|
||||||
|
isActive ? "bg-white drop-shadow-sm dark:bg-zinc-800 border-gray-200 dark:border-zinc-700" : "border-transparent",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
to={navLink.path}
|
||||||
|
viewTransition
|
||||||
|
>
|
||||||
|
{navLink.icon}
|
||||||
|
<span className="ml-2 truncate leading-8">{navLink.title}</span>
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<MemoFilters />
|
<MemoFilters />
|
||||||
|
<div className="px-2 w-full">
|
||||||
<StatisticsView />
|
<StatisticsView />
|
||||||
{currentUser && <ShortcutsSection />}
|
{currentUser && <ShortcutsSection />}
|
||||||
<TagsSection />
|
<TagsSection />
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -27,8 +27,8 @@ const HomeSidebarDrawer = () => {
|
|||||||
<SearchIcon className="w-5 h-auto dark:text-gray-400" />
|
<SearchIcon className="w-5 h-auto dark:text-gray-400" />
|
||||||
</Button>
|
</Button>
|
||||||
<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-4 bg-zinc-100 dark:bg-zinc-900">
|
<div className="w-full h-full bg-zinc-100 dark:bg-zinc-900">
|
||||||
<HomeSidebar className="py-4" />
|
<HomeSidebar className="px-4 py-4" />
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</>
|
</>
|
||||||
|
@@ -72,7 +72,7 @@ const MemoFilters = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full mt-3 flex flex-row justify-start items-center flex-wrap gap-x-2 gap-y-1">
|
<div className="w-full mt-2 flex flex-row justify-start items-center flex-wrap gap-x-2 gap-y-1">
|
||||||
{filters.map((filter) => (
|
{filters.map((filter) => (
|
||||||
<div
|
<div
|
||||||
key={getMemoFilterKey(filter)}
|
key={getMemoFilterKey(filter)}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Tooltip } from "@mui/joy";
|
import { Tooltip } from "@mui/joy";
|
||||||
import { ArchiveIcon, BellIcon, Globe2Icon, HomeIcon, LogInIcon, PaperclipIcon, SettingsIcon, SmileIcon, User2Icon } from "lucide-react";
|
import { ArchiveIcon, BellIcon, PaperclipIcon, SettingsIcon, SmileIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
@@ -37,30 +37,12 @@ const Navigation = observer((props: Props) => {
|
|||||||
userStore.fetchInboxes();
|
userStore.fetchInboxes();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const homeNavLink: NavLinkItem = {
|
|
||||||
id: "header-home",
|
|
||||||
path: Routes.ROOT,
|
|
||||||
title: t("common.home"),
|
|
||||||
icon: <HomeIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
|
||||||
};
|
|
||||||
const resourcesNavLink: NavLinkItem = {
|
const resourcesNavLink: NavLinkItem = {
|
||||||
id: "header-resources",
|
id: "header-resources",
|
||||||
path: Routes.RESOURCES,
|
path: Routes.RESOURCES,
|
||||||
title: t("common.resources"),
|
title: t("common.resources"),
|
||||||
icon: <PaperclipIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
icon: <PaperclipIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
||||||
};
|
};
|
||||||
const exploreNavLink: NavLinkItem = {
|
|
||||||
id: "header-explore",
|
|
||||||
path: Routes.EXPLORE,
|
|
||||||
title: t("common.explore"),
|
|
||||||
icon: <Globe2Icon className="w-6 h-auto opacity-70 shrink-0" />,
|
|
||||||
};
|
|
||||||
const profileNavLink: NavLinkItem = {
|
|
||||||
id: "header-profile",
|
|
||||||
path: user ? `/u/${encodeURIComponent(user.username)}` : "",
|
|
||||||
title: t("common.profile"),
|
|
||||||
icon: <User2Icon className="w-6 h-auto opacity-70 shrink-0" />,
|
|
||||||
};
|
|
||||||
const inboxNavLink: NavLinkItem = {
|
const inboxNavLink: NavLinkItem = {
|
||||||
id: "header-inbox",
|
id: "header-inbox",
|
||||||
path: Routes.INBOX,
|
path: Routes.INBOX,
|
||||||
@@ -86,12 +68,6 @@ const Navigation = observer((props: Props) => {
|
|||||||
title: t("common.settings"),
|
title: t("common.settings"),
|
||||||
icon: <SettingsIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
icon: <SettingsIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
||||||
};
|
};
|
||||||
const signInNavLink: NavLinkItem = {
|
|
||||||
id: "header-auth",
|
|
||||||
path: Routes.AUTH,
|
|
||||||
title: t("common.sign-in"),
|
|
||||||
icon: <LogInIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
|
||||||
};
|
|
||||||
const aboutNavLink: NavLinkItem = {
|
const aboutNavLink: NavLinkItem = {
|
||||||
id: "header-about",
|
id: "header-about",
|
||||||
path: Routes.ABOUT,
|
path: Routes.ABOUT,
|
||||||
@@ -99,16 +75,14 @@ const Navigation = observer((props: Props) => {
|
|||||||
icon: <SmileIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
icon: <SmileIcon className="w-6 h-auto opacity-70 shrink-0" />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const navLinks: NavLinkItem[] = user
|
const navLinks: NavLinkItem[] = user ? [resourcesNavLink, inboxNavLink, archivedNavLink, settingNavLink] : [aboutNavLink];
|
||||||
? [homeNavLink, resourcesNavLink, exploreNavLink, profileNavLink, inboxNavLink, archivedNavLink, settingNavLink]
|
|
||||||
: [exploreNavLink, signInNavLink, aboutNavLink];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn("w-full h-full overflow-auto flex flex-col justify-start items-start py-4 md:pt-6 z-30 hide-scrollbar", className)}
|
className={cn("w-full h-full overflow-auto flex flex-col justify-start items-start py-4 md:pt-6 z-30 hide-scrollbar", className)}
|
||||||
>
|
>
|
||||||
<UserBanner collapsed={collapsed} />
|
<UserBanner collapsed={collapsed} />
|
||||||
<div className="w-full px-1 py-2 flex flex-col justify-start items-start shrink-0 space-y-2">
|
<div className="w-full mt-2 px-1 py-2 flex flex-col justify-start items-start shrink-0 space-y-2">
|
||||||
{navLinks.map((navLink) => (
|
{navLinks.map((navLink) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
|
@@ -27,7 +27,7 @@ const NavigationDrawer = () => {
|
|||||||
<MenuIcon className="w-5 h-auto dark:text-gray-400" />
|
<MenuIcon className="w-5 h-auto dark:text-gray-400" />
|
||||||
</Button>
|
</Button>
|
||||||
<Drawer anchor="left" size="sm" open={open} onClose={toggleDrawer(false)}>
|
<Drawer anchor="left" size="sm" open={open} onClose={toggleDrawer(false)}>
|
||||||
<div className="w-full h-full overflow-auto px-4 bg-zinc-100 dark:bg-zinc-900">
|
<div className="w-full h-full overflow-auto px-2 bg-zinc-100 dark:bg-zinc-900">
|
||||||
<Navigation />
|
<Navigation />
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
@@ -31,15 +31,15 @@ const SearchBar = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-auto flex flex-row justify-start items-center">
|
<div className="relative w-full h-auto flex flex-row justify-start items-center">
|
||||||
<SearchIcon className="absolute left-3 w-4 h-auto opacity-40" />
|
<SearchIcon className="absolute left-2 w-4 h-auto opacity-40" />
|
||||||
<input
|
<input
|
||||||
className="w-full text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 border dark:border-zinc-800 text-sm leading-7 rounded-lg p-1 pl-8 outline-none"
|
className="w-full text-gray-500 leading-6 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 border dark:border-zinc-800 text-sm rounded-xl p-1 pl-8 outline-none"
|
||||||
placeholder={t("memo.search-placeholder")}
|
placeholder={t("memo.search-placeholder")}
|
||||||
value={queryText}
|
value={queryText}
|
||||||
onChange={onTextChange}
|
onChange={onTextChange}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
<MemoDisplaySettingMenu className="absolute right-3 top-3" />
|
<MemoDisplaySettingMenu className="absolute right-2 top-2.5" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -60,7 +60,7 @@ const StatisticsView = () => {
|
|||||||
showMonthYearPicker
|
showMonthYearPicker
|
||||||
showFullMonthYearPicker
|
showFullMonthYearPicker
|
||||||
customInput={
|
customInput={
|
||||||
<span className="cursor-pointer text-base md:text-lg hover:text-gray-600 dark:hover:text-gray-300">
|
<span className="cursor-pointer text-base hover:text-gray-600 dark:hover:text-gray-300">
|
||||||
{dayjs(visibleMonthString).toDate().toLocaleString(i18n.language, { year: "numeric", month: "long" })}
|
{dayjs(visibleMonthString).toDate().toLocaleString(i18n.language, { year: "numeric", month: "long" })}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Dropdown, Menu, MenuButton, MenuItem } from "@mui/joy";
|
import { Dropdown, Menu, MenuButton, MenuItem } from "@mui/joy";
|
||||||
import { LogOutIcon, SmileIcon } from "lucide-react";
|
import { LogOutIcon, SmileIcon, User2Icon } from "lucide-react";
|
||||||
import { authServiceClient } from "@/grpcweb";
|
import { authServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
@@ -17,10 +17,10 @@ const UserBanner = (props: Props) => {
|
|||||||
const { collapsed } = props;
|
const { collapsed } = props;
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
const user = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
|
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
|
||||||
const title = (user ? user.nickname || user.username : workspaceGeneralSetting.customProfile?.title) || "Memos";
|
const title = (currentUser ? currentUser.nickname || currentUser.username : workspaceGeneralSetting.customProfile?.title) || "Memos";
|
||||||
const avatarUrl = (user ? user.avatarUrl : workspaceGeneralSetting.customProfile?.logoUrl) || "/full-logo.webp";
|
const avatarUrl = (currentUser ? currentUser.avatarUrl : workspaceGeneralSetting.customProfile?.logoUrl) || "/full-logo.webp";
|
||||||
|
|
||||||
const handleSignOut = async () => {
|
const handleSignOut = async () => {
|
||||||
await authServiceClient.signOut({});
|
await authServiceClient.signOut({});
|
||||||
@@ -30,10 +30,10 @@ const UserBanner = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="relative w-full h-auto px-1 shrink-0">
|
<div className="relative w-full h-auto px-1 shrink-0">
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<MenuButton disabled={!user} slots={{ root: "div" }}>
|
<MenuButton disabled={!currentUser} slots={{ root: "div" }}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-1 w-auto flex flex-row justify-start items-center cursor-pointer text-gray-800 dark:text-gray-400",
|
"w-auto flex flex-row justify-start items-center cursor-pointer text-gray-800 dark:text-gray-400",
|
||||||
collapsed ? "px-1" : "px-3",
|
collapsed ? "px-1" : "px-3",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -42,6 +42,10 @@ const UserBanner = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<Menu placement="bottom-start" style={{ zIndex: "9999" }}>
|
<Menu placement="bottom-start" style={{ zIndex: "9999" }}>
|
||||||
|
<MenuItem onClick={() => navigateTo(`/u/${encodeURIComponent(currentUser.username)}`)}>
|
||||||
|
<User2Icon className="w-4 h-auto opacity-60" />
|
||||||
|
<span className="truncate">{t("common.profile")}</span>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem onClick={handleSignOut}>
|
<MenuItem onClick={handleSignOut}>
|
||||||
<LogOutIcon className="w-4 h-auto opacity-60" />
|
<LogOutIcon className="w-4 h-auto opacity-60" />
|
||||||
<span className="truncate">{t("common.sign-out")}</span>
|
<span className="truncate">{t("common.sign-out")}</span>
|
||||||
|
@@ -21,10 +21,10 @@ const HomeLayout = observer(() => {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"sticky top-0 left-0 shrink-0 h-[100svh] transition-all",
|
"sticky top-0 left-0 shrink-0 h-[100svh] transition-all",
|
||||||
"border-r border-gray-200 dark:border-zinc-800",
|
"border-r border-gray-200 dark:border-zinc-800",
|
||||||
lg ? "px-5 w-72" : "px-4 w-56",
|
lg ? "w-72" : "w-56",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HomeSidebar className={cn("py-6")} />
|
<HomeSidebar className={cn("px-3 py-6")} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={cn("w-full mx-auto px-4 sm:px-6 sm:pt-3 md:pt-6 pb-8", md && "max-w-3xl")}>
|
<div className={cn("w-full mx-auto px-4 sm:px-6 sm:pt-3 md:pt-6 pb-8", md && "max-w-3xl")}>
|
||||||
|
@@ -7,7 +7,6 @@ import { toast } from "react-hot-toast";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import MemoFilters from "@/components/MemoFilters";
|
import MemoFilters from "@/components/MemoFilters";
|
||||||
import MemoView from "@/components/MemoView";
|
import MemoView from "@/components/MemoView";
|
||||||
import MobileHeader from "@/components/MobileHeader";
|
|
||||||
import PagedMemoList from "@/components/PagedMemoList";
|
import PagedMemoList from "@/components/PagedMemoList";
|
||||||
import UserAvatar from "@/components/UserAvatar";
|
import UserAvatar from "@/components/UserAvatar";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
@@ -77,8 +76,7 @@ const UserProfile = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="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="w-full max-w-5xl min-h-full flex flex-col justify-start items-center pb-8">
|
||||||
<MobileHeader />
|
|
||||||
<div className="w-full px-4 sm:px-6 flex flex-col justify-start items-center">
|
<div className="w-full px-4 sm:px-6 flex flex-col justify-start items-center">
|
||||||
{!loadingState.isLoading &&
|
{!loadingState.isLoading &&
|
||||||
(user ? (
|
(user ? (
|
||||||
|
Reference in New Issue
Block a user