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/CK-210] 로드맵 디테일 페이지의 UI를 개편한다. #173

Merged
merged 9 commits into from
Oct 1, 2023
15 changes: 15 additions & 0 deletions client/src/components/icons/svgIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2488,3 +2488,18 @@ export const RightIcon = ({ width, ...props }: SVGProps<SVGSVGElement>) => (
<path d='M9 18l6-6-6-6' />
</svg>
);

export const NoImageIcon = ({ width, ...props }: SVGProps<SVGSVGElement>) => (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
width={width}
height={width}
viewBox='0 0 32 32'
>
<path
fill='currentColor'
d='M30 3.414L28.586 2L2 28.586L3.414 30l2-2H26a2.003 2.003 0 0 0 2-2V5.414zM26 26H7.414l7.793-7.793l2.379 2.379a2 2 0 0 0 2.828 0L22 19l4 3.997zm0-5.832l-2.586-2.586a2 2 0 0 0-2.828 0L19 19.168l-2.377-2.377L26 7.414zM6 22v-3l5-4.997l1.373 1.374l1.416-1.416l-1.375-1.375a2 2 0 0 0-2.828 0L6 16.172V6h16V4H6a2.002 2.002 0 0 0-2 2v16z'
/>
</svg>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import styled from 'styled-components';
import media from '@styles/media';

export const ExtraInfo = styled.div`
${({ theme }) => theme.fonts.description5};
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;

width: 30%;
height: 55rem;

${media.mobile`
display: none;
`}
`;

export const RoadmapMetadata = styled.div`
display: flex;
flex-direction: column;

& > div:not(:last-child) {
margin-bottom: 3rem;
}
`;

export const Category = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
`;

export const Difficulty = styled.div`
text-align: end;
`;

export const RecommendedRoadmapPeriod = styled.div`
text-align: end;
`;

export const Tags = styled.div`
color: ${({ theme }) => theme.colors.main_dark};

& > div {
text-align: end;
}
`;
33 changes: 33 additions & 0 deletions client/src/components/roadmapDetailPage/extraInfo/ExtraInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { RoadmapDetailType } from '@myTypes/roadmap/internal';
import * as S from './ExtraInfo.styles';
import SVGIcon from '@components/icons/SVGIcon';
import { CategoriesInfo } from '@constants/roadmap/category';

type ExtraInfoProps = {
roadmapInfo: RoadmapDetailType;
};

const ExtraInfo = ({ roadmapInfo }: ExtraInfoProps) => {
return (
<S.ExtraInfo>
<div>Created by {roadmapInfo.creator.name}</div>
<S.RoadmapMetadata>
<S.Category>
카테고리: {roadmapInfo.category.name}
<SVGIcon name={CategoriesInfo[roadmapInfo.category.id].iconName} />
</S.Category>
<S.Difficulty>난이도: {roadmapInfo.difficulty}</S.Difficulty>
<S.RecommendedRoadmapPeriod>
예상 소요시간: {roadmapInfo.recommendedRoadmapPeriod}일
</S.RecommendedRoadmapPeriod>
</S.RoadmapMetadata>
<S.Tags>
{roadmapInfo.tags.map((tag) => (
<div key={tag.id}>#{tag.name}</div>
))}
</S.Tags>
</S.ExtraInfo>
);
};

export default ExtraInfo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import media from '@styles/media';
import styled from 'styled-components';

export const IntroductionWrapper = styled.div`
width: 70%;

${media.mobile`
width:100%;
`}
`;

export const Introduction = styled.div<{ isExpanded: boolean }>`
${({ theme }) => theme.fonts.description5};
overflow: hidden;
max-height: ${({ isExpanded }) => (isExpanded ? 'auto' : '55rem')};

& > p:not(:last-child) {
margin-bottom: 2rem;
}

& div {
${({ theme }) => theme.fonts.h1};
margin-bottom: 0.5rem;
color: ${({ theme }) => theme.colors.main_dark};
}
`;

export const LineShadow = styled.div`
position: relative;
width: 100%;
height: 0.2rem;
box-shadow: 0 -4px 6px rgba(0, 0, 0, 1);
`;

export const ReadMoreButton = styled.button`
position: relative;
top: calc(-2rem - 4px);
left: 50%;
transform: translateX(-5rem);

width: 10rem;
height: 4rem;

background-color: ${({ theme }) => theme.colors.white};
border-radius: 8px;
box-shadow: ${({ theme }) => theme.shadows.main};
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { RoadmapDetailType } from '@myTypes/roadmap/internal';
import * as S from './Introduction.styles';
import { useEffect, useRef, useState } from 'react';

type IntroductionType = {
roadmapInfo: RoadmapDetailType;
};

const Introduction = ({ roadmapInfo }: IntroductionType) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

타입 네이밍 컨벤션은 InfoductionProps 에요!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

반영완!

const [isExpanded, setIsExpanded] = useState(false);
const [showMoreButton, setShowMoreButton] = useState(false);
const introRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!introRef.current) return;

