Skip to content

Commit

Permalink
Merge pull request #141 from choco-team/feat/137-studentInfo
Browse files Browse the repository at this point in the history
[FE] 가로 스크롤 탭 UI 추가
  • Loading branch information
nlom0218 authored Nov 14, 2023
2 parents e9dd2fb + 27ad158 commit 8b80dfa
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 57 deletions.
1 change: 1 addition & 0 deletions src/components/Button/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const Button = styled.button<StyledButton>`
text-align: center;
font-size: ${(props) => props.fontSize};
font-weight: ${(props) => props.fontWeight};
word-break: keep-all;
transition:
background-color 0.3s ease,
border-color 0.3s ease;
Expand Down
68 changes: 68 additions & 0 deletions src/components/Tab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useEffect, useRef, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6';

import * as S from './style';

const Tab = ({ children }: React.PropsWithChildren) => {
const [hasScroll, setHasScroll] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);

const handleScrollTab = (scrollTo: 'left' | 'right') => {
if (!scrollRef.current) return;
const { clientWidth } = scrollRef.current;

scrollRef.current.scrollBy({
top: 0,
left: ((scrollTo === 'left' ? -1 : 1) * clientWidth) / 2,
behavior: 'smooth',
});
};

useEffect(() => {
if (!scrollRef.current) return;

const observer = new ResizeObserver((entries) => {
const element = entries[0];
const {
contentRect: { width },
target: { scrollWidth },
} = element;

setHasScroll(width < scrollWidth);
});

observer.observe(scrollRef.current);

return () => {
observer.disconnect();
};
}, []);

return (
<S.TabContainer>
{hasScroll && (
<S.ScrollTabButton
concept="text"
onClick={() => handleScrollTab('left')}
>
<FaChevronLeft />
</S.ScrollTabButton>
)}
<S.ScrollBox ref={scrollRef}>
<S.ListContainer>{children}</S.ListContainer>
</S.ScrollBox>
{hasScroll && (
<S.ScrollTabButton
concept="text"
onClick={() => handleScrollTab('right')}
>
<FaChevronRight />
</S.ScrollTabButton>
)}
</S.TabContainer>
);
};

Tab.Button = S.ListButton;

export default Tab;
66 changes: 66 additions & 0 deletions src/components/Tab/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import styled from 'styled-components';

import Button from '@Components/Button';

import { flexCustom } from '@Styles/common';

export const TabContainer = styled.div`
${flexCustom('row', 'flex-start', 'center', '8px')}
width: 100%;
`;

export const ScrollTabButton = styled(Button)`
${flexCustom('row', 'center', 'center')}
margin-top: 0.25rem;
width: 2rem;
min-width: 2rem;
height: 2rem;
padding: 0;
border-radius: 50%;
`;

export const ScrollBox = styled.div`
width: 100%;
padding-bottom: 5px;
margin-bottom: 8px;
overflow-x: auto;
overflow-y: hidden;
&::-webkit-scrollbar {
height: 3px;
}
&::-webkit-scrollbar-thumb {
background-color: ${({ theme }) => theme.border.gray};
border-radius: 10px;
}
`;

export const ListContainer = styled.div`
display: flex;
column-gap: 0.5rem;
width: max-content;
`;

export const ListButton = styled(Button)<{
$isSelected: boolean;
}>`
${flexCustom('row', 'center', 'center', '0.25rem')}
border: none;
background-color: ${({ theme, $isSelected }) =>
$isSelected ? theme.selectionBackground.primary : 'transparent'};
color: ${({ theme, $isSelected }) =>
$isSelected ? theme.accentText : theme.subText};
font-weight: ${({ $isSelected }) => ($isSelected ? 600 : 400)};
line-height: var(--title-button-line-height);
transition: background-color 0.2s ease;
&:hover {
background-color: ${({ theme }) => theme.selectionBackground.primary};
}
svg {
color: ${({ theme }) => theme.color.warning[400]};
}
`;
54 changes: 24 additions & 30 deletions src/pages/StudentManagement/StudentInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { STUDENT_INFO_KEY_NAME } from '@Constant/studentManagement/studentInfo';
import { type ColDef, type GridOptions } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import { MouseEvent, useRef, useState } from 'react';
import { FaChevronDown, FaStar } from 'react-icons/fa6';
import { FaStar } from 'react-icons/fa6';

import AgGrid from '@Components/AgGrid';
import Tab from '@Components/Tab';

import { MOCK_STUDENT_LISTS } from './mock';
import * as S from './style';
Expand All @@ -29,6 +30,12 @@ const defaultColumnDefs: ColDef[] = [
flex: 0.5,
cellStyle: { textAlign: 'center' },
},
{
headerName: '알러지',
field: 'allergy',
flex: 0.5,
cellStyle: { textAlign: 'center' },
},
];

const StudentInfo = () => {
Expand All @@ -38,7 +45,6 @@ const StudentInfo = () => {
const [selectedStudentList, setSelectedStudentList] = useState(
studentListItems[0],
);
const [isOpenAccordion, setIsOpenAccordion] = useState(false);

const gridRef = useRef<AgGridReact>(null);
const rowData = selectedStudentList.students.map(
Expand Down Expand Up @@ -69,36 +75,24 @@ const StudentInfo = () => {
if (selectedItem) setSelectedStudentList(selectedItem);
};

const handleToggleAccordion = () => {
setIsOpenAccordion((prev) => !prev);
};

return (
<S.Layout>
<S.AccordionContainer>
<S.StudentListTitleContainer $isOpenAccordion={isOpenAccordion}>
{studentListItems.map(({ id, name, isMain }) => (
<S.StudentListTitleButton
key={id}
size="sm"
concept="text"
$isSelected={selectedStudentList.id === id}
value={id}
onClick={handleClickStudentListItem}
>
{isMain && <FaStar />}
{name}
</S.StudentListTitleButton>
))}
</S.StudentListTitleContainer>
<S.AccordionToggleButton
concept="text"
$isOpenAccordion={isOpenAccordion}
onClick={handleToggleAccordion}
>
<FaChevronDown />
</S.AccordionToggleButton>
</S.AccordionContainer>
<Tab>
{studentListItems.map(({ id, name, isMain }) => (
<Tab.Button
key={id}
size="sm"
concept="text"
$isSelected={selectedStudentList.id === id}
value={id}
onClick={handleClickStudentListItem}
>
{isMain && <FaStar />}
{name}
</Tab.Button>
))}
</Tab>

<AgGrid
ref={gridRef}
gridOptions={gridOptions}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/StudentManagement/StudentInfo/mock.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const MOCK_STUDENT_LISTS = [
{
id: 1,
name: '3월 6학년 1반 명렬표',
name: '3월 6학년 1반 명렬표명렬표명렬표명렬표명렬표',
isMain: false,
createdAt: '2023-10-02T11:51:26.815Z',
updatedAt: '2023-10-02T11:51:26.815Z',
Expand Down
52 changes: 28 additions & 24 deletions src/pages/StudentManagement/StudentInfo/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,42 @@ export const ColumnContainer = styled.div<{
height: ${({ height }) => height};
`;

export const AccordionContainer = styled.div`
${flexCustom('row', 'flex-start', 'flex-start', '0.5rem')}
margin-bottom: 0.5rem;
export const TabContainer = styled.div`
${flexCustom('row', 'flex-start', 'center', '8px')}
width: 100%;
`;

export const AccordionToggleButton = styled(Button)<{
$isOpenAccordion: boolean;
}>`
export const ScrollTabButton = styled(Button)`
${flexCustom('row', 'center', 'center')}
width: 2.4rem;
min-width: 2.4rem;
height: 2.4rem;
margin-top: 0.25rem;
width: 2rem;
min-width: 2rem;
height: 2rem;
padding: 0;
border-radius: 50%;
transform: ${({ $isOpenAccordion }) => $isOpenAccordion && 'rotate(-180deg)'};
-webkit-transition: transform 0.3s ease;
transition: transform 0.3s ease;
`;

export const StudentListTitleContainer = styled.div<{
$isOpenAccordion: boolean;
}>`
--title-button-line-height: 1.8rem;
export const ScrollBox = styled.div`
width: 100%;
padding-bottom: 5px;
margin-bottom: 8px;
overflow-x: auto;
overflow-y: hidden;
&::-webkit-scrollbar {
height: 3px;
}
&::-webkit-scrollbar-thumb {
background-color: ${({ theme }) => theme.border.gray};
border-radius: 10px;
}
`;

${flexCustom('row', 'flex-start', 'flex-start', '1rem')}
row-gap: 0.5rem;
flex-wrap: wrap;
height: ${({ $isOpenAccordion }) =>
$isOpenAccordion
? 'auto'
: 'calc(var(--title-button-line-height) + 0.4rem * 2)'};
export const StudentListTitleContainer = styled.div`
display: flex;
column-gap: 0.5rem;
width: max-content;
`;

export const StudentListTitleButton = styled(Button)<{
Expand Down
2 changes: 1 addition & 1 deletion src/pages/StudentManagement/StudentRegister/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ const StudentRegister = () => {
</S.ColumnContainer>

<S.ColumnContainer gap="8px">
<S.RowContainer gap="8px">
<S.RowContainer alignItems="stretch" gap="8px">
{[
{ tabValue: CREATE_TYPE.xlsx, tabName: '엑셀 파일 업로드' },
{ tabValue: CREATE_TYPE.manual, tabName: '직접 입력' },
Expand Down
1 change: 1 addition & 0 deletions src/pages/StudentManagement/StudentRegister/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const TabButton = styled(Button)<{ isOnFocus: boolean }>`
border-bottom: 2px solid transparent;
border-color: ${({ isOnFocus, theme }) =>
isOnFocus ? theme.border.primary : 'transparent'};
font-size: 1.6rem;
&:hover,
&:active {
Expand Down
2 changes: 1 addition & 1 deletion src/styles/darkTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const darkTheme = {
...theme,
text: theme.color.gray[100],
primaryText: theme.color.gray[800],
subText: theme.color.gray[600],
subText: theme.color.gray[400],
grayText: theme.color.gray[500],
accentText: theme.color.primary[500],
pageBackground: theme.color.gray[900],
Expand Down

0 comments on commit 8b80dfa

Please sign in to comment.