Skip to content

Commit

Permalink
[Team23] 캐로셀 적용, 모달창 설계 및 구현, 상품 이미지 호버 적용 (#30)
Browse files Browse the repository at this point in the history
* [Add] Carousel

- 캐러셀 구현
- 캐러셀 틀을 잡기 위해 구조 살짝 변경 - 컴포넌트 트리 참고
- products.length가 8의 배수가 아닌 경우를 생각해서 로직을 짰는데 아직 테스트는 안해봄

* [Fix] Carousel

- 4의 배수가 아닐 때 캐러셀 애니메이션 테스트함
- left 버튼을 누를 때 문제가 있었는데 index 업데이트를 수정하여 고침

* [Fix] Direction of carousel-button

- 왼쪽 버튼을 누르면 이전 슬라이드가, 오른쪽 버튼을 누르면 이후 슬라이드가 나오도록 수정

* [Add] hover ver.1
- 리렌더링 때문에 깜빡거림

* [Feat] 카드 이미지에 hover 시 배송 타입 노출

* [Add] Modal skeleton code

* [Refactor] Modal 컴포넌트 분리
- 공통적으로 사용되는 styled component를 common.jsx에 분리
- 카드에 있는 정보를 모달에 전달

* [Fix] 할인 전후 가격 잘못 넣어서 수정

* [Add] Modal에 클릭한 상품의 데이터를 넣어줌

Co-authored-by: Hongbi <dyongdi@gmail.com>
  • Loading branch information
adelakim5 and deprecated-hongbiii committed Apr 26, 2021
1 parent 631fc3a commit 5b1790b
Show file tree
Hide file tree
Showing 13 changed files with 581 additions and 75 deletions.
6 changes: 3 additions & 3 deletions banchan/src/components/StateProvider.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from "react";
// import { useState } from "react";
import Header from "./header/Header";
import MainPage from "./main/MainPage";
import Test from "./Test";
// import Test from "./Test";

const StateProvider = () => {
// const [loginState, setLoginState] = useState(false);
Expand All @@ -11,7 +11,7 @@ const StateProvider = () => {
<>
<Header />
<MainPage />
<Test />
{/* <Test /> */}
</>
);
};
Expand Down
36 changes: 36 additions & 0 deletions banchan/src/components/main/CarouselList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import Card from '../utils/Card';
import theme from '../utils/styles/theme';

const CarouselList = (props) => {
const [products, setProducts] = useState([]);

useEffect(() => {
fetch(
'https://h3rb9c0ugl.execute-api.ap-northeast-2.amazonaws.com/develop/baminchan/main'
)
.then((response) => response.json())
.then((result) => setProducts(result.body))
.then((error) => console.log('error', error));
}, []);

return (
<StyledUl>
{products.map((product) => (
<Card
key={product.detail_hash}
product={product}
cardSize={theme.cardSizes.M}
/>
))}
</StyledUl>
);
};

const StyledUl = styled.ul`
display: flex;
justify-content: space-evenly;
`;

export default CarouselList;
142 changes: 123 additions & 19 deletions banchan/src/components/main/CarouselSection.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,134 @@
import { useEffect, useState } from "react";
import Card from "../utils/Card";
import theme from "../utils/styles/theme";
import { useEffect, useRef, useState } from 'react';
// import { mockData, temp } from "../utils/mockData.js";
import Card from '../utils/Card';
import styled from 'styled-components';
import IconButton from '../utils/button/IconButton';
import { CenterContainer } from '../utils/styles/common';

const CarouselSection = (props) => {
const CarouselSection = ({ key, url, title, onModal }) => {
const [products, setProducts] = useState([]);
// const [products, setProducts] = useState(mockData);
const [currentX, setX] = useState(0);
const [currentIndex, setCurrentIndex] = useState(4);
const [rightDisabled, setLeftDisabled] = useState(false);
const [leftDisabled, setRightDisabled] = useState(true);
const slides = useRef();
const slideCount = 4;
const slideWidth = 320;
const totalWidth = 320 * slideCount + 16;

useEffect(() => {
fetch(
"https://h3rb9c0ugl.execute-api.ap-northeast-2.amazonaws.com/develop/baminchan/main"
)
.then((response) => response.json())
.then((result) => setProducts(result.body))
.then((error) => console.log("error", error));
(async () => {
const response = await fetch(url);
const result = await response.json();
setProducts(result.body);
})();
}, []);

const moveSlide = (speed, distance, nextIndex) => {
slides.current.style.transition = `${speed}ms`;
slides.current.style.transform = `translateX(${distance}px)`;
setX(distance);
setCurrentIndex(nextIndex);
};

const moveRight = () => {
const remainSlideCount = products.length - currentIndex;
const distance =
remainSlideCount >= slideCount
? currentX - totalWidth
: currentX - slideWidth * remainSlideCount;
const nextIndex =
remainSlideCount >= slideCount
? currentIndex + slideCount
: currentIndex + remainSlideCount;
moveSlide(300, distance, nextIndex);
distance && setRightDisabled(false);
nextIndex >= products.length && setLeftDisabled(true);
};

const moveLeft = () => {
const remainSlideCount = currentIndex - slideCount;
const distance =
remainSlideCount >= slideCount
? currentX + totalWidth
: currentX + slideWidth * remainSlideCount;
const nextIndex =
remainSlideCount >= slideCount
? currentIndex - slideCount
: currentIndex - remainSlideCount;
moveSlide(300, distance, nextIndex);
!distance && setRightDisabled(true);
nextIndex < products.length && setLeftDisabled(false);
};

return (
<ul>
{products.map((product) => (
<Card
key={product.detail_hash}
product={product}
cardSize={theme.cardSizes.M}
/>
))}
</ul>
<SectionContainer>
<SectionBox>
<SectionTitle>{title}</SectionTitle>
<SectionContent>
<CardList ref={slides}>
{products.map((product) => (
<Card
key={product.detail_hash}
product={product}
cardSize={(props) => props.theme.cardSizes.M}
margin={8}
onModal={onModal}
/>
))}
</CardList>
</SectionContent>
</SectionBox>
<SectionButton>
<IconButton type="LEFT" fn={moveLeft} disabled={leftDisabled} />
<IconButton type="RIGHT" fn={moveRight} disabled={rightDisabled} />
</SectionButton>
</SectionContainer>
);
};

const SectionContainer = styled(CenterContainer)`
position: relative;
margin: 30px 0;
/*border: 1px solid blue;*/
width: 1320px;
`;

const SectionBox = styled.div`
/*border: 1px solid violet;*/
`;

const SectionContent = styled.div`
position: relative;
display: flex;
margin: 20px 0;
width: 1296px; /* 원래 1280 */
height: 500px;
/*border: 1px solid black;*/
overflow: hidden;
`;

const CardList = styled(CenterContainer)`
list-style: none;
align-items: start;
position: absolute;
`;

const SectionTitle = styled.div`
font-size: ${(props) => props.theme.fontSizes.XL};
font-weight: bold;
color: ${(props) => props.theme.darkGray};
margin: 20px 0;
`;

const SectionButton = styled.div`
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
display: flex;
justify-content: space-between;
`;

export default CarouselSection;
33 changes: 32 additions & 1 deletion banchan/src/components/main/CarouselSectionList.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
const CarouselSectionList = (props) => <></>;
import { useState } from 'react';
import { URLS } from '../utils/variables.js';
import CarouselSection from './CarouselSection.jsx';
import styled from 'styled-components';
import { CenterContainer } from '../utils/styles/common.jsx';

const CarouselSectionList = (props) => {
const [sections, setSections] = useState([
{ id: 0, kind: 'main', title: '모두가 좋아하는 든든한 메인요리' },
{ id: 1, kind: 'soup', title: '정성이 담긴 뜨끈한 국물요리' },
{ id: 2, kind: 'side', title: '식탁을 풍성하게 하는 정갈한 밑반찬' },
]);

return (
<CarouselContainer>
{sections.map((section) => (
<CarouselSection
key={section.id}
url={URLS.base.concat(section.kind)}
title={section.title}
onModal={props.onModal}
/>
))}
</CarouselContainer>
);
};

const CarouselContainer = styled(CenterContainer)`
/*border: 1px solid red;*/
display: flex;
flex-direction: column;
`;

export default CarouselSectionList;
57 changes: 53 additions & 4 deletions banchan/src/components/main/MainPage.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
import CarouselSection from "./CarouselSection";
import TabSection from "./tab/TabSection";
import TabSection from './tab/TabSection';
import CarouselSectionList from './CarouselSectionList';
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { CenterContainer } from '../utils/styles/common';
import Modal from './Modal';

const MainPage = (props) => {
const [modalState, setModalState] = useState(false);
const [modalData, setModalData] = useState({});
const [detailDataMap, setDetailDataMap] = useState(new Map());

useEffect(() => {
fetch(
'https://h3rb9c0ugl.execute-api.ap-northeast-2.amazonaws.com/develop/baminchan/detail'
)
.then((res) => res.json())
.then((response) => {
response.body.forEach((e) => {
setDetailDataMap(detailDataMap.set(e.hash, e.data));
});
});
}, []);

const handleModal = (product) => {
setModalState(true);
const detailData = detailDataMap.get(product.detail_hash);
setModalData({ ...product, ...detailData });
};

return (
<>
<TabSection />
<CarouselSection />
<TabSection onModal={handleModal} />
<CarouselSectionList onModal={handleModal} />
{modalState && (
<ModalBackground>
<ModalContainer>
<Modal product={modalData} />
<button onClick={() => setModalState(false)}>X</button>
</ModalContainer>
</ModalBackground>
)}
</>
);
};

const ModalBackground = styled(CenterContainer)`
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.4);
width: 100%;
height: 100%;
z-index: 5;
`;

const ModalContainer = styled.div`
display: flex;
align-items: flex-start;
`;

export default MainPage;
77 changes: 77 additions & 0 deletions banchan/src/components/main/Modal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import styled from 'styled-components';
import Price from '../utils/Price';
import TextButton from '../utils/button/TextButton';
import {
LabelList,
StyledDescription,
StyledTitle,
} from '../utils/styles/common';

const Modal = ({ product }) => {
console.log(product);
return (
<ModalCard>
<ProductImage>
<img src={product.top_image} alt="product-thumbnail" />
<ThumbnailUL>
{product.thumb_images.map((i) => (
<li>
<Thumbnail src={i} />
</li>
))}
</ThumbnailUL>
</ProductImage>
<Information>
<ProductMainInfo>
<StyledTitle>{product.title}</StyledTitle>
<StyledDescription>{product.description}</StyledDescription>
<div>
<LabelList />
<Price product={product} />
</div>
</ProductMainInfo>
<ProductBuyInfo>
<div>적립금: {product.point}</div>
<div>배송정보: {product.delivery_info}</div>
<div>배송비 : {product.delivery_fee}</div>
</ProductBuyInfo>
<ProductCount></ProductCount>
<ProductPrice>
여기는 총 주문 금액이 들어갈 예정입니다. 카운트를 같이 계산해서..
</ProductPrice>
<TextButton type="ORDER"></TextButton>
</Information>
</ModalCard>
);
};

const ModalCard = styled.div`
background: white;
width: 960px;
height: 1076px;
`;

const ThumbnailUL = styled.div`
display: flex;
`;

const Thumbnail = styled.img`
width: 100px;
height: 100px;
`;

const ProductImage = styled.div``;

const Information = styled.div``;

const ProductMainInfo = styled.div``;

// const LabelList = styled.div``;

const ProductBuyInfo = styled.div``;

const ProductCount = styled.div``;

const ProductPrice = styled.div``;

export default Modal;
Loading

0 comments on commit 5b1790b

Please sign in to comment.