spartacoding(프론트엔드의 꽃, 리액트) 2주차

hr0513·2021년 7월 14일
0

styled-components 설치하기

yarn add styled-components

버킷리스트에 styled-components 적용하기

App.js

import React from 'react';
import BucketList from './BucketList';
import styled from 'styled-components';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      list: ['영화관 가기', '매일 책읽기', '수영 배우기'],
    };
  }

  componentDidMount() {

  }

  render() {
    return (
      <div className="App">
        <Container>
          <Title>내 버킷리스트</Title>
          <Line/>
          <BucketList list = {this.state.list} />
        </Container>
      </div>
    );
  }
}

const Container = styled.div`
  max-width: 350px;
  min-height: 80vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Title = styled.h1`
  color: slateblue;
  text-align: center;
`;

const Line = styled.hr`
  margin: 16px 0px;
  border: 1px dotted #ddd;
`;

export default App;

BucketList.js

import React from 'react';
import styled from 'styled-components';

const BucketList = (props) => {
    const my_lists = props.list;

    return (
        <ListStyle>
            {my_lists.map((list, index) => {
                return (
                    <ItemStyle key={index}>
                        {list}
                    </ItemStyle>
                )
            })}
        </ListStyle>
    );
};

const ListStyle = styled.div`
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
`;

const ItemStyle = styled.div`
    padding: 16px;
    margin: 8px;
    background-color: aliceblue;
`;

export default BucketList;

State 관리

App.js

import React from 'react';
import './App.css';
import Nemo from './Nemo';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {};
  }

  render() {
    return(
      <div className="App">
        <Nemo/>
      </div>
    );
  }
}

export default App;

Nemo.js

import React from 'react';

const Nemo = () => {
    const [count, setCount] = React.useState(3);

    const addNemo = () => {
        setCount(count + 1);
    }

    const removeNemo = () => {
        setCount(count>0 ? count - 1 : 0);
    }
    
    const nemo_count = Array.from({length: count}, (v, i) => i); 
    
    return (
        <div>
            {nemo_count.map((num, idx) => {
                return (
                    <div
                        style={{
                        display: "flex",
                        justifyContent: "center",
                        alignItems: "center",
                        width: "150px",
                        height: "150px",
                        background: "#eee",
                        margin: "10px",
                        }}
                    >nemo</div>
                );
            })}
            <button onClick={addNemo}>하나 추가</button>
            <button onClick={removeNemo}>하나 빼기</button>
        </div>
    );
}

export default Nemo;

버킷리스트에 아이템 추가하기

App.js

import React from 'react';
import BucketList from './BucketList';
import styled from 'styled-components';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      list: ['영화관 가기', '매일 책읽기', '수영 배우기'],
    };

    this.text = React.createRef();
  }

  addBucketList = () => {
    let list = this.state.list;
    const new_item = this.text.current.value;

    this.setState({list: [...list, new_item]});
  }

  componentDidMount() {
    console.log(this.text);
    console.log(this.text.current);
  }

  render() {
    return (
      <div className="App">
        <Container>
          <Title>내 버킷리스트</Title>
          <Line/>
          <BucketList list = {this.state.list} />
        </Container>
        <Input>
          <input type="text" ref={this.text}/>
          <button onClick={this.addBucketList}>추가하기</button>
        </Input>
      </div>
    );
  }
}

const Input = styled.div`
  max-width: 350px;
  min-height: 10vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Container = styled.div`
  max-width: 350px;
  min-height: 80vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Title = styled.h1`
  color: slateblue;
  text-align: center;
`;

const Line = styled.hr`
  margin: 16px 0px;
  border: 1px dotted #ddd;
`;

export default App;

BucketList.js

import React from 'react';
import styled from 'styled-components';

const BucketList = (props) => {
    const my_lists = props.list;

    return (
        <ListStyle>
            {my_lists.map((list, index) => {
                return (
                    <ItemStyle key={index}>
                        {list}
                    </ItemStyle>
                )
            })}
        </ListStyle>
    );
};

const ListStyle = styled.div`
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
`;

const ItemStyle = styled.div`
    padding: 16px;
    margin: 8px;
    background-color: aliceblue;
