-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #142 from KNU-HAEDAL/Design/issue-#141
탭 컴포넌트 디자인 변경 #141
- Loading branch information
Showing
8 changed files
with
202 additions
and
269 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { | ||
cloneElement, | ||
forwardRef, | ||
ReactElement, | ||
useEffect, | ||
useRef, | ||
useState, | ||
} from 'react'; | ||
|
||
import * as S from './styles'; | ||
|
||
type TabsProps = { | ||
selectedTab: number; | ||
onChange: (value: number) => void; | ||
children: ReactElement[]; | ||
}; | ||
|
||
export const Tabs = ({ selectedTab, onChange, children }: TabsProps) => { | ||
const containerRef = useRef<HTMLDivElement>(null); | ||
const tabRefs = useRef<(HTMLDivElement | null)[]>([]); // Tab refs 배열 | ||
const [tabWidths, setTabWidths] = useState<number[]>([]); // 각 Tab 너비 저장 | ||
|
||
// 각 Tab의 너비를 계산하여 상태로 저장 | ||
useEffect(() => { | ||
if (tabRefs.current.length > 0) { | ||
const widths = tabRefs.current.map( | ||
(ref) => ref?.getBoundingClientRect().width || 0 | ||
); | ||
setTabWidths(widths); | ||
} | ||
}, [children]); | ||
|
||
const sliderWidth = tabWidths[selectedTab] - 4 || 0; // 여백 4px 빼기 | ||
|
||
const tabs = children.map((child, index) => { | ||
const handleClick = () => { | ||
onChange(child.props.value); | ||
}; | ||
|
||
return cloneElement(child, { | ||
key: child.props.value, | ||
active: child.props.value === selectedTab, | ||
onClick: handleClick, | ||
ref: (el: HTMLDivElement) => (tabRefs.current[index] = el), // Tab 요소에 ref 연결 | ||
}); | ||
}); | ||
|
||
return ( | ||
<S.StyledTabs ref={containerRef}> | ||
{tabs} | ||
<S.TabSlider width={sliderWidth} index={selectedTab} /> | ||
</S.StyledTabs> | ||
); | ||
}; | ||
|
||
type TabProps = { | ||
label: string; | ||
value: number; | ||
active?: boolean; | ||
onClick?: () => void; | ||
}; | ||
|
||
export const Tab = forwardRef<HTMLDivElement, TabProps>( | ||
({ label, active, onClick }, ref) => { | ||
return ( | ||
<S.StyledTab active={active} onClick={onClick} ref={ref}> | ||
{label} | ||
</S.StyledTab> | ||
); | ||
} | ||
); | ||
|
||
// displayName 설정으로 경고 해결 | ||
Tab.displayName = 'Tab'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { css } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
|
||
export const StyledTabs = styled.div<{ | ||
position?: string; | ||
}>` | ||
display: flex; | ||
justify-content: space-between; /* Tab들이 좌우로 정렬되도록 설정 */ | ||
position: relative; | ||
align-self: center; | ||
height: 46px; | ||
margin: 0 16px; | ||
padding: 4px 0; | ||
border-radius: 10px; | ||
background-color: var(--color-green-06); | ||
`; | ||
|
||
// 선택된 탭 | ||
export const TabSlider = styled.div<{ | ||
width: number; | ||
index: number; | ||
}>` | ||
position: absolute; | ||
top: 4px; | ||
left: 4px; | ||
width: ${({ width }) => `${width}px`}; | ||
height: 38px; | ||
background-color: var(--color-white); | ||
border-radius: 10px; | ||
/* 슬라이딩 애니메이션 */ | ||
transition: 0.2s; | ||
transform: ${({ width, index }) => `translateX(${width * index}px)`}; | ||
`; | ||
|
||
export const StyledTab = styled.div<{ | ||
active?: boolean; | ||
onClick?: () => void; | ||
inactiveStyle?: React.CSSProperties; | ||
}>` | ||
z-index: 1; | ||
width: 50%; /* 각 Tab의 너비를 50%로 설정하여 두 개의 Tab이 꽉 차도록 설정 */ | ||
height: 100%; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
background-color: transparent; | ||
border: none; | ||
font-size: var(--font-size-md); | ||
color: var(--color-grey-02); | ||
cursor: pointer; | ||
${(p) => | ||
p.active && | ||
css` | ||
color: var(--color-green-01); | ||
font-weight: 600; | ||
`} | ||
`; | ||
|
||
export const StyledTabPanels = styled.div` | ||
height: 100%; | ||
width: 100%; | ||
position: relative; | ||
text-align: center; | ||
`; | ||
|
||
export const StyledTabPanel = styled.div<{ | ||
active: boolean; | ||
}>` | ||
display: ${(p) => (p.active ? 'flex' : 'none')}; | ||
font-size: 2rem; | ||
flex-direction: column; | ||
width: 100%; | ||
height: 100%; | ||
justify-content: center; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { ReactElement } from 'react'; | ||
|
||
import * as S from './styles'; | ||
|
||
type TapPanelsProps = { | ||
children: ReactElement[]; | ||
}; | ||
|
||
export const TabPanels = ({ children }: TapPanelsProps) => { | ||
return <S.StyledTabPanels>{children}</S.StyledTabPanels>; | ||
}; | ||
|
||
type TabPanelProps = { | ||
children?: ReactElement; | ||
value: number; | ||
selectedIndex: number; | ||
}; | ||
|
||
export const TabPanel = ({ children, value, selectedIndex }: TabPanelProps) => { | ||
const hidden: boolean = value !== selectedIndex; | ||
|
||
return ( | ||
<S.StyledTabPanel hidden={hidden} active={!hidden}> | ||
{children} | ||
</S.StyledTabPanel> | ||
); | ||
}; |
Oops, something went wrong.