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

Style/#286: 꿀팁 페이지 디자인 수정 #287

Merged
merged 25 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
da26a77
chore(delete unused images): 사용하지 않는 이미지들 삭제
hwinkr Nov 27, 2023
57fd0e3
chore(upload png images): 브라우저가 webp 확장자를 지원하지 않을 경우 사용 할 png 이미지 업로드
hwinkr Nov 27, 2023
ec3fba0
chore(upload webp images): TipCard 에서 사용할 백경이 webp 이미지 업로드
hwinkr Nov 27, 2023
383dcb4
chore(delete sidebar): 사용하지 않도록 결정 한 사이드 바 삭제
hwinkr Nov 27, 2023
2a5183b
chore(add path type): 꿀팁 페이지 경로에 type 경로 파라미터 추가
hwinkr Nov 27, 2023
30262e5
chore(delete unused file): 사용하지 않는 파일 삭제
hwinkr Nov 27, 2023
98639c5
chore(modify image size): 재사용하는 이미지 컴포넌트의 'tiny' 사이즈 변경
hwinkr Nov 27, 2023
7a0b4e7
feat(compound component): 팁 카드 컴포넌트에 합성 컴포넌트 적용
hwinkr Nov 27, 2023
617e656
feat(TipImage): 팁 카드 컴포넌트에서 사용할 팁 이미지 컴포넌트 구현
hwinkr Nov 27, 2023
05c27c2
feat(TipSubTitle): 팁 카드 컴포넌트에서 사용할 팁 소제목 컴포넌트 구현
hwinkr Nov 27, 2023
0634a15
feat(TipTitle): 팁 카드 컴포넌트에서 사용할 팁 제목 컴포넌트 구현
hwinkr Nov 27, 2023
9deaccf
feat(util function): 팁 카드 합성 컴포넌트에서 렌더링 할 자식 컴포넌트를 결정하는 함수 구현
hwinkr Nov 27, 2023
6ad84b3
refactor(Header): 홈페이지에서는 뒤로가기 버튼을 렌더링하지 않도록 변경
hwinkr Nov 27, 2023
1cff18c
feat(InformUpperLayout): 정보를 보여주는 페이지의 상단 레이아웃을 공통 컴포넌트로 구현
hwinkr Nov 27, 2023
f8b3f67
feat(InformSubTitle): 정보 페이지 상단 레이아웃 컴포넌트에서 사용할 소제목 컴포넌트 구현
hwinkr Nov 27, 2023
06159ce
feat(InformTitle): 정보 페이지 상단 레이아웃 컴포넌트에서 사용할 제목 컴포넌트 구현
hwinkr Nov 27, 2023
c9844aa
feat(InformTypeButton): 정보 페이지 상단 레이아웃 컴포넌트에서 사용할 버튼 컴포넌트 구현
hwinkr Nov 27, 2023
34806d6
feat(util function): 정보 페이지 상단 레이아웃 합성 컴포넌트에서 렌더링 할 자식 컴포넌트를 결정하는 함수 구현
hwinkr Nov 27, 2023
940f65f
feat(TipCardList): 팁 카드들을 모두 렌더링하는 컴포넌트 구현
hwinkr Nov 27, 2023
6bf3166
config(add tip path): 경로를 상수로 관리하는 PATH 객체에 tip 페이지 경로 추가
hwinkr Nov 27, 2023
ce845c1
config(modify tip constants): 꿀팁 페이지에서 사용할 데이터 및 상수 설정
hwinkr Nov 27, 2023
19b2d53
refactor(modify tip page): 팁 페이지 스타일 수정, 합성 컴포넌트 적용
hwinkr Nov 27, 2023
c9b2f7f
chore(modify typo): TipCarList -> TipCardList 오타 수정
hwinkr Nov 27, 2023
26f2b58
chore(add readonly): 객체 불변성을 유지하기 위한 readonly 추가
hwinkr Nov 28, 2023
2f1c3fc
Merge branch 'dev' into style/#286
hwinkr Nov 28, 2023
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
Binary file removed public/assets/baekgyeong-speaker.png
Binary file not shown.
Binary file removed public/assets/baekgyeong-whalebe.png
Binary file not shown.
Binary file removed public/assets/pknu.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/tipImages/png/baekgyeong_guide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/tipImages/png/baekgyeong_hi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/tipImages/png/baekgyeong_love.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/tipImages/png/baekgyeong_teach.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/tipImages/png/pknu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file added public/assets/tipImages/webp/baekgyeong_hi.webp
Binary file not shown.
Binary file added public/assets/tipImages/webp/baekgyeong_love.webp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added public/assets/tipImages/webp/pknu.webp
Binary file not shown.
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const App = () => {
<Route path="/announcement/*" element={<Announcement />} />
<Route path="/major-decision/*" element={<MajorDecision />} />
<Route path="/my" element={<My />} />
<Route path="/tip" element={<Tip />} />
<Route path="/tip/:type" element={<Tip />} />
<Route path="/FAQ" element={<FAQPage />} />
</Route>
<Route element={<OverlayProvider />}>
Expand Down
32 changes: 32 additions & 0 deletions src/components/Card/TipCard/TipImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Image from '@components/Common/Image';
import { css } from '@emotion/react';
import React from 'react';

interface TipImageProps {
title: string;
webpPath: string;
pngPath: string;
}

const TipImage = ({ title, webpPath, pngPath }: TipImageProps) => {
return (
<picture>
<source srcSet={webpPath} type="image/webp" />
<Image
src={pngPath}
size="tiny"
alt={title}
css={css`
padding: 0 16px 16px 0;
position: absolute;
z-index: -1;
right: 0;
bottom: 0;
opacity: 0.2;
`}
/>
Comment on lines +14 to +27
Copy link
Member

Choose a reason for hiding this comment

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

해당 코드는 webp 를 지원하는 브라우저면 webp 파일을 보여주고
그게 아니면 태그로 png를 보여주는 방식인가요?

분기처리가 없는걸로 봐서 2가지 방식의 이미지를 다 보여주는 방식인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

2개의 이미지를 다 보여주는 방식이 아니라, webp를 지원하는 브라우저면 webp를 사용해서 보여주고 그렇지 않다면 png 이미지를 보여주는 방식이에요. 여기 잘 정리되어 있어 도움 될 것 같아요~

</picture>
);
};

export default TipImage;
25 changes: 25 additions & 0 deletions src/components/Card/TipCard/TipSubTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from '@emotion/styled';
import React from 'react';

interface TipSubTitleProps {
subTitle: string;
}

const TipSubTitle = ({ subTitle }: TipSubTitleProps) => {
const seperatedSubTitle = subTitle.split('\n');
return (
<SubTitle>
{seperatedSubTitle.map((subTitle, index) => (
<p key={index}>{subTitle}</p>
))}
</SubTitle>
);
};

export default TipSubTitle;

const SubTitle = styled.span`
padding: 0 0 0 16px;
line-height: 1rem;
font-size: 0.8rem;
`;
26 changes: 26 additions & 0 deletions src/components/Card/TipCard/TipTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import styled from '@emotion/styled';
import React from 'react';

interface TipTitleProps {
title: string;
}

const TipTitle = ({ title }: TipTitleProps) => {
const seperatedTitle = title.split('\n');
return (
<Title>
{seperatedTitle.map((titleItem, index) => (
<p key={index}>{titleItem}</p>
))}
</Title>
);
};

export default TipTitle;

const Title = styled.span`
padding: 20px 0px 10px 16px;
line-height: 1.2rem;
font-size: 1.2rem;
font-weight: bold;
`;
21 changes: 21 additions & 0 deletions src/components/Card/TipCard/domain/getTipCardSubElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Children, isValidElement } from 'react';

