Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/#474] responsive basic setting & main page 임시 responsive 뷰 적용 #475

Merged
merged 14 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .stylelintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"plugins": ["stylelint-order"],
"customSyntax": "postcss-styled-syntax",
"rules": {
"media-query-no-invalid": null,
"declaration-empty-line-before": [
"never",
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@radix-ui/react-slot": "^1.1.0",
"@tanstack/react-query": "^5.17.10",
"@tanstack/react-query-devtools": "^5.37.1",
"@tiptap/extension-blockquote": "^2.2.3",
Expand Down
25 changes: 17 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import styled from '@emotion/styled';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import router from './routers/Router';
import { RouterProvider } from 'react-router-dom';
import { Suspense } from 'react';
import { RouterProvider } from 'react-router-dom';
import ResponsiveProvider from './components/commons/Responsive/ResponsiveProvider';
import Loading from './pages/loading/Loading';

import router from './routers/Router';
import { MOBILE_MEDIA_QUERY } from './styles/mediaQuery';
const App = () => {
const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -16,16 +17,18 @@ const App = () => {
});
return (
<>
<div style={{ fontSize: '16px' }}>
<QueryClientProvider client={queryClient}>
{/* <div style={{ fontSize: '16px' }}> */}
<QueryClientProvider client={queryClient}>
<ResponsiveProvider>
<DesktopWrapper>
<Suspense fallback={<Loading />}>
<RouterProvider router={router} />
</Suspense>
</DesktopWrapper>
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
</div>
</ResponsiveProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
{/* </div> */}
</>
);
};
Expand All @@ -38,4 +41,10 @@ const DesktopWrapper = styled.div`
align-items: center;
width: 100%;
scroll-behavior: smooth;

@media ${MOBILE_MEDIA_QUERY} {
/* width: 100%; */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) 지우기~

width: 100%;
max-width: 83rem;
}
`;
13 changes: 11 additions & 2 deletions src/components/commons/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import styled from '@emotion/styled';

import Spacing from './Spacing';
import { FOOTER_LINK } from '../../constants/footerLink';
import Spacing from './Spacing';

import { MOBILE_MEDIA_QUERY } from '../../styles/mediaQuery';
import {
FooterInstaIc,
FooterLogoIc,
Expand Down Expand Up @@ -53,11 +54,19 @@ const FooterWrapper = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
min-width: 136.7rem;

/* min-width: 136.7rem; */
height: 24.6rem;
padding: 8rem 16.5rem;

background-color: ${({ theme }) => theme.colors.grayViolet};

@media ${MOBILE_MEDIA_QUERY} {
flex-direction: column;
gap: 4rem;
align-items: center;
padding: 5rem;
}
`;

const FooterLayout = styled.div`
Expand Down
48 changes: 48 additions & 0 deletions src/components/commons/Responsive/Responsive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Slot } from '@radix-ui/react-slot';
import { ReactNode, useContext, useEffect, useState } from 'react';
import { MOBILE_MEDIA_QUERY } from '../../../styles/mediaQuery';
import { ResponsiveContext } from './context';
interface ResponsivePropTypes {
children: ReactNode;
only: AvailableSize;
asChild?: boolean;
}

type AvailableSize = 'mobile' | 'desktop';

const Responsive = ({ children, only, asChild }: ResponsivePropTypes) => {
const [current, setCurrent] = useState<AvailableSize | null>(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P5) current의 초기 값을 null로 설정해준 이유가 궁금해요 !

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current는 추후 useEffect에 브라우저 API를 통해 값이 설정되므로 값이 할당되기 전에는 mobile, desktop이 아닌 null값을 가지고 있어야한다는 판단이였습니다.


const Comp = asChild ? Slot : 'div';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) 굿


const { mobileOnlyClassName, desktopOnlyClassName } = useContext(ResponsiveContext);

const selectedClassName = () => {
if (only === 'desktop') {
return desktopOnlyClassName;
} else if (only === 'mobile') {
return mobileOnlyClassName;
} else {
throw new Error(`잘못된 타입의 only값 : ${only}`);
}
};

useEffect(() => {
const mobileMedia = window.matchMedia(MOBILE_MEDIA_QUERY);
const handleCurrentChange = (e: MediaQueryListEvent) => {
setCurrent(e.matches ? 'mobile' : 'desktop');
};
mobileMedia.addEventListener('change', handleCurrentChange);

return () => {
mobileMedia.removeEventListener('change', handleCurrentChange);
};
}, []);
return current === null || only === current ? (
<Comp className={`${selectedClassName()}`}>{children}</Comp>
) : (
<></>
Comment on lines +41 to +44
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) 삼항연산자 대신 && 연산자를 사용해서 불필요한 코드를 최소화 할 수 있을 것 같습니다 ! 아니면 빈 태그를 리턴해주시는 이유가 있을까요 ?

return current === null || only === current && (
    <Comp className={`${selectedClassName()}`}>{children}</Comp>
);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드가 거짓일 때 빈 fragment를 반환함으로서 명시적으로 빈 값을 나타내기 위해서 작성하였습니다.
논리연산자를 사용하면 undefined나 false를 반환하는데 반해 특정 조건에서 아무것도 렌더링 하지 않겠다 라는 의도를 명확히 표현할 수 있다고 생각했습니다.

);
};

