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

[FE] feat: NavigationTab 컴포넌트 구현 #1037

Merged
merged 15 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
17 changes: 17 additions & 0 deletions frontend/src/components/common/NavigationTab/NavItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as S from './styles';

interface NavItemProps {
label: string;
$isSelected: boolean;
onClick: () => void;
}

const NavItem = ({ label, $isSelected, onClick }: NavItemProps) => {
return (
<S.NavItem $isSelected={$isSelected}>
<button onClick={onClick}>{label}</button>
</S.NavItem>
);
};

export default NavItem;
33 changes: 33 additions & 0 deletions frontend/src/components/common/NavigationTab/NavItem/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import styled from '@emotion/styled';

import media from '@/utils/media';

interface NavItemProps {
$isSelected: boolean;
}

export const NavItem = styled.li<NavItemProps>`
border-bottom: 0.3rem solid ${({ theme, $isSelected }) => ($isSelected ? theme.colors.primary : 'none')};
padding: 0 1rem 1.3rem 1rem;

button {
font-weight: ${({ theme }) => theme.fontWeight.semibold};
color: ${({ theme, $isSelected }) => ($isSelected ? theme.colors.black : theme.colors.disabled)};

&:hover {
color: ${({ theme }) => theme.colors.black};
}
}

${media.xSmall} {
display: flex;
flex: 1;
justify-content: center;

margin: 0 2rem;
}

${media.xxSmall} {
margin: 0 1.6rem;
}
`;
27 changes: 27 additions & 0 deletions frontend/src/components/common/NavigationTab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import useNavigationTabs from '@/hooks/useNavigationTabs';

import NavItem from './NavItem';
import * as S from './styles';

const NavigationTab = () => {
const { currentTabIndex, tabList } = useNavigationTabs();

return (
<S.NavContainer>
<S.NavList>
{tabList.map((tab, index) => {
return (
<NavItem
key={tab.label}
label={tab.label}
$isSelected={currentTabIndex === index}
onClick={tab.handleTabClick}
/>
);
})}
</S.NavList>
</S.NavContainer>
);
};

export default NavigationTab;
29 changes: 29 additions & 0 deletions frontend/src/components/common/NavigationTab/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import styled from '@emotion/styled';
Copy link
Contributor

Choose a reason for hiding this comment

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

스타일 컴포넌트에 props들 에 모두 $ 표시 없네요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

추가하겠습니다....😅


import media from '@/utils/media';

export const NavContainer = styled.nav`
position: relative;
display: flex;
width: calc(100vw - ${({ theme }) => theme.scrollbarWidth.basic});
height: 4rem;

border-bottom: 0.1rem solid ${({ theme }) => theme.colors.lightGray};

${media.small} {
width: calc(100vw - ${({ theme }) => theme.scrollbarWidth.small});
}
`;

export const NavList = styled.ul`
display: flex;
gap: 3rem;
padding: 0 2.5rem;
list-style-type: none;

${media.xSmall} {
gap: 0;
width: 100%;
padding: 0;
}
`;
8 changes: 7 additions & 1 deletion frontend/src/components/layouts/Topbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { useLocation } from 'react-router';

import UndraggableWrapper from '@/components/common/UndraggableWrapper';
import { ROUTE } from '@/constants';

import Logo from './components/Logo';
import * as S from './styles';