const element = introRef.current;
if (element.scrollHeight > element.clientHeight) {
setShowMoreButton(true);
}
}, []);

const toggleExpand = () => {
setIsExpanded(!isExpanded);
Copy link
Collaborator

Choose a reason for hiding this comment

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

사소한 부분이지만 setIsExpanded((prev) => !prev) 가 더 정확한 표현인 것 같아요~!;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

오메 매의 눈 굿굿!
반영완!

};

return (
<S.IntroductionWrapper>
<S.Introduction isExpanded={isExpanded} ref={introRef}>
<p>
<div>설명</div>
{roadmapInfo.introduction}
</p>
<p>
<div>본문</div>
{roadmapInfo.content.content === ''
? '로드맵에 대한 설명이 없어요🥲'
: roadmapInfo.content.content}
</p>
</S.Introduction>
{showMoreButton && !isExpanded && (
<>
<S.LineShadow />
<S.ReadMoreButton onClick={toggleExpand}>더 보기</S.ReadMoreButton>
</>
)}
</S.IntroductionWrapper>
);
};

export default Introduction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import styled from 'styled-components';
import media from '@styles/media';

export const SliderContent = styled.div`
display: flex;
aspect-ratio: 5 / 3.5;
background-color: ${({ theme }) => theme.colors.gray100};
border-radius: 8px;

${media.mobile`
aspect-ratio: 0;
`}
`;

export const LeftContent = styled.div`
width: 45%;

${media.mobile`
display: none;
`}
`;

export const NodeImg = styled.img`
width: 100%;
height: 100%;
padding: 1.5rem;
object-fit: cover;
`;

export const NoImg = styled.div`
${({ theme }) => theme.fonts.title_large}
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;

width: 100%;
height: 100%;
`;

export const Separator = styled.div`
display: flex;
flex-direction: column;
width: 0.2rem;
height: 100%;

& > div {
height: 50%;
}

& > div:last-child {
background-color: black;
}
`;

export const RightContent = styled.div`
${({ theme }) => theme.fonts.h1}
overflow: scroll;
width: 55%;
padding: 1.5rem;
padding-top: 3rem;

${media.mobile`
width: 100%;
height: 60rem;
padding-top: 1.5rem;
`}
`;

export const ContentTitle = styled.div`
${({ theme }) => theme.fonts.title_large}
display: flex;
align-items: center;
margin-bottom: 1rem;
`;

export const Step = styled.div`
${({ theme }) => theme.fonts.h2}
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;

width: 3rem;
height: 3rem;
margin-right: 0.5rem;

border: 0.3rem solid black;
border-radius: 50%;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { NodeType } from '@myTypes/roadmap/internal';
import * as S from './NodeContent.styles';
import SVGIcon from '@components/icons/SVGIcon';

type NodeContentProps = {
node: NodeType;
index: number;
};

const NodeContent = ({ node, index }: NodeContentProps) => {
return (
<S.SliderContent key={node.id}>
<S.LeftContent>
{node.imageUrls[0] ? (
<S.NodeImg src={node.imageUrls[0]} />
) : (
<S.NoImg>
<SVGIcon name='NoImageIcon' size={100} />
<div>No Image</div>
</S.NoImg>
)}
</S.LeftContent>
<S.Separator>
<div />
<div />
</S.Separator>
<S.RightContent>
<S.ContentTitle>
<S.Step>{index + 1}</S.Step>
<p>{node.title}</p>
</S.ContentTitle>
{node.description}
</S.RightContent>
</S.SliderContent>
);
};

export default NodeContent;
Loading
Loading