TIL_60_2차 프로젝트 Nav 와 검색 모달창


네브 바와 검색 모달창에 대해 간단한 리뷰를 남겨보겠다.

  • 네브 바에서 검색 인풋창을 클릭하면 검색 모달창이 뜨도록 구현했다.
  • 검색 모달창의 이미지들에 호버효과도 추가했다.
    (우리 팀원들의 사진은 보너스~~)
  • api 통신을 통해 영어 이름 과 한글 이름 둘 다로 가능하며, 10개씩 데이터를 fetch.
  • 네브 바의 로고와 shop 을 누르면 메인페이지로 이동되도록 구현.
  • 로그인을 누를 시 로그인 페이지로 이동.
//Nav.js
import React from "react";
import { Link } from "react-router-dom";
import SearchModal from "./SearchModal";
import styled from "styled-components";
import { FaSearch } from "react-icons/fa";

class Nav extends React.Component {
  constructor() {
    super();
    this.state = {
      modalOn: false,
    };
  }

  handleSearchModal = () => {
    this.setState({ modalOn: !this.state.modalOn });
  };

  render() {
    return (
      <WrapNav>
        <NavContainer>
          <Logo>
            <Link to="/">HEUREAM</Link>
          </Logo>
          <FaSearch size="30" color="lightgray" />
          {this.state.modalOn && (
            <SearchModal
              handleSearchModal={this.handleSearchModal}
              modalOn={this.state.modalOn}
            />
          )}
          <SearchInput
            type="text"
            placeholder="브랜드명, 모델명, 모델번호 등"
            onClick={this.handleSearchModal}
          />
          <ListContainer>
            <Ul>
              <Li>
                <Link to="/">SHOP</Link>
              </Li>
              <Li>고객센터</Li>
              <Li>
                <Link to="/login">로그인</Link>
              </Li>
            </Ul>
          </ListContainer>
        </NavContainer>
      </WrapNav>
    );
  }
}

export default Nav;

const WrapNav = styled.div`
  position: fixed;
  z-index: 1000;
  width: 100%;
  height: 90px;
  background-color: white;
`;

const NavContainer = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 90px;
  padding: 0 32px 0 40px;
`;

const Logo = styled.h1`
  width: 150px;
  height: 24px;
  font-size: 25px;
  font-weight: bold;
  font-family: "Fugaz One", cursive;
`;

const SearchInput = styled.input`
  width: 800px;
  height: 40px;
  cursor: text;
  border-radius: 8px;
  background-color: rgb(244, 244, 244);
  &::placeholder {
    font-size: 13px;
    color: gray;
    padding-left: 10px;
  }
`;

const ListContainer = styled.nav`
  width: 187px;
  height: 38px;
  margin-left: 10px;
`;

const Ul = styled.ul`
  margin: 0;
`;

const Li = styled.li`
  display: list-item;
  float: left;
  margin: 10px 0 0 17px;
  font-size: 13px;
`;
//SearchModal.js
import React from "react";
import SearchDataList from "./SearchDataList";
import styled from "styled-components";
import { FaSearch } from "react-icons/fa";
// `http://127.0.0.1:8000/product/search?q=${keyword}`
// "data/SearchList.json"

class SearchModal extends React.Component {
  constructor() {
    super();
    this.state = {
      keyWord: "",
      keyWordList: [],
    };
  }

  keyWordInput = e => {
    this.setState({
      keyWord: e.target.value,
    });
    this.filterKeyword(e);
  };

  // keyWordDelete = () => {
  //   this.setState({
  //     keyWord: "",
  //   });
  // };

  filterKeyword = e => {
    let keyword = e.target.value;
    if (keyword === "") {
      keyword = "나이키";
    }
    fetch(`http://10.58.7.188:8000/product/search?q=${keyword}`)
      .then(res => res.json())
      .then(res =>
        this.setState({
          keyWordList: res.result,
        })
      );
  };

  render() {
    return (
      <WrapSearchModal>
        <SearchWrap>
          <FaSearch size="30" color="lightgray" />
          <SearchInput
            type="text"
            placeholder="브랜드명, 모델명, 모델번호 등"
            onChange={this.keyWordInput}
            value={this.state.keyWord}
          />
          {this.props.modalOn ? (
            <CancleBtn onClick={this.props.handleSearchModal}>취소</CancleBtn>
          ) : null}
        </SearchWrap>
        {this.state.keyWord ? (
          <SearchDataList keyWordList={this.state.keyWordList} />
        ) : (
          <ImgArea>
            <BrandList>
              {BRANDLIST.map(brand => (
                <Brand id={brand.id} key={brand.id}>
                  <BrandImg alt={brand.name} src={brand.img} />
                  <BrandName>{brand.name}</BrandName>
                </Brand>
              ))}
            </BrandList>
          </ImgArea>
        )}
      </WrapSearchModal>
    );
  }
}