`;

export default BucketList;

퀴즈 문제 페이지

스와이프 이벤트를 미리 구현해준 패키기 다운로드

yarn add react-tinder-card

App.js

import React from "react";
import Start from "./Start";
import Quiz from './Quiz';
import Score from './Score';

class App extends React.Component {
  constructor(props){
    super(props);
  
    this.state = {
      name: "르탄이",
      page: "quiz",
      list: [
        {question: "르탄이는 1살이다", answer: "O"},
        {question: "르탄이는 2살이다", answer: "O"},
        {question: "르탄이는 3살이다", answer: "O"},
        {question: "르탄이는 4살이다", answer: "O"},
      ],
      scoreMsg: "이 정도면 아주 친한 친구 사이! 앞으로도 더 친하게 지내요!:)"
    };
  }

  render () {
    return (
      <div className="App">
        {this.state.page === "quiz" && <Quiz list={this.state.list}/>}
        {this.state.page === "start" && <Start name={this.state.name}/>}
        {this.state.page === "score" && <Score name={this.state.name} scoreMsg={this.state.scoreMsg}/>}
        {/* <Start name={this.state.name}/> */}
        {/* <Quiz/> */}
      </div>
    );
  }
}

export default App;

Quiz.js

import React from "react";
import styled from "styled-components";
import img from "./scc_img01.png";
import TinderCard from "react-tinder-card";
import SwipeItem from "./SwipeItem";

const Quiz = (props) => {
    console.log(props);
   
    const [num, setNum] = React.useState(0);
    const onSwipe = (direction) => {
        console.log("You swiped: " + direction);
        setNum(num + 1);
    };

    if (num > 10) {
        return <div>퀴즈 끝!</div>;
    }
    
    return (
        <QuizContainer>
        <p>
            <span>{num + 1}번 문제</span>
        </p>
        {props.list.map((l, idx) => {
            if (num === idx) {
                return <Question key={idx}>{l.question}</Question>;
            }
        })}

        <AnswerZone>
            <Answer>{"O "}</Answer>
            <Answer>{" X"}</Answer>
        </AnswerZone>
        
        {props.list.map((l, idx) => {
            if (idx === num) {
                return <SwipeItem key={idx} onSwipe={onSwipe} />;
            }
        })}
        </QuizContainer>
    );
};

const QuizContainer = styled.div`
    text-align: center;
    & > p > span {
        padding: 8px 16px;
        background-color: #fef5d4;
        // border-bottom: 3px solid #ffd6aa;
        border-radius: 30px;
    }
`;

const Question = styled.h1`
    font-size: 1.5em;
`;

const AnswerZone = styled.div`
    width: 100%;
    display: flex;
    flex-direction: row;
    min-height: 70vh;
`;

const Answer = styled.div`
    width: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 100px;
    font-weight: 600;
    color: #dadafc77;
`;

const DragItem = styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    & > div {
    border-radius: 500px;
    background-color: #ffd6aa;
    }
    & img {
    max-width: 150px;
    }
`;

export default Quiz;

Score.js

import React from 'react';
import styled from 'styled-components';

const Score = (props) => {
    return (
        <ScoreContainer>
            <Text> <span>{props.name}</span> 퀴즈에 <br/>
            대한 내 점수는?
            </Text>
            <MyScore>
                <span>100</span><p>{props.scoreMsg}</p>
            </MyScore>

            <Button>랭킹보기</Button>
        </ScoreContainer>
    );
};

const ScoreContainer = styled.div`
    display: flex;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    padding: 16px;
    box-sizing: border-box;
    flex-direction: column;
    justify-content: center;
    align-items: center;
`;

const Text = styled.h1`
    font-size: 1.5em;
    margin: 0px;
    line-height: 1.4;
    & span {
        background-color: #fef5d4;
        padding: 5px 10px;
        border-radius: 30px;
    }
`;

const MyScore = styled.div`
    text-align: center;
    & span {
        background-color: #fef5d4;
        padding: 5px 10px;
        border-radius: 30px;
    }
    font-weight: 600;
    font-size: 2em;
    margin: 24px;
    & > p {
        margin: 24px 0;
        font-size: 16px;
        font-weight: 400;
    }
`;

const Button = styled.button`
    padding: 8px 24px;
    background-color: #dadafc;
    border-radius: 80px;
    margin: 8px;
    border: 1px solid #dadafc;
    width: 80vw;
`;

export default Score;

Start.js

import React from "react";
import img from "./scc_img01.png";

