diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..0053e8309 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // IntelliSense를 사용하여 가능한 특성에 대해 알아보세요. + // 기존 특성에 대한 설명을 보려면 가리킵니다. + // 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요. + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 43a0cc5db..3573b97b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1659,6 +1659,22 @@ } } }, + "@jjunyjjuny/react-carousel": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@jjunyjjuny/react-carousel/-/react-carousel-0.0.2.tgz", + "integrity": "sha512-Bf4W+adk7pL+O17eHpqqfRU5OTYlFk5Uu/qX2KdvJwa9p3QSxHarnICYuvCXFPHFbo186lGGA9SIAyj7XgBddg==", + "requires": { + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.1.0", + "@testing-library/user-event": "^12.1.10", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-icons": "^4.2.0", + "react-scripts": "4.0.3", + "styled-components": "^5.2.3", + "web-vitals": "^1.0.1" + } + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -11694,6 +11710,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" }, + "react-icons": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz", + "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==" + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index a3a3b022d..95039c6df 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,13 @@ "version": "0.1.0", "private": true, "dependencies": { + "@jjunyjjuny/react-carousel": "0.0.2", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-icons": "^4.2.0", "react-scripts": "4.0.3", "styled-components": "^5.2.3", "web-vitals": "^1.0.1" diff --git a/src/App.js b/src/App.js index 88b1259ea..689c56af1 100644 --- a/src/App.js +++ b/src/App.js @@ -1,22 +1,9 @@ -import BestList from "./js/components/bestList/BestList"; +import Main from "./js/components/Main"; import TestCarousel from "./js/util/TestCarousel"; function App() { - const test = { - detail_hash: "HBDEF", - image: - "http://public.codesquad.kr/jk/storeapp/data/2d3f99a9a35601f4e98837bc4d39b2c8.jpg", - alt: "[미노리키친] 규동 250g", - delivery_type: ["새벽배송", "전국택배"], - title: "[미노리키친] 규동 250g", - description: "일본인의 소울푸드! 한국인도 좋아하는 소고기덮밥", - n_price: "6,500", - s_price: "7,000원", - badge: ["이벤트특가"], - }; - return ( <> - +
); diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx new file mode 100644 index 000000000..0bb0ab6e8 --- /dev/null +++ b/src/js/components/Header.jsx @@ -0,0 +1,149 @@ +import { useState } from "react"; +import styled from "styled-components"; + +const HeaderWrapper = styled.div` + position: relative; + width: 1440px; + height: 122px; + + font-family: Noto Sans KR; + font-style: normal; +`; +const Title = styled.div` + position: absolute; + width: 190px; + height: 58px; + top: 32px; + left: 0px; + + font-weight: 900; + font-size: 40px; + line-height: 58px; + letter-spacing: -0.04em; + text-transform: uppercase; + color: #333333; +`; +const MenuWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + position: absolute; + padding: 0px; + width: 360px; + height: 23px; + top: 50px; + left: 246px; +`; +const Menu = styled.div``; +const MenuTitle = styled.div` + font-weight: ${(props) => (props.isOn ? "bold" : "normal")}; + font-size: 16px; + line-height: 23px; + color: #333; +`; +const MenuBodyWrapper = styled.div` + visibility: ${(props) => (props.isOn ? "visible" : "hidden")}; + display: flex; + flex-direction: column; + align-items: flex-start; + position: relative; + top: 16px; + padding: 16px; + background: #fff; + box-shadow: 0px 0px 4px rgba(204, 204, 204, 0.5), 0px 2px 4px rgba(0, 0, 0, 0.25); + z-index: 1; +`; +const MenuBody = styled.div` + font-size: 16px; + line-height: 28px; + color: #828282; + &:hover { + color: #333333; + text-decoration-line: underline; + } +`; +const SearchBar = styled.input` + position: absolute; + width: 248px; + height: 40px; + top: 41px; + left: 898px; + border-radius: 5px; + border: none; + background: #f5f5f7; +`; +const SearchBarIcon = styled.div` + position: absolute; + top: 50px; + left: 1116px; +`; +const Login = styled.div` + position: absolute; + top: 50px; + left: 1172px; + font-weight: normal; + font-size: 16px; + line-height: 23px; + color: #333; + &:hover { + font-weight: bold; + } +`; +const Basket = styled.div` + position: absolute; + top: 50px; + left: 1241px; + font-weight: normal; + font-size: 16px; + line-height: 23px; + color: #333; + &:hover { + font-weight: bold; + } +`; + +const Header = () => { + const [isM1On, setM1On] = useState(false); + const [isM2On, setM2On] = useState(false); + const [isM3On, setM3On] = useState(false); + return ( + + banchan + + setM1On(true)} onMouseLeave={() => setM1On(false)}> + 든든한 메인요리 + + 육류 요리 + 해산물 요리 + + + setM2On(true)} onMouseLeave={() => setM2On(false)}> + 뜨끈한 국물요리 + + 국/밥/찌개 + + + setM3On(true)} onMouseLeave={() => setM3On(false)}> + 정갈한 밑반찬 + + 나물/무침 + 조림/볶음 + 절임/장아찌 + + + + + + + + + + + 로그인 + 장바구니 + + ); +}; + +export default Header; diff --git a/src/js/components/Main.jsx b/src/js/components/Main.jsx new file mode 100644 index 000000000..b0941255d --- /dev/null +++ b/src/js/components/Main.jsx @@ -0,0 +1,39 @@ +import { useState } from "react"; +import styled from "styled-components"; +import Header from "./Header"; +import BestList from "./bestList/BestList"; +import Modal from "./Modal/Modal"; + +const Wrapper = styled.div` + filter: ${(props) => (props.isModalOn ? "brightness(40%)" : "brightness(100%)")}; + background-color: #fff; + margin-left: 40px; +`; + +const Main = () => { + const test = { + data: { + top_image: "", + thumb_images: [""], + product_description: "", + point: "", + delivery_info: "", + delivery_fee: "", + prices: ["원"], + detail_section: [], + }, + }; + const [modalData, setModalData] = useState(test); + const [isModalOn, setModalOn] = useState(false); + return ( + <> + +
+ + + + + ); +}; + +export default Main; diff --git a/src/js/components/Modal/Modal.js b/src/js/components/Modal/Modal.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/js/components/Modal/Modal.jsx b/src/js/components/Modal/Modal.jsx new file mode 100644 index 000000000..26352dad0 --- /dev/null +++ b/src/js/components/Modal/Modal.jsx @@ -0,0 +1,258 @@ +import { useState } from "react"; +import styled from "styled-components"; +import ThumbNail from "./ThumbNail"; +import Badge from "../common/Badge"; +import Button from "../common/Button"; + +const ContentWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + font-family: Noto Sans KR; + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 23px; + margin: 16px 0px; +`; +const ContentTitle = styled.div` + width: 76px; + color: #828282; +`; +const ContentBody = styled.div` + width: 364px; + color: #4f4f4f; +`; +const Content = ({ title, body }) => { + const free = "(40,000원 이상 구매 시 무료)"; + return ( + + {title} + + {body.replace(free, "")} + {body.includes(free) ? free : ""} + + + ); +}; + +const ModalWrapper = styled.div` + visibility: ${(props) => (props.isOn ? "visible" : "hidden")}; + position: absolute; + width: 1000px; + height: 1076px; + left: 220px; + top: 170px; + font-family: Noto Sans KR; + font-style: normal; + font-weight: normal; +`; + +const Box = styled.div` + width: 960px; + height: 680px; + background: #fff; + border-radius: 5px 5px 0px 0px; +`; + +const ProductInfo = styled.div` + position: absolute; + width: 440px; + height: 416px; + left: 472px; + top: 48px; +`; + +const ProductName = styled.div` + font-weight: bold; + font-size: 24px; + line-height: 35px; + margin-bottom: 16px; +`; + +const ProductDescription = styled.div` + font-size: 18px; + line-height: 26px; + color: #828282; + margin-bottom: 16px; +`; + +const ProductPrice = styled.div` + display: flex; + flex-direction: row; + align-items: flex-end; + padding: 0px; + height: 35px; + margin-bottom: 24px; +`; + +const ProductPrice1 = styled.div` + font-weight: bold; + font-size: 24px; + line-height: 35px; + margin-right: 8px; +`; +const ProductPrice2 = styled.div` + font-size: 16px; + line-height: 23px; + text-decoration-line: line-through; + color: #828282; +`; +const Line = styled.div` + width: 440px; + height: 1px; + background: #e0e0e0; + margin: 8px 0px; +`; +const Number = styled.div` + position: relative; + width: 440px; + height: 41px; + display: flex; + align-items: center; + margin: 16px 0px; +`; +const NumberLabel = styled.div` + width: 60px; + height: 23px; + font-size: 16px; + line-height: 23px; + color: #828282; +`; +const NumberInput = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: absolute; + width: 57px; + height: 41px; + top: 0px; + left: 354px; + border: 1px solid #e0e0e0; +`; +const NumberUpButton = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: absolute; + width: 28px; + height: 20px; + left: 412px; + top: 0px; + border: 1px solid #e0e0e0; +`; +const NumberDownButton = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: absolute; + width: 28px; + height: 20px; + left: 412px; + top: 21px; + border: 1px solid #e0e0e0; +`; +const TotalPrice = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding: 0px; + + position: absolute; + width: 360px; + height: 46px; + left: 552px; + top: 496px; +`; +const TotalPriceLabel = styled.div` + height: 26px; + font-weight: bold; + font-size: 18px; + line-height: 26px; + text-align: right; + color: #828282; +`; +const TotalPriceContent = styled.div` + height: 46px; + font-weight: bold; + font-size: 32px; + line-height: 46px; + text-align: right; + color: #010101; + margin-left: 24px; +`; +const CloseButton = styled.div` + position: absolute; + width: 32px; + height: 32px; + left: 968px; + top: 0px; + svg { + position: absolute; + } +`; +const ButtonWrapper = styled.div` + position: absolute; + top:574px; + left:472px; +`; + +const Modal = ({ data, isModalOn, setModalOn }) => { + const [count, setCount] = useState(1); + const price = parseInt((data.prices[1] ? data.prices[1] : data.prices[0]).replace("원", "").replace(",", "")); + return ( + + + + + {data.name} + {data.product_description} + + {data.badge ? : ""} + {data.prices[1] ? data.prices[1] : data.prices[0]} + {data.prices[1] ? data.prices[0] : ""} + + + + + + + + 수량 + {count} + setCount((count) => ++count)}> + + + + + setCount((count) => (count > 0 ? --count : 0))}> + + + + + + + + + + 총 주문금액 + {(price * count).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}원 + + + + + + + {renderList()} + + + ); }; const createVirtureSlides = (items, itemsPerPeice) => { - return items.reduce((result, item, index) => { + if (!Array.isArray(items)) { + return [[items], []]; + } + const newItems = items.reduce((result, item, index) => { const [i, j] = divmod(index, itemsPerPeice); result[i] ? (result[i][j] = item) : (result[i] = [item]); return result; }, []); + return [newItems.shift(), newItems]; }; const divmod = (a, b) => { return [parseInt(a / b), a % b]; }; -const moveToPrev = keyframes` - from{ - transform: translateX(0px); - }to{ - transform: translateX(100%); - }; -`; -const moveToNext = keyframes` - from{ - transform: translateX(100%); - }to{ - transform: translateX(0); - }; -`; const CarouselWrapper = styled.div` - background-color: #b5b5b5; - /* overflow: hidden; */ - position: relative; + background: #b5b5b5; + display: flex; +`; +const CarouselContent = styled.div` + width: 90%; + overflow: hidden; `; const CarouselContainer = styled.div` - transition: all 1s ease; - display: flex; - ${({ direction }) => { - if (direction === "none") return; - return direction === "prev" - ? css` - animation: ${moveToPrev} 1s; - ` - : css` - animation: ${moveToNext} 1s; - `; - }} + display: inline-flex; + width: 100%; + padding-right: ${({ gap }) => `${gap}`}; `; const Slide = styled.ul` - background-color: #777777; + background: #777777; display: flex; width: 100%; flex: 1 0 auto; padding: 0; + padding-right: ${({ gap }) => `${gap}`}; + &::first-child { + } `; const SlideItem = styled.li` list-style-type: none; - width: 100%; + ${({ autoFit, itemsPerPeice, gap }) => css` + width: ${autoFit ? `calc(100%/${itemsPerPeice})` : "auto"}; + & + & { + margin-left: ${gap}; + } + `} `; const Button = styled.div` - position: absolute; - width: 50px; - height: 50px; - background: orange; - top: 0; + flex: 1; + z-index: 2; ${(props) => css` ${props.prev && css` - left: 10px; + left: 10%; `} ${props.next && css` - right: 10px; + right: 10%; `} - `} + `}; `; export default Carousel; diff --git a/src/js/util/TestCarousel.js b/src/js/util/TestCarousel.js index 4df0ec13e..0196f24b4 100644 --- a/src/js/util/TestCarousel.js +++ b/src/js/util/TestCarousel.js @@ -2,42 +2,32 @@ import styled from "styled-components"; import Carousel from "./Carousel"; const StyledBlock = styled.div` + margin: 0 auto; + margin-top: 100px; background-color: #e3e3e3; - width: 700px; + width: 500px; `; const TestBlock = styled.div` background-color: tan; + height: 50px; + box-sizing: border-box; `; +const array = [1, 2, 3, 4, 6, 7, 8]; const TestCarousel = () => { return (