export default SearchModal;

const WrapSearchModal = styled.div`
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;
  height: auto;
  background-color: rgba(34, 34, 34, 0.5);
  overflow-y: auto;
`;

const SearchWrap = styled.div`
  display: flex;
  justify-content: center;
  padding: 0 32px 0 40px;
  min-width: 320px;
  width: 100%;
  height: 90px;
  align-items: center;
  background-color: white;
`;

const SearchInput = styled.input`
  width: 600px;
  height: 40px;
  cursor: text;
  border-radius: 8px;
  background-color: rgb(244, 244, 244);
  &::placeholder {
    font-size: 13px;
    color: gray;
    padding-left: 10px;
  }
`;

const CancleBtn = styled.button`
  background-color: white;
  font-size: 11px;
`;

const ImgArea = styled.div`
  background-color: white;
`;

const BrandList = styled.div`
  display: flex;
  justify-content: center;
  width: 100%;
  height: 140px;
  text-align: center;
  cursor: pointer;
`;

const Brand = styled.div`
  width: 100px;
  height: 100px;
  align-items: center;
  margin-left: 15px;
  border-radius: 8px;
  background-color: rgb(246, 238, 237);
  &:hover {
    cursor: pointer;
    border: 3px solid white;
  }
`;

const BrandImg = styled.img`
  width: 80px;
  height: 80px;
  margin-left: 10px;
  border-radius: 8px;
`;

const BrandName = styled.p`
  margin-top: 5px;
  font-size: 10px;
`;

const BRANDLIST = [
  {
    id: 1,
    name: "Jordan 1",
    img:
      "https://media.vlpt.us/images/poohv7/post/32f43b4c-7dbd-4708-afc0-7d3a189747d3/%EC%BA%A1%EC%B2%985.JPG",
  },
  {
    id: 2,
    name: "꼼데가르송",
    img:
      "https://media.vlpt.us/images/poohv7/post/062eace6-d549-47b5-98f3-aa6e1809805e/%EC%BA%A1%EC%B2%981.JPG",
  },
  {
    id: 3,
    name: "IAB",
    img:
      "https://media.vlpt.us/images/poohv7/post/5d647b26-8e67-464b-9c2e-b455d0e427ed/%EC%BA%A1%EC%B2%982.JPG",
  },
  {
    id: 4,
    name: "마르지엘라",
    img:
      "https://media.vlpt.us/images/poohv7/post/34d9f7e2-0921-46bc-aacc-6a33a85781c5/KakaoTalk_20210225_133012878.jpg",
  },
  {
    id: 5,
    name: "메종키츠네",
    img:
      "https://media.vlpt.us/images/poohv7/post/b906e5e9-b55b-4d38-b3b6-03367afdacf5/%EC%BA%A1%EC%B2%983.JPG",
  },
  {
    id: 6,
    name: "뉴발란스",
    img:
      "https://media.vlpt.us/images/poohv7/post/fa37e960-03cd-4a79-81a9-9499089259aa/%EC%BA%A1%EC%B2%984.JPG",
  },
];
//SearchDataList.js
import React from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";

class SearchDataList extends React.Component {
  render() {
    return (
      <DataList>
        {this.props.keyWordList.length === 0 ? (
          <NoResult>검색결과가 없습니다.</NoResult>
        ) : (
          <SearchResult>
            {this.props.keyWordList.map(data => (
              <Li key={data.product_id}>
                <div>
                  <Img src={data.product_image_url} alt={data.korean_name} />
                </div>
                <div>
                  <div>{data.korean_name}</div>
                  <div>{data.english_name}</div>
                </div>
                <Link to={`/product/search?q=${data.product_id}`}></Link>
              </Li>
            ))}
          </SearchResult>
        )}
      </DataList>
    );
  }
}

export default SearchDataList;

const DataList = styled.div`
  width: 100%;
  border-top: 1px solid lightgray;
  background-color: white;
`;

const NoResult = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  color: rgb(154, 154, 158);
`;

const SearchResult = styled.div`
  padding: 16.5px 0px 46px;
`;

const Li = styled.div`
  display: flex;
  padding: 6.5px 20px;
  font-size: 11px;
  border-bottom: 1px solid rgb(235, 235, 235);
  line-height: 1.5;
`;

const Img = styled.img`
  width: 70px;
  height: 70px;
  border-radius: 8px;
`;

좋은 웹페이지 즐겨찾기