mirror of https://github.com/usememos/memos.git
refactor: replace useResponsiveWidth with useMediaQuery across components
This commit is contained in:
parent
d21610cce1
commit
0ad75b8f08
|
|
@ -55,7 +55,6 @@
|
|||
"react-leaflet": "^4.2.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.9.6",
|
||||
"react-simple-pull-to-refresh": "^1.3.3",
|
||||
"react-use": "^17.6.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import useWindowScroll from "react-use/lib/useWindowScroll";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import { cn } from "@/lib/utils";
|
||||
import NavigationDrawer from "./NavigationDrawer";
|
||||
|
||||
|
|
@ -10,19 +10,22 @@ interface Props {
|
|||
|
||||
const MobileHeader = (props: Props) => {
|
||||
const { className, children } = props;
|
||||
const { sm } = useResponsiveWidth();
|
||||
const { y: offsetTop } = useWindowScroll();
|
||||
const md = useMediaQuery("md");
|
||||
const sm = useMediaQuery("sm");
|
||||
|
||||
if (md) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"sticky top-0 pt-3 pb-2 sm:pt-2 px-4 sm:px-6 sm:mb-1 bg-background bg-opacity-80 backdrop-blur-lg flex md:hidden flex-row justify-between items-center w-full h-auto flex-nowrap shrink-0 z-1",
|
||||
"sticky top-0 pt-3 pb-2 sm:pt-2 px-4 sm:px-6 sm:mb-1 bg-background bg-opacity-80 backdrop-blur-lg flex flex-row justify-between items-center w-full h-auto flex-nowrap shrink-0 z-1",
|
||||
offsetTop > 0 && "shadow-md",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row justify-start items-center mr-2 shrink-0 overflow-hidden">{!sm && <NavigationDrawer />}</div>
|
||||
<div className="flex flex-row justify-end items-center">{children}</div>
|
||||
{!sm && <NavigationDrawer />}
|
||||
<div className="w-full flex flex-row justify-end items-center">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { ArrowUpIcon, LoaderIcon } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { matchPath } from "react-router-dom";
|
||||
import PullToRefresh from "react-simple-pull-to-refresh";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { userServiceClient } from "@/connect";
|
||||
import { useView } from "@/contexts/ViewContext";
|
||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||
import { useInfiniteMemos } from "@/hooks/useMemoQueries";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import { Routes } from "@/router";
|
||||
import { State } from "@/types/proto/api/v1/common_pb";
|
||||
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
|
||||
|
|
@ -82,14 +80,13 @@ function useAutoFetchWhenNotScrollable({
|
|||
|
||||
const PagedMemoList = (props: Props) => {
|
||||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const { layout } = useView();
|
||||
|
||||
// Show memo editor only on the root route
|
||||
const showMemoEditor = Boolean(matchPath(Routes.ROOT, window.location.pathname));
|
||||
|
||||
// Use React Query's infinite query for pagination
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } = useInfiniteMemos({
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteMemos({
|
||||
state: props.state || State.NORMAL,
|
||||
orderBy: props.orderBy || "display_time desc",
|
||||
filter: props.filter,
|
||||
|
|
@ -192,29 +189,7 @@ const PagedMemoList = (props: Props) => {
|
|||
</div>
|
||||
);
|
||||
|
||||
if (md) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<PullToRefresh
|
||||
onRefresh={async () => {
|
||||
await refetch();
|
||||
}}
|
||||
pullingContent={
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<LoaderIcon className="opacity-60" />
|
||||
</div>
|
||||
}
|
||||
refreshingContent={
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<LoaderIcon className="animate-spin" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</PullToRefresh>
|
||||
);
|
||||
return children;
|
||||
};
|
||||
|
||||
const BackToTop = () => {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ export * from "./useCurrentUser";
|
|||
export * from "./useDateFilterNavigation";
|
||||
export * from "./useFilteredMemoStats";
|
||||
export * from "./useLoading";
|
||||
export * from "./useMediaQuery";
|
||||
export * from "./useMemoFilters";
|
||||
export * from "./useMemoSorting";
|
||||
export * from "./useNavigateTo";
|
||||
export * from "./useResponsiveWidth";
|
||||
export * from "./useUserLocale";
|
||||
export * from "./useUserTheme";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
type Breakpoint = "sm" | "md" | "lg";
|
||||
|
||||
const BREAKPOINTS: Record<Breakpoint, number> = {
|
||||
sm: 640,
|
||||
md: 768,
|
||||
lg: 1024,
|
||||
};
|
||||
|
||||
const useMediaQuery = (breakpoint: Breakpoint): boolean => {
|
||||
const [matches, setMatches] = useState(() => {
|
||||
if (typeof window === "undefined") return false;
|
||||
return window.matchMedia(`(min-width: ${BREAKPOINTS[breakpoint]}px)`).matches;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia(`(min-width: ${BREAKPOINTS[breakpoint]}px)`);
|
||||
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
setMatches(e.matches);
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener("change", handleChange);
|
||||
|
||||
return () => {
|
||||
mediaQuery.removeEventListener("change", handleChange);
|
||||
};
|
||||
}, [breakpoint]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
export default useMediaQuery;
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import useWindowSize from "react-use/lib/useWindowSize";
|
||||
|
||||
enum TailwindResponsiveWidth {
|
||||
sm = 640,
|
||||
md = 768,
|
||||
lg = 1024,
|
||||
}
|
||||
|
||||
const useResponsiveWidth = () => {
|
||||
const { width } = useWindowSize();
|
||||
return {
|
||||
sm: width >= TailwindResponsiveWidth.sm,
|
||||
md: width >= TailwindResponsiveWidth.md,
|
||||
lg: width >= TailwindResponsiveWidth.lg,
|
||||
};
|
||||
};
|
||||
|
||||
export default useResponsiveWidth;
|
||||
|
|
@ -6,12 +6,13 @@ import MobileHeader from "@/components/MobileHeader";
|
|||
import { userServiceClient } from "@/connect";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import { useFilteredMemoStats } from "@/hooks/useFilteredMemoStats";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Routes } from "@/router";
|
||||
|
||||
const MainLayout = () => {
|
||||
const { md, lg } = useResponsiveWidth();
|
||||
const md = useMediaQuery("md");
|
||||
const lg = useMediaQuery("lg");
|
||||
const location = useLocation();
|
||||
const currentUser = useCurrentUser();
|
||||
const [profileUserName, setProfileUserName] = useState<string | undefined>();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import Navigation from "@/components/Navigation";
|
|||
import { useInstance } from "@/contexts/InstanceContext";
|
||||
import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Loading from "@/pages/Loading";
|
||||
import { Routes } from "@/router";
|
||||
|
|
@ -13,7 +13,7 @@ import { Routes } from "@/router";
|
|||
const RootLayout = () => {
|
||||
const location = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { sm } = useResponsiveWidth();
|
||||
const sm = useMediaQuery("sm");
|
||||
const currentUser = useCurrentUser();
|
||||
const { memoRelatedSetting } = useInstance();
|
||||
const { removeFilter } = useMemoFilterContext();
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { attachmentServiceClient } from "@/connect";
|
|||
import { useDeleteAttachment } from "@/hooks/useAttachmentQueries";
|
||||
import useDialog from "@/hooks/useDialog";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import i18n from "@/i18n";
|
||||
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
|
@ -69,7 +69,7 @@ const AttachmentItem = ({ attachment }: AttachmentItemProps) => (
|
|||
|
||||
const Attachments = () => {
|
||||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const md = useMediaQuery("md");
|
||||
const loadingState = useLoading();
|
||||
const deleteUnusedAttachmentsDialog = useDialog();
|
||||
const { mutateAsync: deleteAttachment } = useDeleteAttachment();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useState } from "react";
|
|||
import Empty from "@/components/Empty";
|
||||
import MemoCommentMessage from "@/components/Inbox/MemoCommentMessage";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import { useNotifications } from "@/hooks/useUserQueries";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { UserNotification, UserNotification_Status, UserNotification_Type } from "@/types/proto/api/v1/user_service_pb";
|
||||
|
|
@ -13,7 +13,7 @@ import { useTranslate } from "@/utils/i18n";
|
|||
|
||||
const Inboxes = () => {
|
||||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const md = useMediaQuery("md");
|
||||
const [filter, setFilter] = useState<"all" | "unread" | "archived">("all");
|
||||
|
||||
// Fetch notifications with React Query
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ import MobileHeader from "@/components/MobileHeader";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { memoNamePrefix } from "@/helpers/resource-names";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import { useMemo, useMemoComments } from "@/hooks/useMemoQueries";
|
||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
const MemoDetail = () => {
|
||||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const md = useMediaQuery("md");
|
||||
const params = useParams();
|
||||
const navigateTo = useNavigateTo();
|
||||
const { state: locationState } = useLocation();
|
||||
|
|
@ -70,7 +70,7 @@ const MemoDetail = () => {
|
|||
</MobileHeader>
|
||||
)}
|
||||
<div className={cn("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
||||
<div className={cn(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
||||
<div className={cn("w-full md:w-[calc(100%-15rem)]")}>
|
||||
{parentMemo && (
|
||||
<div className="w-auto inline-block mb-2">
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import StorageSection from "@/components/Settings/StorageSection";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { useInstance } from "@/contexts/InstanceContext";
|
||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||
import { InstanceSetting_Key } from "@/types/proto/api/v1/instance_service_pb";
|
||||
import { User_Role } from "@/types/proto/api/v1/user_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
|
@ -38,7 +38,7 @@ const SECTION_ICON_MAP: Record<SettingSection, LucideIcon> = {
|
|||
|
||||
const Setting = () => {
|
||||
const t = useTranslate();
|
||||
const { md } = useResponsiveWidth();
|
||||
const sm = useMediaQuery("sm");
|
||||
const location = useLocation();
|
||||
const user = useCurrentUser();
|
||||
const { profile, fetchSetting } = useInstance();
|
||||
|
|
@ -85,57 +85,61 @@ const Setting = () => {
|
|||
|
||||
return (
|
||||
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-start sm:pt-3 md:pt-6 pb-8">
|
||||
{!md && <MobileHeader />}
|
||||
{!sm && <MobileHeader />}
|
||||
<div className="w-full px-4 sm:px-6">
|
||||
<div className="w-full border border-border flex flex-row justify-start items-start px-4 py-3 rounded-xl bg-background text-muted-foreground">
|
||||
<div className="hidden sm:flex flex-col justify-start items-start w-40 h-auto shrink-0 py-2">
|
||||
<span className="text-sm mt-0.5 pl-3 font-mono select-none text-muted-foreground">{t("common.basic")}</span>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-1">
|
||||
{BASIC_SECTIONS.map((item) => (
|
||||
<SectionMenuItem
|
||||
key={item}
|
||||
text={t(`setting.${item}`)}
|
||||
icon={SECTION_ICON_MAP[item]}
|
||||
isSelected={state.selectedSection === item}
|
||||
onClick={() => handleSectionSelectorItemClick(item)}
|
||||
/>
|
||||
))}
|
||||
{sm && (
|
||||
<div className="flex flex-col justify-start items-start w-40 h-auto shrink-0 py-2">
|
||||
<span className="text-sm mt-0.5 pl-3 font-mono select-none text-muted-foreground">{t("common.basic")}</span>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-1">
|
||||
{BASIC_SECTIONS.map((item) => (
|
||||
<SectionMenuItem
|
||||
key={item}
|
||||
text={t(`setting.${item}`)}
|
||||
icon={SECTION_ICON_MAP[item]}
|
||||
isSelected={state.selectedSection === item}
|
||||
onClick={() => handleSectionSelectorItemClick(item)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{isHost ? (
|
||||
<>
|
||||
<span className="text-sm mt-4 pl-3 font-mono select-none text-muted-foreground">{t("common.admin")}</span>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-1">
|
||||
{ADMIN_SECTIONS.map((item) => (
|
||||
<SectionMenuItem
|
||||
key={item}
|
||||
text={t(`setting.${item}`)}
|
||||
icon={SECTION_ICON_MAP[item]}
|
||||
isSelected={state.selectedSection === item}
|
||||
onClick={() => handleSectionSelectorItemClick(item)}
|
||||
/>
|
||||
))}
|
||||
<span className="px-3 mt-2 opacity-70 text-sm">
|
||||
{t("setting.version")}: v{profile.version}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{isHost ? (
|
||||
<>
|
||||
<span className="text-sm mt-4 pl-3 font-mono select-none text-muted-foreground">{t("common.admin")}</span>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-1">
|
||||
{ADMIN_SECTIONS.map((item) => (
|
||||
<SectionMenuItem
|
||||
key={item}
|
||||
text={t(`setting.${item}`)}
|
||||
icon={SECTION_ICON_MAP[item]}
|
||||
isSelected={state.selectedSection === item}
|
||||
onClick={() => handleSectionSelectorItemClick(item)}
|
||||
/>
|
||||
))}
|
||||
<span className="px-3 mt-2 opacity-70 text-sm">
|
||||
{t("setting.version")}: v{profile.version}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full grow sm:pl-4 overflow-x-auto">
|
||||
<div className="w-auto inline-block my-2 sm:hidden">
|
||||
<Select value={state.selectedSection} onValueChange={(value) => handleSectionSelectorItemClick(value as SettingSection)}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select section" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{settingsSectionList.map((settingSection) => (
|
||||
<SelectItem key={settingSection} value={settingSection}>
|
||||
{t(`setting.${settingSection}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{!sm && (
|
||||
<div className="w-auto inline-block my-2">
|
||||
<Select value={state.selectedSection} onValueChange={(value) => handleSectionSelectorItemClick(value as SettingSection)}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select section" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{settingsSectionList.map((settingSection) => (
|
||||
<SelectItem key={settingSection} value={settingSection}>
|
||||
{t(`setting.${settingSection}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
{state.selectedSection === "my-account" ? (
|
||||
<MyAccountSection />
|
||||
) : state.selectedSection === "preference" ? (
|
||||
|
|
|
|||
Loading…
Reference in New Issue