diff --git a/src/components/common/Loading.tsx b/src/components/common/Loading.tsx new file mode 100644 index 0000000..8bf4ce9 --- /dev/null +++ b/src/components/common/Loading.tsx @@ -0,0 +1,25 @@ +import { cn } from '@/lib/utils'; + +interface LoadingProps { + size?: 'sm' | 'md' | 'lg' | 'xl'; + className?: string; +} + +const sizeClasses = { + sm: 'w-12 h-12', + md: 'w-14 h-14', + lg: 'w-16 h-16', + xl: 'w-20 h-20', +}; + +export function Loading({ size = 'lg', className }: LoadingProps) { + return ( +
+ ); +} diff --git a/src/components/common/LoadingOverlay.tsx b/src/components/common/LoadingOverlay.tsx new file mode 100644 index 0000000..812abac --- /dev/null +++ b/src/components/common/LoadingOverlay.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react'; + +import { useIsFetching, useIsMutating } from '@tanstack/react-query'; + +import { Loading } from './Loading'; +import { Overlay } from './Overlay'; + +interface LoadingOverlayProps { + className?: string; +} + +export function LoadingOverlay({ className }: LoadingOverlayProps) { + const MIN_DELAY = 200; + + const [showLoading, setShowLoading] = useState(false); + const isFetching = useIsFetching(); + const isMutating = useIsMutating(); + + const isLoading = isFetching > 0 || isMutating > 0; + + useEffect(() => { + let timeout: ReturnType | null = null; + + // 로딩중인 경우 로딩 보여줌 + if (isLoading) { + setShowLoading(true); + return; + } + + // 로딩 보여주고 있을 때, 최소 0.2초 이상 보여줌 + if (showLoading) { + timeout = setTimeout(() => { + setShowLoading(false); + }, MIN_DELAY); + } + + return () => { + if (timeout) clearTimeout(timeout); + }; + }, [isLoading, showLoading]); + + if (!showLoading) return null; + + return ( + + + + ); +} diff --git a/src/components/common/Overlay.tsx b/src/components/common/Overlay.tsx new file mode 100644 index 0000000..693ae62 --- /dev/null +++ b/src/components/common/Overlay.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { cn } from '@/lib/utils'; + +interface OverlayProps { + className?: string; + children?: React.ReactNode; +} + +export function Overlay({ className, children }: OverlayProps) { + return ( +
+ {children} +
+ ); +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index bb5a742..87f1614 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -9,6 +9,7 @@ import { useRouter } from 'next/router'; import ErrorBoundary from '@/components/common/ErrorBoundary'; import Gnb from '@/components/common/Gnb'; +import { LoadingOverlay } from '@/components/common/LoadingOverlay'; import { useInitUser } from '@/hooks/useInitUser'; import type { AppProps } from 'next/app'; @@ -26,7 +27,7 @@ export default function App({ Component, pageProps }: AppProps) { const router = useRouter(); const { pathname } = useRouter(); - const pagesWithoutGnb = ['/signup', '/signin', '/oauth/kakao', '/oauth/signup/kakao']; + const pagesWithoutGnb = ['/signup', '/signin', '/oauth/signup/kakao']; const hideHeader = pagesWithoutGnb.includes(pathname); const isLanding = pathname === '/'; const is404 = pathname === '/404'; @@ -38,6 +39,7 @@ export default function App({ Component, pageProps }: AppProps) { + {!hideHeader && } } router={router}>