-
Notifications
You must be signed in to change notification settings - Fork 1
style: 커뮤니티 navbar 위치 및 스타일 변경 #52
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,27 @@ | ||
| import AppLayout from '@/components/layout/AppLayout'; | ||
| import CommunityNavigator from '@/pages/Community/_components/CommunityNavigator'; | ||
| import type { NavKey } from '@/types/community'; | ||
| import styled from '@emotion/styled'; | ||
|
|
||
| type BaseProps = React.ComponentProps<typeof AppLayout>; | ||
| type Props = BaseProps & { activeNav?: NavKey }; | ||
|
|
||
| const TAB_BAR_HEIGHT = 60; | ||
| type Props = BaseProps; | ||
|
|
||
| export default function CommunityAppLayout({ children, ...rest }: Props) { | ||
| return ( | ||
| <> | ||
| <ScrollContainer> | ||
| <InnerScroll paddingBottom={TAB_BAR_HEIGHT}> | ||
| <AppLayout {...rest}>{children}</AppLayout> | ||
| </InnerScroll> | ||
| </ScrollContainer> | ||
| <FixedNav> | ||
| <ScrollContainer> | ||
| <AppLayout {...rest}> | ||
| <CommunityNavigator /> | ||
| </FixedNav> | ||
| </> | ||
| <Content>{children}</Content> | ||
| </AppLayout> | ||
| </ScrollContainer> | ||
| ); | ||
| } | ||
|
|
||
| const ScrollContainer = styled.div` | ||
| min-height: 100vh; | ||
| /* iOS 부드러운 스크롤 */ | ||
| -webkit-overflow-scrolling: touch; | ||
| `; | ||
|
|
||
| const InnerScroll = styled.div<{ paddingBottom: number }>` | ||
| const Content = styled.div` | ||
| overflow-y: auto; | ||
| /* AppLayout이 내부에서 height를 쓰더라도 여기가 스크롤을 맡습니다 */ | ||
| max-height: 100vh; | ||
| padding-bottom: ${({ paddingBottom }) => `${paddingBottom}px`}; | ||
| `; | ||
|
|
||
| const FixedNav = styled.div` | ||
| position: fixed; | ||
| left: 0; | ||
| right: 0; | ||
| bottom: 0; | ||
| z-index: 50; /* 컨텐츠 위에 오도록 */ | ||
| max-height: 100%; | ||
| `; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,13 +3,13 @@ import { useNavigate } from 'react-router-dom'; | |
| import { useCommunityStore } from '@/stores/communityStore'; | ||
|
|
||
| const PATH_BY_KEY = { | ||
| home: '/community', | ||
| photo: '/community/feed/proof', | ||
| route: '/community/feed/share', | ||
| run: '/community/feed/mate', | ||
| photo: '/community/feed/proof', | ||
| all: '/community', | ||
| } as const; | ||
|
|
||
| type NavKey = keyof typeof PATH_BY_KEY; | ||
| export type NavKey = keyof typeof PATH_BY_KEY; | ||
|
|
||
| export default function CommunityNavigator() { | ||
| const navigate = useNavigate(); | ||
|
|
@@ -22,71 +22,69 @@ export default function CommunityNavigator() { | |
| }; | ||
|
|
||
| return ( | ||
| <Container> | ||
| <NavItem | ||
| active={active === 'home'} | ||
| aria-current={active === 'home' ? 'page' : undefined} | ||
| onClick={go('home')} | ||
| > | ||
| <i className="ri-home-4-fill" /> | ||
| <span>홈</span> | ||
| </NavItem> | ||
|
|
||
| <NavItem | ||
| active={active === 'photo'} | ||
| aria-current={active === 'photo' ? 'page' : undefined} | ||
| onClick={go('photo')} | ||
| > | ||
| <i className="ri-camera-2-fill" /> | ||
| <span>인증샷</span> | ||
| </NavItem> | ||
|
|
||
| <NavItem | ||
| <Container role="tablist" aria-label="커뮤니티 내비게이션"> | ||
| <TabButton | ||
| role="tab" | ||
| aria-selected={active === 'route'} | ||
| active={active === 'route'} | ||
| aria-current={active === 'route' ? 'page' : undefined} | ||
| onClick={go('route')} | ||
| > | ||
| <i className="ri-route-line" /> | ||
| <span>경로</span> | ||
| </NavItem> | ||
|
|
||
| <NavItem | ||
| 경로 | ||
| </TabButton> | ||
| <TabButton | ||
| role="tab" | ||
| aria-selected={active === 'run'} | ||
| active={active === 'run'} | ||
| aria-current={active === 'run' ? 'page' : undefined} | ||
| onClick={go('run')} | ||
| > | ||
| <i className="ri-run-fill" /> | ||
| <span>메이트</span> | ||
| </NavItem> | ||
| 메이트 | ||
| </TabButton> | ||
| <TabButton | ||
| role="tab" | ||
| aria-selected={active === 'photo'} | ||
| active={active === 'photo'} | ||
| onClick={go('photo')} | ||
| > | ||
| 인증샷 | ||
| </TabButton> | ||
| <TabButton | ||
| role="tab" | ||
| aria-selected={active === 'all'} | ||
| active={active === 'all'} | ||
| onClick={go('all')} | ||
| > | ||
| 전체보기 | ||
| </TabButton> | ||
| </Container> | ||
| ); | ||
| } | ||
|
|
||
| /** 상단 고정(헤더 바로 아래) */ | ||
| const Container = styled.nav` | ||
| position: fixed; | ||
| bottom: 0; | ||
| left: 0; | ||
| right: 0; | ||
| position: sticky; | ||
| top: 0; /* AppLayout 헤더가 fixed이면 필요에 따라 간격 조절 */ | ||
| z-index: ${({ theme }) => theme.zIndex.header}; | ||
| display: grid; | ||
| grid-template-columns: repeat(4, 1fr); | ||
| align-items: center; | ||
| border-top: 1px solid ${({ theme }) => theme.colors.border}; | ||
| gap: 0; | ||
| background: ${({ theme }) => theme.colors.surface}; | ||
| padding: 6px 0; | ||
| z-index: ${({ theme }) => theme.zIndex.header}; | ||
| border-bottom: 1px solid ${({ theme }) => theme.colors.border}; | ||
| `; | ||
|
|
||
| const NavItem = styled.button<{ active?: boolean }>` | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| gap: 2px; | ||
| const TabButton = styled.button<{ active?: boolean }>` | ||
| ${({ theme }) => theme.typography.small}; | ||
| height: 36px; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| background: ${({ theme, active }) => | ||
| active ? theme.colors.surfaceAlt : 'transparent'}; | ||
| color: ${({ theme, active }) => | ||
| active ? theme.colors.primary : theme.colors.subtext}; | ||
| i { | ||
| font-size: 20px; | ||
| line-height: 1; | ||
| transition: | ||
| background 0.15s ease, | ||
| color 0.15s ease, | ||
| border-color 0.15s ease; | ||
|
Comment on lines
+82
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Style Guide ReferencesFootnotes
|
||
|
|
||
| &:hover { | ||
| background: ${({ theme }) => theme.colors.surfaceAlt}; | ||
| } | ||
| `; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -28,4 +28,4 @@ export interface Comment { | |||||
| authorInfo?: AuthorObj; | ||||||
| } | ||||||
|
|
||||||
| export type NavKey = 'home' | 'photo' | 'route' | 'run'; | ||||||
| export type NavKey = 'home' | 'photo' | 'route' | 'run' | 'all'; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Props타입에서 더 이상 사용되지 않는activeNav가 제거되었어야 하지만, 이전 코드(type Props = BaseProps & { activeNav?: NavKey };)에서NavKey타입이types/community에서 import 되고 있었습니다. 현재 코드에서는NavKeyimport가 제거되었지만,CommunityNavigator내부에서NavKey를 export하고 있으므로, 이 컴포넌트의Props타입도CommunityNavigator에서 export한NavKey를 사용하는 것이 타입 안정성 측면에서 더 명확할 수 있습니다. 현재 구조 변경으로 인해 이 prop이 필요없어졌지만, 추후 비슷한 패턴을 사용할 때 참고하시면 좋을 것 같습니다.