import TipImage from '../TipImage';
import TipSubTitle from '../TipSubTitle';
import TipTitle from '../TipTitle';

type TipCardChildType = typeof TipTitle | typeof TipSubTitle | typeof TipImage;

const getTipCardSubElement = (
children: React.ReactNode,
childType: TipCardChildType,
) => {
const chidrenArray = Children.toArray(children);
const targetChild = chidrenArray
.filter((child) => isValidElement(child) && child.type === childType)
.slice(0, 2);

return targetChild;
};

export default getTipCardSubElement;
47 changes: 47 additions & 0 deletions src/components/Card/TipCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import styled from '@emotion/styled';
import { THEME } from '@styles/ThemeProvider/theme';
import React from 'react';

import getTipCardSubElement from './domain/getTipCardSubElement';
import TipImage from './TipImage';
import TipSubTitle from './TipSubTitle';
import TipTitle from './TipTitle';

type StrictPropsWithChildren<T = unknown> = T & {
children: React.ReactNode;
onClick: () => void;
};

const TipCard = ({ children, onClick }: StrictPropsWithChildren) => {
const tipTitle = getTipCardSubElement(children, TipTitle);
const tipSubTitle = getTipCardSubElement(children, TipSubTitle);
const tipImage = getTipCardSubElement(children, TipImage);

return (
<Container onClick={onClick}>
{tipTitle}
{tipSubTitle}
{tipImage}
</Container>
);
};

