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 IntroductionProps = {
roadmapInfo: RoadmapDetailType;
};

const Introduction = ({ roadmapInfo }: IntroductionProps) => {
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((prev) => !prev);
};

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