테스트 중입니다

{ - console.log("click item!"); + itemsPerPeice={5} + autoFit + gap={"10px"} + onClickItem={({ target }) => { + console.log(1, target); }} > - test1 - test2 - test3 - test4 - test5 - test6 - test7 - test8 - test9 - test10 - test11 - test12 - test13 - test14 - test15 - test16 - test17 - test18 - test19 - test20 + {array.map((el) => ( + el + ))}
); diff --git a/webapck.dev.js b/webapck.dev.js deleted file mode 100644 index d86463300..000000000 --- a/webapck.dev.js +++ /dev/null @@ -1,62 +0,0 @@ -const path = require("path"); - -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); - -module.exports = { - mode: "development", - devServer: { - contentBase: path.resolve(__dirname, "dist"), - host: "localhost", - compress: true, - port: 8080, - }, - devtool: "inline-source-map", - entry: "./src/js/index.js", - output: { - path: path.resolve(__dirname, "dist"), - filename: "bundle.js", - clean: true, - }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: "babel-loader", - options: { - presets: [ - [ - "@babel/preset-env", - { - targets: { - ie: 11, - }, - useBuiltIns: "usage", - corejs: { version: 3, proposals: true }, - }, - ], - ], - plugins: [ - ["@babel/plugin-proposal-class-properties", { loose: true }], - ], - }, - }, - }, - { - test: /\.css$/, - include: [path.resolve(__dirname, "src/css")], - use: [MiniCssExtractPlugin.loader, "css-loader"], - }, - ], - }, - plugins: [ - new HtmlWebpackPlugin({ - template: "./index.html", - }), - new MiniCssExtractPlugin({ - filename: "index.css", - }), - ], -};