const Topbar = () => {
const { pathname } = useLocation();
const $hasNavigationTab = [ROUTE.reviewLinks, ROUTE.writtenReview].includes(pathname);

return (
<S.Layout>
<S.Layout $hasNavigationTab={$hasNavigationTab}>
<S.Container>
<UndraggableWrapper>
<Logo />
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/layouts/Topbar/styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from '@emotion/styled';

export const Layout = styled.section`
export const Layout = styled.section<{ $hasNavigationTab: boolean }>`
z-index: ${({ theme }) => theme.zIndex.topbar};

display: flex;
Expand All @@ -11,7 +11,8 @@ export const Layout = styled.section`
height: ${({ theme }) => theme.componentHeight.topbar};
padding: 2rem 2.5rem;

border-bottom: 0.1rem solid ${({ theme }) => theme.colors.lightGray};
border-bottom: ${({ theme, $hasNavigationTab }) =>
$hasNavigationTab ? `0.1rem solid ${theme.colors.lightGray}` : 'none'};
`;

export const Container = styled.div`
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/amplitudeEventName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const PAGE_VISITED_EVENT_NAME: { [key in Exclude<PageName, undefined>]: s
reviewWriting: '[page] 리뷰 작성 페이지',
reviewWritingComplete: '[page] 리뷰 작성 완료 페이지',
reviewLinks: '[page] 리뷰 링크 관리 페이지',
writtenReview: '[page] 작성한 리뷰 확인 페이지',
};

export const REVIEW_WRITING_EVENT_NAME = {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export const ROUTE = {
reviewZone: 'user/review-zone',
reviewCollection: 'user/review-collection',
reviewLinks: 'user/review-links',
writtenReview: 'user/written-review',
};
35 changes: 35 additions & 0 deletions frontend/src/hooks/useNavigationTabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useLocation, useNavigate } from 'react-router';

import { ROUTE } from '@/constants';

const useNavigationTabs = () => {
const { pathname } = useLocation();
const navigate = useNavigate();

const navigateReviewLinkManagementPage = () => {
navigate(`/${ROUTE.reviewLinks}`);
};

const navigateWrittenReviewConfirmPage = () => {
navigate(`/${ROUTE.writtenReview}`);
};

const tabList = [
{
label: '리뷰 링크 관리',
path: `/${ROUTE.reviewLinks}`,
handleTabClick: navigateReviewLinkManagementPage,
Copy link
Contributor

Choose a reason for hiding this comment

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

tabList의 요소안에 path가 있으니, tabList 사용시 onClick={()=> navigate(tab.path)}로 해도 될 것 같은데, tabList에 handleTabClick으로 선언한 이유가 있을까요?

Copy link
Contributor

Choose a reason for hiding this comment

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

저도 같은 의문이 있었는데, navigate 코드에 더해 페이지 방문 amplitude 코드를 추가한다고 생각하면 nav 함수를 따로 파도 괜찮을 것 같아요. 지금 useReviewDisplayLayoutOptions도 이런 식으로 되어 있습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tabList의 요소안에 path가 있으니, tabList 사용시 onClick={()=> navigate(tab.path)}로 해도 될 것 같은데, tabList에 handleTabClick으로 선언한 이유가 있을까요?

amplitude 코드 추가될 것 같아서 함수 따로 팠어요~

지금 useReviewDisplayLayoutOptions도 이런 식으로 되어 있습니다!

맞아요. useReviewDisplayLayoutOptions 참고했어요!

},
{
label: '작성한 리뷰 확인',
path: `/${ROUTE.writtenReview}`,
handleTabClick: navigateWrittenReviewConfirmPage,
},
];

const currentTabIndex = tabList.findIndex((tab) => tab.path === pathname);

return { currentTabIndex, tabList };
};

export default useNavigationTabs;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ReviewCard from '@/components/common/ReviewCard';
import { URLGeneratorForm } from '@/pages/HomePage/components';

import ReviewLinkLayout from '../layouts/ReviewLinkLayout';
Expand All @@ -16,8 +17,16 @@ const ReviewLinkDashboard = () => {
title="생성한 리뷰 링크를 확인해보세요"
subTitle="클릭하면 해당 프로젝트의 리뷰 목록으로 이동해요"
>
{/* TODO: ReviewCard 컴포넌트 추가 및 생성한 리뷰 링크가 없을 경우, 돋보기 컴포넌트 추가 */}
<></>
{/* TODO: 생성한 리뷰 링크가 없을 경우, 돋보기 컴포넌트 추가 */}
<ReviewCard
createdAt="2024-01-15"
contentPreview="임시용"
categories={[
{ optionId: 2, content: '💡 문제 해결 능력' },
{ optionId: 1, content: '🗣️ 커뮤니케이션 능력' },
]}
handleClick={() => {}}
/>
</ReviewLinkLayout>
</S.LinkSection>
</S.ReviewLinkDashboardContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const ReviewLinkDashboardContainer = styled.div`
gap: 7rem;

width: 100%;
// 전체 영역에서 헤더(7rem), 푸터(6rem), 네비게이션 탭(4rem) 영역 제외
min-height: calc(100vh - 17rem);

${media.medium} {
gap: 4rem;
Expand All @@ -19,6 +21,7 @@ export const ReviewLinkDashboardContainer = styled.div`

${media.small} {
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
`;
Expand All @@ -44,8 +47,6 @@ export const FormSection = styled.section`

export const Separator = styled.div`
width: 0.1rem;
// 전체 영역에서 헤더(7rem)와 푸터(6rem) 영역 제외하고, 추후 네비게이션 탭이 추가되면 해당 영역도 제외
min-height: calc(100vh - 13rem);

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

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/pages/ReviewLinkPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ErrorSuspenseContainer } from '@/components';
import NavigationTab from '@/components/common/NavigationTab';

import ReviewLinkDashboard from './components/ReviewLinkDashboard';

const ReviewLinkPage = () => {
return (
<ErrorSuspenseContainer>
{/* TODO: 네비게이션 탭 추가 */}
<NavigationTab />
<ReviewLinkDashboard />
</ErrorSuspenseContainer>
);
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/styles/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export const scrollbarWidth = {
basic: '1.2rem',
small: '0.5rem',
};

export const breadcrumbSize = {
paddingLeft: '2rem',
};

export const confirmModalSize = {
maxWidth: '90vw',
padding: '3.2rem',
Expand Down
Loading