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] Route Nav 컴포넌트 구현 및 레이아웃 반영 #294

Merged
merged 8 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@tanstack/react-query": "^5.49.2",
"@tanstack/react-query-devtools": "^5.49.2",
"axios": "^1.7.2",
"framer-motion": "^11.11.9",
Copy link
Member

Choose a reason for hiding this comment

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

오호 애니메이션 관련 라이브러리인가보네요 !

Copy link
Contributor

Choose a reason for hiding this comment

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

처음 보는 친군데 이쁘네요!

"mime": "^4.0.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
24 changes: 24 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 11 additions & 17 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css } from '@emotion/react';
import * as Sentry from '@sentry/react';

import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { Outlet, useNavigate } from 'react-router-dom';

import { useQueryErrorResetBoundary } from '@tanstack/react-query';

Expand All @@ -21,11 +21,6 @@ const App = () => {

const { reset } = useQueryErrorResetBoundary();

const { pathname } = useLocation();

/** 아카이빙 페이지 DocumentBar를 위한 라우트별 동적 패딩 */
const isArchivingPage = pathname === '/archiving';

Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [Sentry.browserTracingIntegration()],
Expand Down Expand Up @@ -55,7 +50,7 @@ const App = () => {
<Login>
<ModalContainer />
<SNB />
<main css={layoutStyle(isArchivingPage)}>
<main css={layoutStyle}>
<Header />
<Outlet />
</main>
Expand All @@ -64,19 +59,18 @@ const App = () => {
);
};

const layoutStyle = (flag: boolean) =>
css({
display: 'flex',
flexDirection: 'column',
const layoutStyle = css({
display: 'flex',
flexDirection: 'column',

height: '100%',
width: 'calc(100% - 7.6rem)',
height: '100%',
width: 'calc(100% - 7.6rem)',

padding: flag ? '0' : '2rem 3.4rem 4.8rem 3.2rem',
padding: '2rem 3.4rem 4.8rem 3.2rem',

marginLeft: '7.6rem',
marginLeft: '7.6rem',

overflow: 'hidden',
});
overflow: 'hidden',
});

export default App;
3 changes: 3 additions & 0 deletions src/common/asset/svg/ic_folder_copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/common/asset/svg/ic_handover_empty.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/common/asset/svg/ic_nav_timeline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/common/router/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ const router = createBrowserRouter([
</Suspense>
),
},
{
path: PATH.HANDOVER,
element: (
<Suspense>
<h1>HandOver</h1>
</Suspense>
),
},
],
},
]);
Expand Down
11 changes: 3 additions & 8 deletions src/page/archiving/index/ArchivingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Suspense } from 'react';
import { useLocation } from 'react-router-dom';

import Button from '@/common/component/Button/Button';
import Flex from '@/common/component/Flex/Flex';
Expand All @@ -16,19 +15,15 @@ import ContentBox from '@/shared/component/ContentBox/ContentBox';
import { useOpenModal } from '@/shared/store/modal';