export default TipCard;

TipCard.TipTitle = TipTitle;
TipCard.TipSubTitle = TipSubTitle;
TipCard.TipImage = TipImage;

const Container = styled.div`
position: relative;
height: 10rem;
width: 10rem;
display: flex;
flex-direction: column;
background-color: ${THEME.PRIMARY}20;
border: 1px solid ${THEME.PRIMARY}30;
border-radius: 10px;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
gap: 5px;
z-index: 1;
`;
2 changes: 1 addition & 1 deletion src/components/Common/Image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const imageSize: ImageSize = {
large: setSize(200),
medium: setSize(150),
small: setSize(100),
tiny: setSize(45),
tiny: setSize(80),
};

const Image = ({
Expand Down
41 changes: 17 additions & 24 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
import Icon from '@components/Common/Icon';
import SideBar from '@components/SideBar';
import SideBarContent from '@components/SideBar/Content';
import styled from '@emotion/styled';
import useRoter from '@hooks/useRouter';
import { THEME } from '@styles/ThemeProvider/theme';
import { useState } from 'react';
import { useLocation } from 'react-router-dom';

const Header = () => {
const { routerTo, goBack } = useRoter();
const [open, setOpen] = useState<boolean>(false);
const location = useLocation();
const { routerTo, goBack, currentPath } = useRoter();
if (currentPath === '/map') return <></>;

if (location.pathname === '/map') return <></>;
const isNotHomePage = currentPath !== '/';

return (
<HeaderContainer>
<HeaderWrapper>
<Icon kind="arrowBack" onClick={goBack} />
<Logo onClick={() => routerTo('/')}>부림이</Logo>
<SideBar open={open} setOpen={setOpen}>
<SideBarContent setOpen={setOpen} />
</SideBar>
</HeaderWrapper>
</HeaderContainer>
<Container>
{isNotHomePage && (
<IconContainer>
<Icon kind="arrowBack" onClick={goBack} size="20" />
</IconContainer>
)}
<Logo onClick={() => routerTo('/')}>부림이</Logo>
</Container>
);
};

export default Header;

const HeaderContainer = styled.div`
const Container = styled.section`
position: relative;
display: flex;
justify-content: center;
align-items: center;

width: 100%;
max-width: 480px;
Expand All @@ -43,12 +39,9 @@ const HeaderContainer = styled.div`
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
`;

const HeaderWrapper = styled.div`
width: 90%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
const IconContainer = styled.div`
position: absolute;
left: 1rem;
`;

const Logo = styled.span`
Expand Down
28 changes: 28 additions & 0 deletions src/components/InformUpperLayout/InformSubTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styled from '@emotion/styled';
import { THEME } from '@styles/ThemeProvider/theme';
import React from 'react';

interface InformSubTitleProps {
subTitle: string;
}

const InformSubTitle = ({ subTitle }: InformSubTitleProps) => {
const seperatedSubTitle = subTitle.split('\n');

return (
<SubTitle>
{seperatedSubTitle.map((subTitle, index) => (
<p key={index}>{subTitle}</p>
))}
</SubTitle>
);
};

export default InformSubTitle;

const SubTitle = styled.span`
padding: 0 0 1rem 0;
color: ${THEME.TEXT.GRAY};
line-height: 1.3;
font-size: 0.9rem;
`;
20 changes: 20 additions & 0 deletions src/components/InformUpperLayout/InformTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styled from '@emotion/styled';
import { THEME } from '@styles/ThemeProvider/theme';
import React from 'react';

interface InformTitleProps {
title: string;
}

const InformTitle = ({ title }: InformTitleProps) => {
return <Title>{title}</Title>;
};

export default InformTitle;

const Title = styled.span`
padding: 1.5rem 0 1.5rem 0;
color: ${THEME.TEXT.BLACK};
font-size: 1.5rem;
font-weight: bold;
`;
35 changes: 35 additions & 0 deletions src/components/InformUpperLayout/InformTypeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Button from '@components/Common/Button';
import { css } from '@emotion/react';
import { THEME } from '@styles/ThemeProvider/theme';
import React from 'react';

interface InformTypeButtonProps {
type: string;
isActive: boolean;
onClick: () => void;
}

const InformTypeButton = ({
type,
isActive,
onClick,
}: InformTypeButtonProps) => {
return (
<Button
css={css`
height: 2rem;
width: 4rem;
border-radius: 4rem;
font-size: 0.7rem;
font-weight: normal;
background-color: ${isActive ? THEME.BUTTON.BLUE : THEME.BUTTON.GRAY};
color: ${isActive ? THEME.TEXT.WHITE : THEME.TEXT.GRAY};
`}
onClick={onClick}
>
{type}
</Button>
);
};

export default InformTypeButton;
Copy link
Member

Choose a reason for hiding this comment

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

props 로 받은 childType 에 매핑되는 함수가 언제 적용이 되는건가요?

흐름상 해당 함수에서 childType 으로 받은 함수를 적용하여 반환할 것 같은데 코드만 봤을때 잘 모르겠네요,,

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Children, isValidElement } from 'react';

import InformSubTitle from '../InformSubTitle';
import InformTitle from '../InformTitle';
import InformTypeButton from '../InformTypeButton';

type InformUpperLayoutChildType =
| typeof InformTitle
| typeof InformSubTitle
| typeof InformTypeButton;
Comment on lines +7 to +10
Copy link
Member

Choose a reason for hiding this comment

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

InformTitle, InformSubTitle, InformTypeButton
은 모두 function 타입으로 결국 같은 타입 아닐까요?
정상적인 함수 타입 체크가 가능할지 궁금하네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image

리액트 함수형 컴포넌트에 typeof 연산자를 적용하면 이미지와 같이 어떤 props를 받아서 어떤 것을 반환하는지를 타입으로 정합니다. 함수의 콜 시그니쳐가 타입이 된다고 생각하면 될듯요. 그래서 정상적인 타입 체크가 가능해요

const Component = (props) => UI

따라서,

const add = (a: number, b: number) => a + b;

const TipCard = ({ children, onClick }: StrictPropsWithChildren) => {
  const tipTitle = getTipCardSubElement(children, TipTitle);
  const tipSubTitle = getTipCardSubElement(children, TipSubTitle);
  const tipImage = getTipCardSubElement(children, TipImage);

  const addFunciton = getTipCardSubElement(children, add)
  //...

add함수를 전달하면

Argument of type '(a: number, b: number) => number' is not assignable to parameter of type 'TipCardChildType'.
  Type '(a: number, b: number) => number' is not assignable to type '({ title }: TipTitleProps) => EmotionJSX.Element'.
    Target signature provides too few arguments. Expected 2 or more, but got 1.

이와 같은 에러가 발생하는 것을 알 수 있어요.


const getInformUpperLayoutSubElement = (
children: React.ReactNode,
childType: InformUpperLayoutChildType,
) => {
const childrenArray = Children.toArray(children);
const targetChild = childrenArray
.filter((child) => isValidElement(child) && child.type === childType)
.slice(0, 2);
Copy link
Member

Choose a reason for hiding this comment

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

왜 최대 2개까지 배열을 자르는지 궁금해요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  • 일반, 고정 구분 버튼
  • 바로가기, 꿀팁 구분 버튼

이렇게 합성 컴포넌트의 자식들이 최대 2개씩 있어서 일단 2개까지 잘랐어요. 나중에 구분하는 버튼들이 많아지고 자르는 컴포넌트의 갯수에도 제한을 두려고 한다면 함수 호출부에서 어디까지 자를 것인지를 주입할 수도 있겠네요~


return targetChild;
};

export default getInformUpperLayoutSubElement;
Loading