From 2584c7700946add7039dde3a848c1b8e9c23e467 Mon Sep 17 00:00:00 2001 From: Millie <85419343+jaypedia@users.noreply.github.com> Date: Sun, 24 Apr 2022 17:32:01 +0900 Subject: [PATCH] =?UTF-8?q?[team-01][FE]=20=EA=B8=B0=ED=9A=8D=EC=A0=84=20U?= =?UTF-8?q?I=20&=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#83)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Docs: Readme Update * 팀원 소개, Tools, 그라운드룰, 브랜치 전략, 컨벤션 등에 대한 정리 * Design: Navbar, Menu, Icons 컴포넌트 레이아웃 수정 - #10 Co-authored-by: Hemudi * Feat: Navbar 마우스 이벤트 추가 - #10 * MouseEnter, MouseLeave 이벤트 추가 * SubMenuhover 효과 추가 Co-authored-by: Hemudi * Design: Global Style 에 font-family 적용 - #15 * theme의 font family 삭제 Co-authored-by: Millie * Rename: Header 컴포넌트 폴더 생성해서 그룹화 - #15 Co-authored-by: Millie * Design: 기획전 TitleBox, TabBar UI 구현 - #15 * Tab Mouse hover 밑줄 효과 구현 Co-authored-by: Millie * Fix: 기획전 Tab hover 시 사용성 개선 - #15 * 기존 border 속성을 box-shadow 로 변경 Co-authored-by: Millie * Feat: API fetch 요청하는 util 함수 구현 - #15 * GET 요청만 우선적으로 구현 Co-authored-by: Hemudi * Design: CardContainer를 Styled Components로 구현 - #15 Co-authored-by: Hemudi * Design: Card Component UI 구현 - #15 Co-authored-by: Hemudi * Feat: fetch로 API 요청, Card 동적 생성 - #15 * 임시 데이터로 UI 먼저 구현 Co-authored-by: Hemudi * Feat: Tab 클릭 시 fetch 요청 후 리렌더링 기능 구현 - #16 * fetch 요청 로직을 fetchTabData 함수로 분리 * 하드 코딩 부분은 추후 리팩토링 예정 Co-authored-by: Millie * Refactor: .gitignore 불필요한 옵션 제거 - #17 Co-authored-by: Millie * Refactor: Navbar의 div 태그 header로 변경 - #17 Co-authored-by: Millie * Refactor: menu와 tabData 데이터 분리 후 동적생성 - #18 * 하드코딩 되었던 컴포넌트를 data 분리 후 map 으로 동적 생성 Co-authored-by: Millie Co-authored-by: Hemudi Co-authored-by: Hemudi Co-authored-by: Millie --- FE/.gitignore | 15 +------ FE/src/App.js | 4 +- FE/src/Header.jsx | 44 ------------------- FE/src/Header/Header.jsx | 42 +++++++++++++++++++ FE/src/{ => Header}/Header.style.js | 15 +++++-- FE/src/Special/Special.jsx | 65 +++++++++++++++++++++++++++++ FE/src/Special/Special.style.js | 64 ++++++++++++++++++++++++++++ FE/src/components/Card.jsx | 21 ++++++++++ FE/src/components/Card.syle.js | 56 +++++++++++++++++++++++++ FE/src/data/headerMenu.js | 14 +++++++ FE/src/data/specialTab.js | 18 ++++++++ FE/src/style/global.js | 4 ++ FE/src/style/theme.js | 5 --- FE/src/utils/utils.js | 15 +++++++ 14 files changed, 314 insertions(+), 68 deletions(-) delete mode 100644 FE/src/Header.jsx create mode 100644 FE/src/Header/Header.jsx rename FE/src/{ => Header}/Header.style.js (74%) create mode 100644 FE/src/Special/Special.jsx create mode 100644 FE/src/Special/Special.style.js create mode 100644 FE/src/components/Card.jsx create mode 100644 FE/src/components/Card.syle.js create mode 100644 FE/src/data/headerMenu.js create mode 100644 FE/src/data/specialTab.js create mode 100644 FE/src/utils/utils.js diff --git a/FE/.gitignore b/FE/.gitignore index 4d29575de..a70b1e9d3 100644 --- a/FE/.gitignore +++ b/FE/.gitignore @@ -2,22 +2,9 @@ # dependencies /node_modules -/.pnp -.pnp.js - -# testing -/coverage # production /build # misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* +.DS_Store \ No newline at end of file diff --git a/FE/src/App.js b/FE/src/App.js index 99f003235..a6103f268 100644 --- a/FE/src/App.js +++ b/FE/src/App.js @@ -1,9 +1,11 @@ -import Header from './Header'; +import Header from './Header/Header'; +import Special from './Special/Special'; function App() { return (
+
); } diff --git a/FE/src/Header.jsx b/FE/src/Header.jsx deleted file mode 100644 index 128a2d59c..000000000 --- a/FE/src/Header.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { Navbar, Menu, Icons, MenuBox, MainMenu, SubMenuList, SubMenu } from './Header.style.js'; -import { ReactComponent as Logo } from './assets/logo.svg'; -import { ReactComponent as Search } from './assets/search.svg'; -import { ReactComponent as Login } from './assets/login.svg'; -import { ReactComponent as Cart } from './assets/cart.svg'; - -const Header = () => { - return ( - - - - - 든든한 메인요리 - - 육류 요리 - 해산물 요리 - - - - 뜨끈한 국물요리 - - 국/탕/찌개 - - - - 정갈한 밑반찬 - - 나물/무침 - 조림/볶음 - 절임/장아찌 - - - - - - - - - - ); -}; - -export default Header; diff --git a/FE/src/Header/Header.jsx b/FE/src/Header/Header.jsx new file mode 100644 index 000000000..7611e53c5 --- /dev/null +++ b/FE/src/Header/Header.jsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; +import { Navbar, Menu, Icons, MenuBox, MainMenu, SubMenuList, SubMenu } from './Header.style.js'; +import { ReactComponent as Logo } from '../assets/logo.svg'; +import { ReactComponent as Search } from '../assets/search.svg'; +import { ReactComponent as Login } from '../assets/login.svg'; +import { ReactComponent as Cart } from '../assets/cart.svg'; +import menuData from '../data/headerMenu.js'; + +const Header = () => { + const [isOpen, setIsOpen] = useState(false); + + const toggleSubMenu = () => { + setIsOpen(!isOpen); + }; + + return ( + + + + {menuData.map((v, i) => { + return ( + + {v.main} + + {v.sub.map((name, i) => { + return {name}; + })} + + + ); + })} + + + + + + + + ); +}; + +export default Header; diff --git a/FE/src/Header.style.js b/FE/src/Header/Header.style.js similarity index 74% rename from FE/src/Header.style.js rename to FE/src/Header/Header.style.js index 8fd5c837b..028399e5c 100644 --- a/FE/src/Header.style.js +++ b/FE/src/Header/Header.style.js @@ -1,17 +1,17 @@ import styled from 'styled-components'; -const Navbar = styled.div` +const Navbar = styled.header` display: flex; justify-content: space-between; padding: 16px 80px; width: 100%; - height: 170px; + height: auto; border-bottom: 1px solid black; `; const Menu = styled.div` display: flex; - width: 900px; + width: 75%; justify-content: flex-start; `; @@ -19,7 +19,7 @@ const Icons = styled.div` display: flex; justify-content: space-between; width: 100px; - padding: 10px 0; + padding-top: 15px; `; const MenuBox = styled.div` @@ -35,10 +35,17 @@ const MainMenu = styled.div` const SubMenuList = styled.ul` font-size: ${props => props.theme.fontSize.small}; + display: ${({ isOpen }) => (isOpen ? 'block' : 'none')}; `; const SubMenu = styled.li` margin-bottom: 8px; + cursor: pointer; + + :hover { + text-decoration: underline; + color: ${props => props.theme.colors.grey2}; + } `; export { Navbar, Menu, Icons, MenuBox, MainMenu, SubMenuList, SubMenu }; diff --git a/FE/src/Special/Special.jsx b/FE/src/Special/Special.jsx new file mode 100644 index 000000000..619ff0fa9 --- /dev/null +++ b/FE/src/Special/Special.jsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import Card from '../components/Card'; +import { fetchData } from '../utils/utils'; +import { + SpecialContainer, + SpecialBadge, + SpecialTitle, + SpecialTitleBox, + SpecialTabBar, + SpecialHeader, + SpecialTab, + CardContainer, +} from './Special.style'; +import tabData from '../data/specialTab.js'; + +const Special = () => { + const [data, setData] = useState([]); + const [tabNum, setTabNum] = useState(0); + + useEffect(() => { + fetchTabData(); + }, []); + + const handleTabClick = tabNum => { + setTabNum(tabNum); + fetchTabData(); + }; + + const fetchTabData = async () => { + const TEST_URL = 'http://3.39.42.204/api/dishes'; + const data = await fetchData(TEST_URL); + setData(data.response.slice(0, 3)); + }; + + return ( + + + + 기획전 + 한 번 주문하면 두 번 반하는 반찬 + + + {tabData.map((v, i) => { + return ( + handleTabClick(v.index)} + isSelected={tabNum === v.index ? true : false} + > + {v.name} + + ); + })} + + + + {data.map((v, i) => ( + + ))} + + + ); +}; + +export default Special; diff --git a/FE/src/Special/Special.style.js b/FE/src/Special/Special.style.js new file mode 100644 index 000000000..3740c0c77 --- /dev/null +++ b/FE/src/Special/Special.style.js @@ -0,0 +1,64 @@ +import styled from 'styled-components'; + +const SpecialContainer = styled.div` + width: 100%; +`; + +const SpecialHeader = styled.div` + padding: 56px 80px 0 80px; + border-bottom: 1px solid ${props => props.theme.colors.grey4}; +`; + +const SpecialTitleBox = styled.div` + display: flex; + margin-bottom: 24px; +`; + +const SpecialBadge = styled.div` + width: 76px; + height: 42px; + border: 2px solid ${props => props.theme.colors.black}; + border-radius: 20px; + line-height: 42px; + text-align: center; + margin-right: 16px; +`; + +const SpecialTitle = styled.h1` + font-size: ${props => props.theme.fontSize.xLarge}; + font-weight: ${props => props.theme.fontWeight.display}; + color: ${props => props.theme.colors.black}; +`; + +const SpecialTabBar = styled.div` + display: flex; + width: 100%; +`; + +const SpecialTab = styled.div` + font-size: ${props => props.theme.fontSize.large}; + font-weight: ${props => props.theme.fontWeight.bold}; + color: ${props => props.theme.colors.black}; + margin-right: 32px; + padding-bottom: 17px; + box-shadow: 0 ${({ isSelected }) => (isSelected ? '1px' : '0')} ${props => props.theme.colors.black}; + cursor: pointer; +`; + +const CardContainer = styled.div` + display: flex; + justify-content: space-between; + padding: 34px 80px 56px 80px; + border-bottom: 1px solid ${props => props.theme.colors.grey4}; +`; + +export { + SpecialContainer, + SpecialHeader, + SpecialTitleBox, + SpecialBadge, + SpecialTitle, + SpecialTabBar, + SpecialTab, + CardContainer, +}; diff --git a/FE/src/components/Card.jsx b/FE/src/components/Card.jsx new file mode 100644 index 000000000..4ea189bc0 --- /dev/null +++ b/FE/src/components/Card.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CardWrapper, SubTitle, Title, Thumbnail, PriceBox, SalePrice, Badge, DescriptionWrapper } from './Card.syle'; + +const Card = ({ data }) => { + return ( + + + + {data.name} + {data.description} + + {data.normalPrice.toLocaleString('ko-KR')}원 + {data.salePrice.toLocaleString('ko-KR')}원 + + + 런칭특가 + + ); +}; + +export default Card; diff --git a/FE/src/components/Card.syle.js b/FE/src/components/Card.syle.js new file mode 100644 index 000000000..8ca3a8dc6 --- /dev/null +++ b/FE/src/components/Card.syle.js @@ -0,0 +1,56 @@ +import styled from 'styled-components'; + +const CardWrapper = styled.div` + display: flex; + flex-direction: column; + cursor: pointer; +`; + +const Thumbnail = styled.div` + width: 411px; + height: 411px; + background-image: url(${({ src }) => src}); + background-repeat: no-repeat; + background-size: cover; +`; + +const Title = styled.h3` + font-size: ${props => props.theme.fontSize.medium}; + font-weight: ${props => props.theme.fontWeight.bold}; + color: ${props => props.theme.colors.grey1}; + margin-right: 8px; +`; + +const SubTitle = styled.p` + font-size: ${props => props.theme.fontSize.small}; + color: ${props => props.theme.colors.grey2}; + margin: 8px 0; +`; + +const PriceBox = styled.div` + display: flex; +`; + +const SalePrice = styled.p` + font-size: ${props => props.theme.fontSize.small}; + color: ${props => props.theme.colors.grey3}; + text-decoration: line-through; +`; + +const Badge = styled.div` + width: 76px; + height: 30px; + border-radius: 20px; + background-color: ${props => props.theme.colors.orange}; + color: ${props => props.theme.colors.white}; + font-size: ${props => props.theme.fontSize.xSmall}; + font-weight: ${props => props.theme.fontWeight.medium}; + text-align: center; + line-height: 30px; +`; + +const DescriptionWrapper = styled.div` + margin: 16px 0; +`; + +export { CardWrapper, Thumbnail, Title, SubTitle, PriceBox, SalePrice, Badge, DescriptionWrapper }; diff --git a/FE/src/data/headerMenu.js b/FE/src/data/headerMenu.js new file mode 100644 index 000000000..40693f8dd --- /dev/null +++ b/FE/src/data/headerMenu.js @@ -0,0 +1,14 @@ +export default [ + { + main: '든든한 메인 요리', + sub: ['육류 요리', '해산물 요리'], + }, + { + main: '뜨끈한 국물요리', + sub: ['국/탕/찌개'], + }, + { + main: '정갈한 밑반찬', + sub: ['나물/무침', '조림/볶음', '절임/장아찌'], + }, +]; diff --git a/FE/src/data/specialTab.js b/FE/src/data/specialTab.js new file mode 100644 index 000000000..99d689341 --- /dev/null +++ b/FE/src/data/specialTab.js @@ -0,0 +1,18 @@ +export default [ + { + name: '풍성한 고기 반찬', + index: 0, + }, + { + name: '편리한 반찬 세트', + index: 1, + }, + { + name: '맛있는 제철 요리', + index: 2, + }, + { + name: '우리 아이 영양 반찬', + index: 3, + }, +]; diff --git a/FE/src/style/global.js b/FE/src/style/global.js index 885fd71fd..067085caa 100644 --- a/FE/src/style/global.js +++ b/FE/src/style/global.js @@ -9,6 +9,10 @@ const GlobalStyle = createGlobalStyle` box-sizing: border-box; } +body { + font-family: 'Noto Sans KR'; +} + h1 { margin: 0; } diff --git a/FE/src/style/theme.js b/FE/src/style/theme.js index 1301ce493..e1cff6bac 100644 --- a/FE/src/style/theme.js +++ b/FE/src/style/theme.js @@ -12,10 +12,6 @@ const fontWeight = { regular: 400, }; -const fontFamily = { - default: 'Noto Sans KR', -}; - const colors = { black: '#1B1B1B', grey1: '#3F3F3F', @@ -31,6 +27,5 @@ const colors = { export const theme = { fontSize, fontWeight, - fontFamily, colors, }; diff --git a/FE/src/utils/utils.js b/FE/src/utils/utils.js new file mode 100644 index 000000000..f771f7e43 --- /dev/null +++ b/FE/src/utils/utils.js @@ -0,0 +1,15 @@ +const handleError = async response => { + if (response.status >= 200 && response.status < 300) { + const data = await response.json(); + return data; + } else { + throw new Error('Fetch Failed'); + } +}; + +const fetchData = async url => { + const response = await fetch(url); + return handleError(response); +}; + +export { fetchData };