Commit 0ad75b8f authored by Johnny's avatar Johnny

refactor: replace useResponsiveWidth with useMediaQuery across components

parent d21610cc
...@@ -55,7 +55,6 @@ ...@@ -55,7 +55,6 @@
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.9.6", "react-router-dom": "^7.9.6",
"react-simple-pull-to-refresh": "^1.3.3",
"react-use": "^17.6.0", "react-use": "^17.6.0",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
......
import useWindowScroll from "react-use/lib/useWindowScroll"; import useWindowScroll from "react-use/lib/useWindowScroll";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useMediaQuery from "@/hooks/useMediaQuery";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import NavigationDrawer from "./NavigationDrawer"; import NavigationDrawer from "./NavigationDrawer";
...@@ -10,19 +10,22 @@ interface Props { ...@@ -10,19 +10,22 @@ interface Props {
const MobileHeader = (props: Props) => { const MobileHeader = (props: Props) => {
const { className, children } = props; const { className, children } = props;
const { sm } = useResponsiveWidth();
const { y: offsetTop } = useWindowScroll(); const { y: offsetTop } = useWindowScroll();
const md = useMediaQuery("md");
const sm = useMediaQuery("sm");
if (md) return null;
return ( return (
<div <div
className={cn( 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", offsetTop > 0 && "shadow-md",
className, className,
)} )}
> >
<div className="flex flex-row justify-start items-center mr-2 shrink-0 overflow-hidden">{!sm && <NavigationDrawer />}</div> {!sm && <NavigationDrawer />}
<div className="flex flex-row justify-end items-center">{children}</div> <div className="w-full flex flex-row justify-end items-center">{children}</div>
</div> </div>
); );
}; };
......
import { ArrowUpIcon, LoaderIcon } from "lucide-react"; import { ArrowUpIcon, LoaderIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { matchPath } from "react-router-dom"; import { matchPath } from "react-router-dom";
import PullToRefresh from "react-simple-pull-to-refresh";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { userServiceClient } from "@/connect"; import { userServiceClient } from "@/connect";
import { useView } from "@/contexts/ViewContext"; import { useView } from "@/contexts/ViewContext";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import { useInfiniteMemos } from "@/hooks/useMemoQueries"; import { useInfiniteMemos } from "@/hooks/useMemoQueries";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { State } from "@/types/proto/api/v1/common_pb"; import { State } from "@/types/proto/api/v1/common_pb";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb"; import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
...@@ -82,14 +80,13 @@ function useAutoFetchWhenNotScrollable({ ...@@ -82,14 +80,13 @@ function useAutoFetchWhenNotScrollable({
const PagedMemoList = (props: Props) => { const PagedMemoList = (props: Props) => {
const t = useTranslate(); const t = useTranslate();
const { md } = useResponsiveWidth();
const { layout } = useView(); const { layout } = useView();
// Show memo editor only on the root route // Show memo editor only on the root route
const showMemoEditor = Boolean(matchPath(Routes.ROOT, window.location.pathname)); const showMemoEditor = Boolean(matchPath(Routes.ROOT, window.location.pathname));
// Use React Query's infinite query for pagination // 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, state: props.state || State.NORMAL,
orderBy: props.orderBy || "display_time desc", orderBy: props.orderBy || "display_time desc",
filter: props.filter, filter: props.filter,
...@@ -192,29 +189,7 @@ const PagedMemoList = (props: Props) => { ...@@ -192,29 +189,7 @@ const PagedMemoList = (props: Props) => {
</div> </div>
); );
if (md) { return children;
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>
);
}; };
const BackToTop = () => { const BackToTop = () => {
......
...@@ -3,9 +3,9 @@ export * from "./useCurrentUser"; ...@@ -3,9 +3,9 @@ export * from "./useCurrentUser";
export * from "./useDateFilterNavigation"; export * from "./useDateFilterNavigation";
export * from "./useFilteredMemoStats"; export * from "./useFilteredMemoStats";
export * from "./useLoading"; export * from "./useLoading";
export * from "./useMediaQuery";
export * from "./useMemoFilters"; export * from "./useMemoFilters";
export * from "./useMemoSorting"; export * from "./useMemoSorting";
export * from "./useNavigateTo"; export * from "./useNavigateTo";
export * from "./useResponsiveWidth";
export * from "./useUserLocale"; export * from "./useUserLocale";
export * from "./useUserTheme"; export * from "./useUserTheme";
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;
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"; ...@@ -6,12 +6,13 @@ import MobileHeader from "@/components/MobileHeader";
import { userServiceClient } from "@/connect"; import { userServiceClient } from "@/connect";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useFilteredMemoStats } from "@/hooks/useFilteredMemoStats"; import { useFilteredMemoStats } from "@/hooks/useFilteredMemoStats";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useMediaQuery from "@/hooks/useMediaQuery";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Routes } from "@/router"; import { Routes } from "@/router";
const MainLayout = () => { const MainLayout = () => {
const { md, lg } = useResponsiveWidth(); const md = useMediaQuery("md");
const lg = useMediaQuery("lg");
const location = useLocation(); const location = useLocation();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const [profileUserName, setProfileUserName] = useState<string | undefined>(); const [profileUserName, setProfileUserName] = useState<string | undefined>();
......
...@@ -5,7 +5,7 @@ import Navigation from "@/components/Navigation"; ...@@ -5,7 +5,7 @@ import Navigation from "@/components/Navigation";
import { useInstance } from "@/contexts/InstanceContext"; import { useInstance } from "@/contexts/InstanceContext";
import { useMemoFilterContext } from "@/contexts/MemoFilterContext"; import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useMediaQuery from "@/hooks/useMediaQuery";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import Loading from "@/pages/Loading"; import Loading from "@/pages/Loading";
import { Routes } from "@/router"; import { Routes } from "@/router";
...@@ -13,7 +13,7 @@ import { Routes } from "@/router"; ...@@ -13,7 +13,7 @@ import { Routes } from "@/router";
const RootLayout = () => { const RootLayout = () => {
const location = useLocation(); const location = useLocation();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { sm } = useResponsiveWidth(); const sm = useMediaQuery("sm");
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const { memoRelatedSetting } = useInstance(); const { memoRelatedSetting } = useInstance();
const { removeFilter } = useMemoFilterContext(); const { removeFilter } = useMemoFilterContext();
......
...@@ -15,7 +15,7 @@ import { attachmentServiceClient } from "@/connect"; ...@@ -15,7 +15,7 @@ import { attachmentServiceClient } from "@/connect";
import { useDeleteAttachment } from "@/hooks/useAttachmentQueries"; import { useDeleteAttachment } from "@/hooks/useAttachmentQueries";
import useDialog from "@/hooks/useDialog"; import useDialog from "@/hooks/useDialog";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useMediaQuery from "@/hooks/useMediaQuery";
import i18n from "@/i18n"; import i18n from "@/i18n";
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
...@@ -69,7 +69,7 @@ const AttachmentItem = ({ attachment }: AttachmentItemProps) => ( ...@@ -69,7 +69,7 @@ const AttachmentItem = ({ attachment }: AttachmentItemProps) => (
const Attachments = () => { const Attachments = () => {
const t = useTranslate(); const t = useTranslate();
const { md } = useResponsiveWidth(); const md = useMediaQuery("md");
const loadingState = useLoading(); const loadingState = useLoading();
const deleteUnusedAttachmentsDialog = useDialog(); const deleteUnusedAttachmentsDialog = useDialog();
const { mutateAsync: deleteAttachment } = useDeleteAttachment(); const { mutateAsync: deleteAttachment } = useDeleteAttachment();
......
...@@ -5,7 +5,7 @@ import { useState } from "react"; ...@@ -5,7 +5,7 @@ import { useState } from "react";
import Empty from "@/components/Empty"; import Empty from "@/components/Empty";
import MemoCommentMessage from "@/components/Inbox/MemoCommentMessage"; import MemoCommentMessage from "@/components/Inbox/MemoCommentMessage";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useMediaQuery from "@/hooks/useMediaQuery";
import { useNotifications } from "@/hooks/useUserQueries"; import { useNotifications } from "@/hooks/useUserQueries";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { UserNotification, UserNotification_Status, UserNotification_Type } from "@/types/proto/api/v1/user_service_pb"; import { UserNotification, UserNotification_Status, UserNotification_Type } from "@/types/proto/api/v1/user_service_pb";
...@@ -13,7 +13,7 @@ import { useTranslate } from "@/utils/i18n"; ...@@ -13,7 +13,7 @@ import { useTranslate } from "@/utils/i18n";
const Inboxes = () => { const Inboxes = () => {
const t = useTranslate(); const t = useTranslate();
const { md } = useResponsiveWidth(); const md = useMediaQuery("md");
const [filter, setFilter] = useState<"all" | "unread" | "archived">("all"); const [filter, setFilter] = useState<"all" | "unread" | "archived">("all");
// Fetch notifications with React Query // Fetch notifications with React Query
......
...@@ -10,15 +10,15 @@ import MobileHeader from "@/components/MobileHeader"; ...@@ -10,15 +10,15 @@ import MobileHeader from "@/components/MobileHeader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { memoNamePrefix } from "@/helpers/resource-names"; import { memoNamePrefix } from "@/helpers/resource-names";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useMediaQuery from "@/hooks/useMediaQuery";
import { useMemo, useMemoComments } from "@/hooks/useMemoQueries"; import { useMemo, useMemoComments } from "@/hooks/useMemoQueries";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
const MemoDetail = () => { const MemoDetail = () => {
const t = useTranslate(); const t = useTranslate();
const { md } = useResponsiveWidth(); const md = useMediaQuery("md");
const params = useParams(); const params = useParams();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const { state: locationState } = useLocation(); const { state: locationState } = useLocation();
...@@ -70,7 +70,7 @@ const MemoDetail = () => { ...@@ -70,7 +70,7 @@ const MemoDetail = () => {
</MobileHeader> </MobileHeader>
)} )}
<div className={cn("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}> <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 && ( {parentMemo && (
<div className="w-auto inline-block mb-2"> <div className="w-auto inline-block mb-2">
<Link <Link
......
...@@ -13,7 +13,7 @@ import StorageSection from "@/components/Settings/StorageSection"; ...@@ -13,7 +13,7 @@ import StorageSection from "@/components/Settings/StorageSection";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useInstance } from "@/contexts/InstanceContext"; import { useInstance } from "@/contexts/InstanceContext";
import useCurrentUser from "@/hooks/useCurrentUser"; 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 { InstanceSetting_Key } from "@/types/proto/api/v1/instance_service_pb";
import { User_Role } from "@/types/proto/api/v1/user_service_pb"; import { User_Role } from "@/types/proto/api/v1/user_service_pb";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
...@@ -38,7 +38,7 @@ const SECTION_ICON_MAP: Record<SettingSection, LucideIcon> = { ...@@ -38,7 +38,7 @@ const SECTION_ICON_MAP: Record<SettingSection, LucideIcon> = {
const Setting = () => { const Setting = () => {
const t = useTranslate(); const t = useTranslate();
const { md } = useResponsiveWidth(); const sm = useMediaQuery("sm");
const location = useLocation(); const location = useLocation();
const user = useCurrentUser(); const user = useCurrentUser();
const { profile, fetchSetting } = useInstance(); const { profile, fetchSetting } = useInstance();
...@@ -85,57 +85,61 @@ const Setting = () => { ...@@ -85,57 +85,61 @@ const Setting = () => {
return ( 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"> <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 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="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"> {sm && (
<span className="text-sm mt-0.5 pl-3 font-mono select-none text-muted-foreground">{t("common.basic")}</span> <div className="flex flex-col justify-start items-start w-40 h-auto shrink-0 py-2">
<div className="w-full flex flex-col justify-start items-start mt-1"> <span className="text-sm mt-0.5 pl-3 font-mono select-none text-muted-foreground">{t("common.basic")}</span>
{BASIC_SECTIONS.map((item) => ( <div className="w-full flex flex-col justify-start items-start mt-1">
<SectionMenuItem {BASIC_SECTIONS.map((item) => (
key={item} <SectionMenuItem
text={t(`setting.${item}`)} key={item}
icon={SECTION_ICON_MAP[item]} text={t(`setting.${item}`)}
isSelected={state.selectedSection === item} icon={SECTION_ICON_MAP[item]}
onClick={() => handleSectionSelectorItemClick(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> </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-full grow sm:pl-4 overflow-x-auto">
<div className="w-auto inline-block my-2 sm:hidden"> {!sm && (
<Select value={state.selectedSection} onValueChange={(value) => handleSectionSelectorItemClick(value as SettingSection)}> <div className="w-auto inline-block my-2">
<SelectTrigger className="w-[180px]"> <Select value={state.selectedSection} onValueChange={(value) => handleSectionSelectorItemClick(value as SettingSection)}>
<SelectValue placeholder="Select section" /> <SelectTrigger className="w-[180px]">
</SelectTrigger> <SelectValue placeholder="Select section" />
<SelectContent> </SelectTrigger>
{settingsSectionList.map((settingSection) => ( <SelectContent>
<SelectItem key={settingSection} value={settingSection}> {settingsSectionList.map((settingSection) => (
{t(`setting.${settingSection}`)} <SelectItem key={settingSection} value={settingSection}>
</SelectItem> {t(`setting.${settingSection}`)}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
</div> </Select>
</div>
)}
{state.selectedSection === "my-account" ? ( {state.selectedSection === "my-account" ? (
<MyAccountSection /> <MyAccountSection />
) : state.selectedSection === "preference" ? ( ) : state.selectedSection === "preference" ? (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment