diff --git a/src/App.tsx b/src/App.tsx
index d35d944..9a2e2eb 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,7 @@
+import MainPage from '@pages/MainPage';
+
function App() {
- return
;
+ return ;
}
export default App;
diff --git a/src/components/base/Button/index.tsx b/src/components/base/Button/index.tsx
deleted file mode 100644
index f6bd284..0000000
--- a/src/components/base/Button/index.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Button } from '@mui/material';
-
-interface Props {
- children: string;
-}
-
-export default function CustomButton({ children }: Props) {
- return ;
-}
diff --git a/src/components/base/Card.tsx b/src/components/base/Card.tsx
new file mode 100644
index 0000000..ba2c171
--- /dev/null
+++ b/src/components/base/Card.tsx
@@ -0,0 +1,88 @@
+import {
+ Card,
+ CardContent,
+ CardMedia,
+ Chip,
+ styled,
+ Typography,
+} from '@mui/material';
+import Avatar from '@mui/material/Avatar';
+
+interface Props {
+ author: {
+ name: string;
+ imgSrc: string;
+ url: string;
+ };
+ thumbnail: string;
+ title: string;
+ description: string;
+ team: string;
+ date: string;
+}
+
+const CardInfo = styled('div')`
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ padding: 16px;
+
+ div {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 16px;
+
+ a {
+ display: lock;
+ font-size: 16px;
+ line-height: 1.43;
+ color: inherit;
+ text-decoration: none;
+ }
+
+ span:last-child {
+ display: block;
+ width: 100%;
+ font-size: 14px;
+ }
+ }
+`;
+
+const ClampTypography = styled(Typography)`
+ /* stylelint-disable-next-line value-no-vendor-prefix */
+ display: -webkit-box;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: normal;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+`;
+
+export default function CustomCard({
+ author,
+ thumbnail,
+ title,
+ description,
+ team,
+ date,
+}: Props) {
+ return (
+
+
+
+
+
+
+
+
+ {title}
+
+ {description}
+
+
+ );
+}
diff --git a/src/components/base/Card/index.tsx b/src/components/base/Card/index.tsx
deleted file mode 100644
index 52d2eef..0000000
--- a/src/components/base/Card/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Card } from '@mui/material';
-
-export default function CustomCard() {
- return ;
-}
diff --git a/src/components/base/Chip.tsx b/src/components/base/Chip.tsx
new file mode 100644
index 0000000..f97b886
--- /dev/null
+++ b/src/components/base/Chip.tsx
@@ -0,0 +1,18 @@
+import { Chip } from '@mui/material';
+
+interface Props {
+ label: string;
+ onClick: () => void;
+ variant: 'filled' | 'outlined';
+}
+
+export default function CustomChip({ label, onClick, variant }: Props) {
+ return (
+
+ );
+}
diff --git a/src/components/base/Chip/index.tsx b/src/components/base/Chip/index.tsx
deleted file mode 100644
index b4007e7..0000000
--- a/src/components/base/Chip/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Chip } from '@mui/material';
-
-interface Props {
- label: string;
- onClick: () => void;
-}
-
-export default function CustomChip({ label, onClick }: Props) {
- return ;
-}
diff --git a/src/components/base/Container.tsx b/src/components/base/Container.tsx
new file mode 100644
index 0000000..2efb7f0
--- /dev/null
+++ b/src/components/base/Container.tsx
@@ -0,0 +1,15 @@
+import { Container } from '@mui/material';
+import { ReactNode } from 'react';
+
+interface Props {
+ children: ReactNode;
+ sx?: { mb: string };
+}
+
+export default function CustomContainer({ children, sx }: Props) {
+ return {children};
+}
+
+CustomContainer.defaultProps = {
+ sx: {},
+};
diff --git a/src/components/base/LinkButton.tsx b/src/components/base/LinkButton.tsx
new file mode 100644
index 0000000..d5ac029
--- /dev/null
+++ b/src/components/base/LinkButton.tsx
@@ -0,0 +1,19 @@
+import { Button } from '@mui/material';
+
+interface Props {
+ children: string;
+}
+
+export default function CustomButton({ children }: Props) {
+ return (
+
+ );
+}
diff --git a/src/components/base/Logo/index.tsx b/src/components/base/Logo.tsx
similarity index 51%
rename from src/components/base/Logo/index.tsx
rename to src/components/base/Logo.tsx
index 5f0b088..b68b6b1 100644
--- a/src/components/base/Logo/index.tsx
+++ b/src/components/base/Logo.tsx
@@ -1,3 +1,3 @@
export default function Logo() {
- return Til-Store
;
+ return Til-Store;
}
diff --git a/src/components/base/Pagination.tsx b/src/components/base/Pagination.tsx
new file mode 100644
index 0000000..b40fc93
--- /dev/null
+++ b/src/components/base/Pagination.tsx
@@ -0,0 +1,16 @@
+import { Pagination } from '@mui/material';
+
+interface Props {
+ count: number;
+}
+
+export default function CustomPagination({ count }: Props) {
+ return (
+
+ );
+}
diff --git a/src/components/base/Pagination/index.tsx b/src/components/base/Pagination/index.tsx
deleted file mode 100644
index 789573a..0000000
--- a/src/components/base/Pagination/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Pagination } from '@mui/material';
-
-export default function CustomPagination() {
- return ;
-}
diff --git a/src/components/base/Select.tsx b/src/components/base/Select.tsx
new file mode 100644
index 0000000..045d3da
--- /dev/null
+++ b/src/components/base/Select.tsx
@@ -0,0 +1,42 @@
+import {
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ SelectChangeEvent,
+} from '@mui/material';
+import { useState } from 'react';
+
+interface Props {
+ options: number[];
+ label: string;
+ onChange: (value: string) => void;
+}
+
+export default function CustomSelect({ label, options, onChange }: Props) {
+ const [value, setValue] = useState('');
+
+ const handleChange = (e: SelectChangeEvent) => {
+ setValue(e.target.value);
+ onChange(e.target.value);
+ };
+
+ return (
+
+ {label}
+
+
+ );
+}
diff --git a/src/components/base/Select/index.tsx b/src/components/base/Select/index.tsx
deleted file mode 100644
index 762dbdf..0000000
--- a/src/components/base/Select/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Select } from '@mui/material';
-
-export default function CustomSelect() {
- return ;
-}
diff --git a/src/components/base/index.ts b/src/components/base/index.ts
index 924c1d8..dcc919d 100644
--- a/src/components/base/index.ts
+++ b/src/components/base/index.ts
@@ -1,6 +1,7 @@
-export { default as Button } from './Button';
export { default as Card } from './Card';
export { default as Chip } from './Chip';
export { default as Logo } from './Logo';
export { default as Pagination } from './Pagination';
export { default as Select } from './Select';
+export { default as Container } from './Container';
+export { default as LinkButton } from './LinkButton';
diff --git a/src/components/layouts/CardList/CardList.style.ts b/src/components/layouts/CardList/CardList.style.ts
new file mode 100644
index 0000000..d6855f6
--- /dev/null
+++ b/src/components/layouts/CardList/CardList.style.ts
@@ -0,0 +1,42 @@
+import { styled } from '@mui/material';
+
+export const HiddenTitle = styled('h2')`
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+`;
+
+export const PaginationWrapper = styled('div')`
+ display: flex;
+ justify-content: center;
+ margin-top: 40px;
+`;
+
+export const CardList = styled('ul')`
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+`;
+export const CardItem = styled('li')`
+ width: calc(25% - 12px);
+ padding: 0;
+ margin: 0;
+ @media (max-width: 1024px) {
+ width: calc(33.333% - 16px);
+ }
+ @media (max-width: 768px) {
+ width: calc(50% - 8px);
+ }
+ @media (max-width: 375px) {
+ width: 100%;
+ }
+`;
diff --git a/src/components/layouts/CardList/index.tsx b/src/components/layouts/CardList/index.tsx
new file mode 100644
index 0000000..0ce07e0
--- /dev/null
+++ b/src/components/layouts/CardList/index.tsx
@@ -0,0 +1,45 @@
+import { Card, Container, Pagination } from '@components/base';
+import * as S from './CardList.style';
+
+interface Props {
+ cards: {
+ author: {
+ name: string;
+ imgSrc: string;
+ url: string;
+ };
+ team: string;
+ date: string;
+ thumbnail: string;
+ title: string;
+ description: string;
+ }[];
+}
+
+export default function CardList({ cards }: Props) {
+ return (
+
+ TIL 목록
+
+ {cards.map(
+ ({ author, team, date, thumbnail, title, description }, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+
+
+ )
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/components/layouts/FilterChips/FilterChips.style.ts b/src/components/layouts/FilterChips/FilterChips.style.ts
new file mode 100644
index 0000000..baafe58
--- /dev/null
+++ b/src/components/layouts/FilterChips/FilterChips.style.ts
@@ -0,0 +1,18 @@
+import { styled } from '@mui/material';
+
+const ChipList = styled('ul')`
+ display: flex;
+ gap: 0.5rem;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ margin-bottom: 24px;
+ overflow-x: auto;
+ list-style: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+export default ChipList;
diff --git a/src/components/layouts/FilterChips/index.tsx b/src/components/layouts/FilterChips/index.tsx
new file mode 100644
index 0000000..0ab7299
--- /dev/null
+++ b/src/components/layouts/FilterChips/index.tsx
@@ -0,0 +1,30 @@
+import { Chip, Container } from '@components/base';
+import ChipList from './FilterChips.style';
+
+interface Props {
+ teams: { id: number; name: string }[];
+ selectedTeam: string;
+ onClickTeam: (team: string) => void;
+}
+
+export default function FilterChips({
+ teams,
+ selectedTeam,
+ onClickTeam,
+}: Props) {
+ return (
+
+
+ {teams.map(({ id, name }) => (
+
+ onClickTeam(name)}
+ />
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/layouts/FilterSelect/index.tsx b/src/components/layouts/FilterSelect/index.tsx
new file mode 100644
index 0000000..4d2ca5f
--- /dev/null
+++ b/src/components/layouts/FilterSelect/index.tsx
@@ -0,0 +1,14 @@
+import { Container, Select } from '@components/base';
+
+interface Props {
+ generations: number[];
+ onChange: (generation: string) => void;
+}
+
+export default function FilterSelect({ generations, onChange }: Props) {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/layouts/Footer/Footer.style.ts b/src/components/layouts/Footer/Footer.style.ts
new file mode 100644
index 0000000..11f9ed6
--- /dev/null
+++ b/src/components/layouts/Footer/Footer.style.ts
@@ -0,0 +1,22 @@
+import { styled } from '@mui/material';
+
+const Footer = styled('footer')`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ text-align: center;
+ background-color: #efefef;
+
+ address {
+ font-size: 0.75rem;
+ font-style: normal;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+`;
+
+export default Footer;
diff --git a/src/components/layouts/Footer/index.tsx b/src/components/layouts/Footer/index.tsx
new file mode 100644
index 0000000..82e18cb
--- /dev/null
+++ b/src/components/layouts/Footer/index.tsx
@@ -0,0 +1,20 @@
+import { Container } from '@components/base';
+import StyledFooter from './Footer.style';
+
+export default function Footer() {
+ return (
+
+
+
+
+ https://github.com/TIL-store
+
+
+
+
+ );
+}
diff --git a/src/components/layouts/Header/Header.style.ts b/src/components/layouts/Header/Header.style.ts
new file mode 100644
index 0000000..9156eeb
--- /dev/null
+++ b/src/components/layouts/Header/Header.style.ts
@@ -0,0 +1,17 @@
+import { styled } from '@mui/material';
+
+export const BetweenWrapper = styled('div')`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+export const TitleLink = styled('a')`
+ display: block;
+ font-size: 1rem;
+ ${({ theme }) => `
+ color: ${theme.palette.primary.main};
+ `}
+
+ text-decoration: none;
+`;
diff --git a/src/components/layouts/Header/index.tsx b/src/components/layouts/Header/index.tsx
index a980254..5de1292 100644
--- a/src/components/layouts/Header/index.tsx
+++ b/src/components/layouts/Header/index.tsx
@@ -1,3 +1,19 @@
+import { LinkButton, Container, Logo } from '@components/base';
+import * as S from './Header.style';
+
export default function Header() {
- return ;
+ return (
+
+
+
+
+
+
+
+
+ 관리자 페이지로 이동
+
+
+
+ );
}
diff --git a/src/components/layouts/PageContainer/index.tsx b/src/components/layouts/PageContainer/index.tsx
new file mode 100644
index 0000000..4f0d2eb
--- /dev/null
+++ b/src/components/layouts/PageContainer/index.tsx
@@ -0,0 +1,26 @@
+import { styled } from '@mui/material';
+
+const PageContainer = styled('div')`
+ position: relative;
+ min-height: 100vh;
+
+ & main {
+ margin-bottom: 80px;
+ }
+
+ & footer {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+
+ ${({ theme }) => `
+ padding-bottom: ${theme.dimensions.footerHeight}px;
+
+ footer {
+ height: ${theme.dimensions.footerHeight}px;
+ }
+ `}
+`;
+
+export default PageContainer;
diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts
index 39563b0..ce0e65e 100644
--- a/src/mocks/handlers.ts
+++ b/src/mocks/handlers.ts
@@ -1,21 +1,23 @@
import { rest } from 'msw';
const handlers = [
- rest.get('/movies', (req, res, ctx) => {
+ rest.get('/teams', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
data: [
- {
- id: 1,
- title: 'Spider Man',
- rate: 4.5,
- },
- {
- id: 2,
- title: 'Kings Man',
- rate: 4.5,
- },
+ { id: 1, name: '동영' },
+ { id: 2, name: '기동' },
+ { id: 3, name: '달리' },
+ { id: 4, name: '동근' },
+ { id: 5, name: '나영' },
+ { id: 6, name: '요한' },
+ { id: 7, name: '찬희' },
+ { id: 8, name: '화랑' },
+ { id: 9, name: '지은' },
+ { id: 10, name: '재호' },
+ { id: 11, name: '루카스' },
+ { id: 12, name: '오프' },
],
})
);
diff --git a/src/pages/MainPage/index.tsx b/src/pages/MainPage/index.tsx
new file mode 100644
index 0000000..ea9d492
--- /dev/null
+++ b/src/pages/MainPage/index.tsx
@@ -0,0 +1,133 @@
+import Header from '@components/layouts/Header';
+import CardList from '@components/layouts/CardList';
+import Footer from '@components/layouts/Footer';
+import PageContainer from '@components/layouts/PageContainer';
+import FilterChips from '@components/layouts/FilterChips';
+import { useState } from 'react';
+import FilterSelect from '@components/layouts/FilterSelect';
+
+const teams = [
+ { id: 1, name: '동영' },
+ { id: 2, name: '기동' },
+ { id: 3, name: '달리' },
+ { id: 4, name: '동근' },
+ { id: 5, name: '나영' },
+ { id: 6, name: '요한' },
+ { id: 7, name: '찬희' },
+ { id: 8, name: '화랑' },
+ { id: 9, name: '지은' },
+ { id: 10, name: '재호' },
+ { id: 11, name: '루카스' },
+ { id: 12, name: '오프' },
+];
+
+const generations = [1, 2];
+
+const tils = [
+ {
+ author: {
+ name: 'rekong',
+ imgSrc: '',
+ },
+ team: '동영',
+ date: '2022-04-27',
+ thumbnail: 'image',
+ title: '글의 제목 입니다.',
+ description:
+ '간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다',
+ },
+ {
+ author: {
+ name: 'rekong',
+ imgSrc: '',
+ },
+ team: '동영',
+ date: '2022-04-27',
+ thumbnail: 'image',
+ title: '글의 제목 입니다.',
+ description:
+ '간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다',
+ },
+ {
+ author: {
+ name: 'rekong',
+ imgSrc: '',
+ },
+ team: '동영',
+ date: '2022-04-27',
+ thumbnail: 'image',
+ title: '글의 제목 입니다.',
+ description:
+ '간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다',
+ },
+ {
+ author: {
+ name: 'rekong',
+ imgSrc: '',
+ },
+ team: '동영',
+ date: '2022-04-27',
+ thumbnail: 'image',
+ title: '글의 제목 입니다.',
+ description:
+ '간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다',
+ },
+ {
+ author: {
+ name: 'rekong',
+ imgSrc: '',
+ },
+ team: '동영',
+ date: '2022-04-27',
+ thumbnail: 'image',
+ title: '글의 제목 입니다.',
+ description:
+ '간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다',
+ },
+ {
+ author: {
+ name: 'rekong',
+ imgSrc: '',
+ },
+ team: '동영',
+ date: '2022-04-27',
+ thumbnail: 'image',
+ title: '글의 제목 입니다.',
+ description:
+ '간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다간단 요약입니다 간단 요약입니다 간단 요약입니다',
+ },
+];
+
+export default function MainPage() {
+ const [selectedTeam, setSelectedTeam] = useState(teams[0].name);
+
+ const handleClickTeam = (name: string) => {
+ setSelectedTeam(name);
+ // TODO : 팀별 TIL card 불러오기
+ };
+
+ const handleChangeGeneration = (generation: string) => {
+ console.log('Change', generation);
+ // TODO : 기수별 TIL card 불러오기
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/theme.ts b/src/theme.ts
index 2f02679..c307353 100644
--- a/src/theme.ts
+++ b/src/theme.ts
@@ -1,5 +1,18 @@
import { createTheme } from '@mui/material/styles';
+declare module '@mui/material/styles' {
+ interface Theme {
+ dimensions: {
+ footerHeight: number;
+ };
+ }
+ interface ThemeOptions {
+ dimensions?: {
+ footerHeight?: number;
+ };
+ }
+}
+
const theme = createTheme({
palette: {
mode: 'light',
@@ -15,6 +28,9 @@ const theme = createTheme({
typography: {
fontFamily: 'Noto Sans KR, sans-serif',
},
+ dimensions: {
+ footerHeight: 70,
+ },
});
export default theme;
diff --git a/tsconfig.json b/tsconfig.json
index 029bf1d..f65e844 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,6 +22,11 @@
"@pages/*": ["src/pages/*"]
}
},
- "include": ["**/*.ts", "**/*.tsx", ".eslintrc.json"],
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ ".eslintrc.json",
+ "node_modules/@types/**/*.d.ts"
+ ],
"references": [{ "path": "./tsconfig.node.json" }]
}