export default Responsive;
38 changes: 38 additions & 0 deletions src/components/commons/Responsive/ResponsiveProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Global, css } from '@emotion/react';
import { ReactNode } from 'react';
import { MOBILE_MEDIA_QUERY } from '../../../styles/mediaQuery';
import { ResponsiveContext } from './context';
interface ResponsiveProviderProps {
children: ReactNode;
}

const ResponsiveProvider = ({ children }: ResponsiveProviderProps) => {
const mobileOnlyClassName = `responsive-mobile-only`;
const desktopOnlyClassName = `responsive-desktop-only`;

const ResponsiveClassName = css`
.${mobileOnlyClassName} {
display: none !important;
}
Comment on lines +14 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) !important를 사용하는 대신 쌓임맥락을 고려해서 스타일 우선순위를 높여볼 수도 있을 것 같습니다. 해당 방법이 통하지 않아서 !important 사용하신 걸까요 ??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 로직은 모든 페이지에서 사용되는 부분입니다. 이것이 서드파트 라이브러리일 수도 있고 모든 부분의 쌓임맥락을 고려할 수는 없기에 !important를 사용하였습니다.

.${desktopOnlyClassName} {
display: block !important;
}

@media ${MOBILE_MEDIA_QUERY} {
.${mobileOnlyClassName} {
display: block !important;
}
.${desktopOnlyClassName} {
display: none !important;
}
}
`;
return (
<ResponsiveContext.Provider value={{ mobileOnlyClassName, desktopOnlyClassName }}>
<Global styles={ResponsiveClassName} />
{children}
</ResponsiveContext.Provider>
);
};

export default ResponsiveProvider;
15 changes: 15 additions & 0 deletions src/components/commons/Responsive/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createContext } from 'react';

interface ResponsiveContextValue {
mobileOnlyClassName: string;
desktopOnlyClassName: string;
}

export const ResponsiveContext = createContext<ResponsiveContextValue>(
//ResponsiveProvider로 감싸져있지 않은 컴포넌트에서 값을 접근했을 때 또는 Context가 제대로 초기화되지 않았을 때 에러를 발생시켜 디버깅을 쉽게하기 위함
new Proxy({} as ResponsiveContextValue, {
get() {
throw new Error('ResponsiveProvider가 필요합니다.');
},
}),
);
Comment on lines +1 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) 저희는 전체 컴포넌트들이 다 프로바이더로 감싸져있는데 이 디버깅이 필요한 경우가 있나요??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

누군가 실수로 프로바이더 영역 외에 컴포넌트를 만들어 해당 ResponsiveContext를 사용할 경우 디버깅에 유리합니다. 해당 로직은 에러처리를 용이하기 위해 작성한 로직입니다. 휴먼에러는 언제든 발생할 수 있으니까요!

27 changes: 21 additions & 6 deletions src/pages/main/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import styled from '@emotion/styled';
import { useParams } from 'react-router-dom';

import Responsive from '../../components/commons/Responsive/Responsive';
import { MOBILE_MEDIA_QUERY } from '../../styles/mediaQuery';
import { AuthorizationHeader, UnAuthorizationHeader } from './../../components/commons/Header';
import Spacing from './../../components/commons/Spacing';
import DailyKeyword from './components/DailyKeyword';
import FaqDropdown from './components/FaqDropdown';
import GroupCarousel from './components/GroupCarousel';
import Introduction from './components/Introduction';
import Manual from './components/Manual';
import OnBoarding from './components/OnBoarding';
import { SkeletonComponent } from './components/skeletons/SkeletonComponent';
import { FAQ_DATA } from './constants/faqData';
import { useGetGroupContent, useGetRecommendTopic } from './hooks/queries';

import Footer from './../../components/commons/Footer';
import { AuthorizationHeader, UnAuthorizationHeader } from './../../components/commons/Header';
import Spacing from './../../components/commons/Spacing';
import GroupCarousel from './components/GroupCarousel';
import Footer from '../../components/commons/Footer';
const Main = () => {
const { content, moimId } = useParams();
const topic = useGetRecommendTopic(content || '');
Expand All @@ -28,7 +29,9 @@ const Main = () => {

<GroupCarouselLayout>
<CarouselContainer>
<CarouselTitle>마일과 함께하고 있는 글 모임이에요</CarouselTitle>
<Responsive only={'desktop'}>
<CarouselTitle>마일과 함께하고 있는 글 모임이에요</CarouselTitle>
</Responsive>
{isLoading || isFetching ? (
<SkeletonComponent groupLength={groupLength} />
) : (
Expand All @@ -44,6 +47,7 @@ const Main = () => {
<Spacing marginBottom="10" />
<Introduction />
<Spacing marginBottom="10" />

<Manual />

<FaqLayout>
Expand All @@ -70,6 +74,10 @@ const MainPageWrapper = styled.div`
width: 100%;

background-color: ${({ theme }) => theme.colors.backGroundGray};

/* @media ${MOBILE_MEDIA_QUERY} {
max-width: 76rem;
} */
`;

const GroupCarouselLayout = styled.section`
Expand All @@ -82,6 +90,11 @@ const GroupCarouselLayout = styled.section`
const CarouselContainer = styled.div`
width: 93rem;
height: 100%;

@media ${MOBILE_MEDIA_QUERY} {
width: 100%;
max-width: 420px;
}
`;

const CarouselBox = styled.div`
Expand All @@ -102,6 +115,8 @@ const FaqLayout = styled.section`
`;

const FaqContainer = styled.div`
padding: 0 2rem;

cursor: default;
`;

Expand Down
Loading