const ArchivingPage = () => {
const location = useLocation();

const sideBarRef = useOutsideClick(() => setSelectedBlock(undefined));

const { selectedBlock, setSelectedBlock, handleBlockClick } = useInteractTimeline();

const openModal = useOpenModal();

const teamId = new URLSearchParams(location.search).get('teamId');

if (!teamId) throw new Error('has no teamId');
const teamId = localStorage.getItem('teamId');
Copy link
Contributor

Choose a reason for hiding this comment

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

이제 localStorage를 사용하는군요!


const { ref, currentYear, currentMonth, handlePrevMonth, handleNextMonth, handleToday, endDay } = useDate(teamId);
const { ref, currentYear, currentMonth, handlePrevMonth, handleNextMonth, handleToday, endDay } = useDate(teamId!);

const handleOpenBlockModal = () => {
openModal('create-block');
Expand All @@ -40,7 +35,7 @@ const ArchivingPage = () => {
variant="timeline"
title="타임라인"
headerOption={
<Button variant="secondary" size="small" onClick={handleOpenBlockModal}>
<Button variant="secondary" onClick={handleOpenBlockModal}>
타임블록 추가
</Button>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { theme } from '@/common/style/theme/theme';

export const containerStyle = (blockSelected: string) =>
css({
position: 'relative',
position: 'absolute',
right: 0,

zIndex: theme.zIndex.overlayMiddle,

Expand Down
5 changes: 2 additions & 3 deletions src/page/archiving/index/component/TimeLine/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ const TimeLine = (
{ selectedBlock, onBlockClick, currentYear, currentMonth, endDay }: TimeLineProps,
ref: ForwardedRef<HTMLDivElement>
) => {
const teamId = new URLSearchParams(location.search).get('teamId');
if (!teamId) throw new Error('has no teamId');
const teamId = localStorage.getItem('teamId');

const { data } = useGetTimeBlockQuery(+teamId, 'executive', currentYear, currentMonth);
const { data } = useGetTimeBlockQuery(+teamId!, 'executive', currentYear, currentMonth);
Copy link
Contributor

Choose a reason for hiding this comment

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

위에 Error 던지는 코드가 없어져서 요 ! 를 해준건가요?

Copy link
Contributor Author

@wuzoo wuzoo Oct 25, 2024

Choose a reason for hiding this comment

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

error라기 보다는 teamIdnull일 때의 분기처리를 안하기 위해서 !를 붙혀줬습니다. !undefined 혹은 null이 될 수 있는 값이 있을 때, 해당 변수가 null이나 undefined가 되는 것을 무시한다! 라는 의미에용


const timeBlocks: Block[] = data.timeBlocks;
const blockFloors = alignBlocks(timeBlocks, endDay, currentMonth, currentYear);
Expand Down
2 changes: 2 additions & 0 deletions src/shared/component/ContentBox/ContentBox.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export const headerStyle = css({
position: 'sticky',
top: 0,

height: '7.2rem',

zIndex: theme.zIndex.overlayBottom,

padding: '1.6rem 0rem',
Expand Down
4 changes: 4 additions & 0 deletions src/shared/component/Header/Header.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { css } from '@emotion/react';
import { theme } from '@/common/style/theme/theme';

export const headerStyle = css({
display: 'flex',
flexDirection: 'column',
gap: '2rem',

width: 'fit-content',

paddingBottom: '2rem',
Expand Down
17 changes: 6 additions & 11 deletions src/shared/component/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { useLocation } from 'react-router-dom';

import Heading from '@/common/component/Heading/Heading';

import { headerStyle } from '@/shared/component/Header/Header.style';
import RouteNav from '@/shared/component/RouteNav/RouteNav';

const Header = () => {
/** TODO: 추후 global State 혹은 localStorage에 저장 */
const title = 'TIKI 워크스페이스';

const { pathname } = useLocation();

const hasNoSidebar = pathname !== '/archiving';

return (
hasNoSidebar && (
<header css={headerStyle}>
<Heading tag="H1">{title}</Heading>
</header>
)
<header css={headerStyle}>
<Heading tag="H1">{title}</Heading>

<RouteNav />
</header>
);
};

Expand Down
63 changes: 63 additions & 0 deletions src/shared/component/RouteNav/RouteNav.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { css } from '@emotion/react';

import { theme } from '@/common/style/theme/theme';

export const iconFillActiveStyle = (isActive: boolean) =>
css({
'& > path': {
fill: isActive ? theme.colors.gray_800 : theme.colors.gray_500,
},
});

export const iconStrokeActiveStyle = (isActive: boolean) =>
css({
'& > path': {
stroke: isActive ? theme.colors.gray_800 : theme.colors.gray_500,
},
});

export const navListStyle = css({
display: 'flex',
alignItems: 'center',
gap: '8px',

width: 'max-content',

padding: '0.4rem',

borderRadius: '8px',
backgroundColor: theme.colors.gray_100,
});

export const itemStyle = (isActive: boolean) =>
css({
display: 'flex',
alignItems: 'center',
gap: '0.6rem',

position: 'relative',
padding: '0.6rem 0.8rem',

backgroundColor: 'transparent',

...theme.text.body08,
color: isActive ? theme.colors.black : theme.colors.gray_500,

whiteSpace: 'nowrap',

zIndex: 2,
});

export const indicatorStyle = css({
position: 'absolute',
top: -2,

width: '100%',
height: '3.2rem',

backgroundColor: theme.colors.white,

borderRadius: '4px',

zIndex: 1,
});
54 changes: 54 additions & 0 deletions src/shared/component/RouteNav/RouteNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { motion } from 'framer-motion';

import { Link, useLocation } from 'react-router-dom';

import IcFolder from '@/common/asset/svg/ic_folder_copy.svg?react';
import IcHandOver from '@/common/asset/svg/ic_handover_empty.svg?react';
import IcTimeLine from '@/common/asset/svg/ic_nav_timeline.svg?react';

import {
iconFillActiveStyle,
iconStrokeActiveStyle,
indicatorStyle,
itemStyle,
navListStyle,
} from '@/shared/component/RouteNav/RouteNav.style';
import { PATH } from '@/shared/constant/path';

const RouteNav = () => {
const { pathname } = useLocation();

const isDrivePage = pathname === PATH.DRIVE;
const isArchivingPage = pathname === PATH.ARCHIVING;
const isHandoverPage = pathname === PATH.HANDOVER;

return (
<nav>
<ul css={navListStyle}>
<li css={{ position: 'relative' }}>
<Link css={itemStyle(isDrivePage)} to={PATH.DRIVE}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Link 컴포넌트를 클릭하면 to 에 지정된 경로로 이동한다는 것은 알겠습니다.
그런데 이동하고 랜더링 되는 컴포넌트의 위치는 어디서 잡는거죠?
찾아본 바로는 Route 컴포넌트가 위치한 곳으로 잡힌다고 했는데 Route 컴포넌트가 안보여서 질문드립니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Link 태그는 react-router-dom에서 제공하는 요소로, 말씀하신대로 지정한 router 내에 라우트 이동을 담당합니다 ! Route 컴포넌트로 라우트를 정의하기도 하는데, 저희는 createBrowserRouter로 객체 형식으로 라우터를 생성했어요 ! 만약 규홍님이 말씀하신대로 구현한다면 아래와 같아요 !

// router.tsx
return (
  <BrowserRouter>
    <Route path="..." element={<Home />} />
  </BrowserRouter>
)

리액트 라우터에서 제공하는 브라우저라우터라는 태그 하위에 각각의 라우트 요소로 pathelement를 정의해주면 됩니다 ! 거의 동일한 방법이긴 하나, 저는 createBrowserRouter를 통해 객체 형식으로 선언하는 것을 선호해서 저렇게 한거에용

<IcFolder css={iconFillActiveStyle(isDrivePage)} width={16} height={16} />
파일
</Link>
{isDrivePage && <motion.div layoutId="nav_indicator" css={indicatorStyle} />}
</li>
<li css={{ position: 'relative' }}>
<Link css={itemStyle(isArchivingPage)} to={PATH.ARCHIVING}>
<IcTimeLine css={iconStrokeActiveStyle(isArchivingPage)} width={16} height={16} />
타임라인
</Link>
{isArchivingPage && <motion.div layoutId="nav_indicator" css={indicatorStyle} />}
</li>
<li css={{ position: 'relative' }}>
<Link css={itemStyle(isHandoverPage)} to={PATH.HANDOVER}>
<IcHandOver css={iconFillActiveStyle(isHandoverPage)} width={16} height={16} />
인수인계 노트
</Link>
{isHandoverPage && <motion.div layoutId="nav_indicator" css={indicatorStyle} />}
</li>
</ul>
</nav>
);
};

export default RouteNav;
Loading
Loading