const Start = (props) => {

    return (
        <div
            style={{
                display: "flex",
                height: "100vh",
                width: "100vw",
                overflow: "hidden",
                padding: "16px",
                boxSizing: "border-box",
            }}
        >
        <div
            className="outter"
            style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                flexDirection: "column",
                height: "100vh",
                width: "100vw",
                overflow: "hidden",
                padding: "0px 10vw",
                boxSizing: "border-box",
                maxWidth: "400px",
            }}
        >
        <img src={img} style={{ width: "80%", margin: "16px" }} />
        <h1 style={{ fontSize: "1.5em", margin: "0px", lineHeight: "1.4" }}>
            나는{" "}
            <span
                style={{
                backgroundColor: "#fef5d4",
                padding: "5px 10px",
                borderRadius: "30px",
                }}
            >
                {props.name}
            </span>
            에 대해 얼마나 알고 있을까?
        </h1>

        <input
            type="text"
            style={{
                padding: "10px",
                margin: "24px 0px",
                border: "1px solid #dadafc",
                borderRadius: "30px",
                width: "100%",
                // backgroundColor: "#dadafc55",
            }}
            placeholder="내 이름"
        />
        <button
            style={{
                padding: "8px 24px",
                backgroundColor: "#dadafc",
                borderRadius: "30px",
                border: "#dadafc",
            }}
        >
            시작하기
        </button>
        </div>
        </div>
    );
};

export default Start;

SwipeItem.js

import React from "react";
import styled from "styled-components";
import img from "./scc_img01.png";

const SwipeItem = React.memo(({ onSwipe }) => {
    const swipe_div = React.useRef(null);
        
    let swipe_status = "ready";
    let target_classname = "";
    let coordinate = {
        start_x: 0,
        start_y: 0,
        end_x: 0,
        end_y: 0,
    };

    React.useEffect(() => {
        const reset = () => {
        swipe_status = "ready";
        coordinate = {
            start_x: 0,
            start_y: 0,
            end_x: 0,
            end_y: 0,
        };
        swipe_div.current.className = target_classname;
        swipe_div.current.style.left = 0 + "px";
        swipe_div.current.style.top = 0 + "px";
        };

        const touchStart = (e) => {

            swipe_status = "touchstart";
            target_classname = swipe_div.current.className;

            coordinate = {
                ...coordinate,
                start_x: e.touches[0].clientX,
                start_y: e.touches[0].clientY,
            };
        };

        const touchEnd = (e) => {
            swipe_status = "touchend";
    
            coordinate = {
                ...coordinate,
                end_x: e.changedTouches[0].clientX,
                end_y: e.changedTouches[0].clientY,
            };

        let diff_x = coordinate.end_x - coordinate.start_x;
        let direct = "left";

        if (Math.abs(diff_x) > 50) {
            swipe_div.current.className = target_classname + " swipe";
        
            if (diff_x > 0) {
                direct = "right";
                swipe_div.current.style.left = diff_x + 150 + "px";
                swipe_div.current.style.opacity = 0;
            } else {
                direct = "left";
                swipe_div.current.style.left = diff_x - 150 + "px";
                swipe_div.current.style.opacity = 0;
            }

            window.setTimeout(() => {
                reset();
                onSwipe(direct);
            }, 300);

            return;
        }
        reset();
    };

    const touchMove = (e) => {
        e.preventDefault();

        let current_coordinate = {
            x: e.touches[0].clientX,
            y: e.touches[0].clientY,
        };

        swipe_div.current.style.left =
        current_coordinate.x - coordinate.start_x + "px";
        swipe_div.current.style.top =
        current_coordinate.y - coordinate.start_y + "px";
    };

    const touchCancel = (e) => {
        swipe_status = "cancel";
        reset();
    };

    swipe_div.current.addEventListener("touchstart", touchStart);
    swipe_div.current.addEventListener("touchend", touchEnd);
    swipe_div.current.addEventListener("touchmove", touchMove);
    swipe_div.current.addEventListener("touchcancel", touchCancel);

    return () => {
        if (!swipe_div.current) {
            return;
        }

        swipe_div.current.removeEventListener("touchstart", touchStart);
        swipe_div.current.removeEventListener("touchend", touchEnd);
        swipe_div.current.removeEventListener("touchmove", touchMove);
        swipe_div.current.removeEventListener("touchcancel", touchCancel);
    };

}, []);

    return (
        <DragItem ref={swipe_div}>
            <img src={img} />
        </DragItem>
    );
});

const DragItem = styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    &.swipe {
        transition: 300ms;
    }
    & > div {
        border-radius: 500px;
        background-color: #ffd6aa;
    }
    & img {
        max-width: 150px;
    }
`;

SwipeItem.defaultProps = {
    onSwipe: (direction) => {},
};

export default SwipeItem;

0개의 댓글

관련 채용 정보