티스토리 뷰

Modal.js

import { useState } from 'react';
import styled from 'styled-components';

export const ModalContainer = styled.div`
  // TODO : Modal을 구현하는데 전체적으로 필요한 CSS를 구현합니다.
  display:flex;
  align-items:center;
  justify-content:center;
  //가운데 정렬 하지만 높이가 가운데가 되지 않았다. 이유는 부모가 높이가 없었다.
  height:950px;
   //그래서 부모에게 높이를 주었다.
   `;

export const ModalBackdrop = styled.div`
  // TODO : Modal이 떴을 때의 배경을 깔아주는 CSS를 구현합니다.
  display:flex;
  align-items:center;
  justify-content: center;
  height:100%;
  width:100%;
  
  position:fixed;
  background-color: lightblue;
`;

export const ModalBtn = styled.button`
  background-color: lightpink;
  text-decoration: none;
  border: none;
  padding: 20px;
  color: black;
  border-radius: 30px;
  cursor: grab;
  //올려놨을 때 마우스가 변함
`;

export const ModalView = styled.div.attrs((props) => ({
  // attrs 메소드를 이용해서 아래와 같이 div 엘리먼트에 속성을 추가할 수 있습니다.
  role: 'dialog',
  //styled.div은 div 태그를 스타일링 하기 위한 것이며, 
  //attrs() 메소드를 이용하여 div 엘리먼트에 role 속성을 추가하고 있다.
  //role 속성은 웹 접근성을 위한 것으로, 스크린 리더 사용자에게 모달 창임을 알려즌디/
}))`

  display: flex;
  align-items: center;
  justify-content: center;
  background-color: white;
  width:200px;
  height:100px;
  border-radius:30px;
  position: relative;
  //position: relative는 요소를 자신의 원래 위치를 기준으로
  //상대적으로 이동시키기 위해 사용하는 속성
  >.close-btn{ 
    position: absolute;
    top:3px;
    right:10px;
    cursor:pointer;
  }

`;

export const Modal = () => {
  const [isOpen, setIsOpen] = useState(false);

  const openModalHandler = () => {
    // TODO : isOpen의 상태를 변경하는 메소드를 구현합니다.
    setIsOpen(!isOpen);
  };

  return (
    <>
    <ModalContainer>
    <ModalBtn onClick={openModalHandler}>
      {isOpen ? 'Opened!' : 'Open Modal'} 
    </ModalBtn>


        {/* TODO : 클릭하면 Modal이 열린 상태(isOpen)를 boolean 타입으로 변경하는 메소드가 실행되어야 합니다.*/}
       
          {/* TODO : 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때는 ModalBtn의 내부 텍스트가 'Opened!' 로 Modal이 닫힌 상태(isOpen이 false인 상태)일 때는 ModalBtn 의 내부 텍스트가 'Open Modal'이 되도록 구현해야 합니다. */}
    

        {/* TODO : 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때만 모달창과 배경이 뜰 수 있게 구현해야 합니다. */}
        {isOpen ? <ModalBackdrop onClick={openModalHandler}>
        //isOpen이 true면 열렸다는 뜻
        //모달외 배경을 클릭하면 isOpen로 false로 닫힌다.
        <ModalView onClick={(event) => {event.stopPropagation()}}>
        {/*부모 컴포넌트에 이벤트 걸려있으면 자식 컴포넌트에도 이벤트가 걸려서 막기 위해서 stopPropagation()을 쓴다.*/}
          <div className="close-btn" onClick={openModalHandler}>&times;</div>
          //x를 클릭하면 isOpen로 false로 닫힌다.
          <div>HELLO CODESTATES!</div>
        </ModalView>
      </ModalBackdrop> : null}

    </ModalContainer>
    </>
  );
};

position:fixed

해당 요소를 고정 위치에 놓고 스크롤해도 화면상에서 고정되도록 만들어주는 속성

 

position: relative를 사용하면, 해당 요소는 일반적인 문서 흐름을 유지하면서, 이동할 위치를 기준으로 상대적으로 위치를 이동할 수 있습니다. 이때, top, right, bottom, left 속성을 사용하여 이동할 위치를 지정할 수 있습니다.

반면에 position: absolute를 사용하면, 해당 요소는 부모 요소를 기준으로 절대적인 위치를 이동할 수 있습니다. 즉, 일반적인 문서 흐름에서 벗어나서 위치를 지정할 수 있습니다. 이때, top, right, bottom, left 속성을 사용하여 위치를 지정

 

isOpen이 true여야 모달이 렌더링이 되는 건데 false가 되면 렌더링 되던게 사리는게 옳다.

 

event.stopPropagation() 함수는 이벤트 캡처링과 버블링을 막는 역할을 한다.

모달창 내부의 요소를 클릭했을 때 이벤트가 모달창 밖으로 전파되지 않도록 막아주는 역할을 한다.

 

modal

Toggle.js

import { useState } from 'react';
import styled from 'styled-components';

const ToggleContainer = styled.div`
  position: relative;
  margin-top: 8rem;
  left: 47%;
  cursor: pointer;

  > .toggle-container {
    width: 50px;
    height: 24px;
    border-radius: 30px;
    //background-color: #8b8b8b;
    // TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
    background-position: right;
    //배경 이미지의 위치를 컨테이너 오른쪽 끝으로 설정
    background: linear-gradient(to left, green 50%,pink 50%) right;
    //그라데이션을 왼쪽으로 향하도록 설정
    //green 색상으로 50%까지 채우고, 그 이후부터 pink 색상으로 50%까지 채우도록 설정
    //배경 이미지의 오른쪽 끝부터 그라데이션 배경을 채우도록 설정
      background-size: 200%;
      transition: 1s;
      &.toggle--check{
    background-position: left;
    background: linear-gradient(to right, pink 50%, green 50%) left;
    
      background-size: 200%;
      transition: 1s;
    }
  }

  > .toggle-circle {
    position: absolute;
    //.toggle-circle 클래스는 토글 버튼의 원형 부분을 나타내며, 
    //해당 요소는 position 속성을 absolute로 설정하여 부모 요소를 기준으로 상대적 위치를 지정
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: 100%;
    background-color: #ffffff;
    // TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
    transition: 1s;
    &.toggle--check {
    left: 30px;
    transition: 1s;
    }
  }
`;
//toggle-container와 toggle-circle은 각각 토글 스위치의 배경과 스위치 원형을 표시

const Desc = styled.div`
  // TODO : 설명 부분의 CSS를 구현합니다.
  display: flex;
  justify-content: center;
  //margin-top: 0.5rem;
`;
//Desc 컴포넌트는 토글 스위치의 상태를 설명하는 텍스트를 표시

export const Toggle = () => {
  const [isOn, setisOn] = useState(false);

  const toggleHandler = () => {
    // TODO : isOn의 상태를 변경하는 메소드를 구현합니다.
    setisOn(!isOn);
  };
  //toggleHandler 함수는 클릭할 때마다 isOn 변수의 상태를 변경하는 역할을 한다. 
  //ToggleContainer의 className 속성값에 isOn 변수의 상태에 따라 
  //toggle--checked 클래스를 추가하거나 제거함으로써, 
  //토글 스위치가 켜졌는지 꺼졌는지 시각적으로 나타낸다.

  return (
    <>
     <ToggleContainer onClick={toggleHandler}>
     //ToggleContainer는 토글 스위치 전체 영역을 감싸는 컨테이너 역할을 한다.
     //onClick 이벤트 핸들러를 등록하여, 
     //해당 영역을 클릭할 때마다 toggleHandler 함수를 호출
        <div className={`toggle-container ${isOn ? 'toggle--check' : ''}`}/>
        <div className={`toggle-circle ${isOn ? 'toggle--check' : ''}`}/>
      </ToggleContainer>
        {/*TODO : 클릭하면 토글이 켜진 상태(isOn)를 boolean 타입으로 변경하는 메소드가 실행되어야 합니다.*/}
   
        {/* TODO : 아래에 div 엘리먼트 2개가 있습니다. 각각의 클래스를 'toggle-container', 'toggle-circle' 로 지정하세요. */}
        {/* TIP : Toggle Switch가 ON인 상태일 경우에만 toggle--checked 클래스를 div 엘리먼트 2개에 모두 추가합니다. 조건부 스타일링을 활용하세요. */}
  
      {/* TODO : Desc 컴포넌트를 활용해야 합니다. */}
      {/* TIP:  Toggle Switch가 ON인 상태일 경우에 Desc 컴포넌트 내부의 텍스트를 'Toggle Switch ON'으로, 그렇지 않은 경우 'Toggle Switch OFF'가 됩니다. 조건부 렌더링을 활용하세요. */}
      <Desc><div>{isOn ? 'Toggle Switch ON' : 'Toggle Switch OFF'}</div></Desc>
      //isOn 변수의 상태에 따라 'Toggle Switch ON' 또는 'Toggle Switch OFF'가 표시
    </>
  );
};

toggle

 

Tab.js

import { useState } from 'react';
import styled from 'styled-components';

// TODO: Styled-Component 라이브러리를 활용해 TabMenu 와 Desc 컴포넌트의 CSS를 구현합니다.

const TabMenu = styled.ul`
  background-color: lightgray;
  color: black;
  font-weight: bold;
  border-radius : 20px;

  display: flex;
  flex-direction: row;
  justify-items: center;
  align-items: center;
  list-style: none;


  .submenu {
    ${'' /* 기본 Tabmenu 에 대한 CSS를 구현합니다. */}
    display: flex;
    justify-content: center;
    flex-grow: 1;
    cursor: pointer;
  }

  .focused {
    ${'' /* 선택된 Tabmenu 에만 적용되는 CSS를 구현합니다.  */}
    background-color: pink;
    color: white;
    height: 100%;
    display: flex;
    align-items: center;
    transition: 1s;
  }

  & div.desc {
    text-align: center;
  }
`;

const Desc = styled.div`
  text-align: center;
`;

export const Tab = () => {
  // TIP: Tab Menu 중 현재 어떤 Tab이 선택되어 있는지 확인하기 위한
  // currentTab 상태와 currentTab을 갱신하는 함수가 존재해야 하고, 초기값은 0 입니다.

  const menuArr = [
    { name: 'Tab1', content: 'Tab menu ONE' },
    { name: 'Tab2', content: 'Tab menu TWO' },
    { name: 'Tab3', content: 'Tab menu THREE' },
  ];
  //menuArr 상수 배열에는 탭 메뉴의 이름과 내용이 객체 형태로 저장
  const [currentTab,setCurrentTab] = useState(0);
  //currentTab과 setCurrentTab은 useState를 이용하여 
  //초기값 0을 가진 상태 변수와 해당 상태를 갱신할 수 있는 함수
  //currentTab은 현재 선택된 탭 메뉴의 인덱스를 저장하는 상태값
  //초기 값으로는 첫 번째 탭 메뉴(인덱스 0)가 선택

  const selectMenuHandler = (index) => {
    // TIP: parameter로 현재 선택한 인덱스 값을 전달해야 하며, 이벤트 객체(event)는 쓰지 않습니다
    // TODO : 해당 함수가 실행되면 현재 선택된 Tab Menu 가 갱신되도록 함수를 완성하세요.
    setCurrentTab(index);
  };
  //selectMenuHandler 함수는 인덱스 값을 매개변수로 받아와
  //해당 인덱스 값에 해당하는 탭 메뉴를 현재 선택된 탭 메뉴로 설정하는 함수

//화면에 표시될 JSX 코드가 작성
  return (
    <>
      <div>
        <TabMenu>
          {/*TODO: 아래 하드코딩된 내용 대신에, map을 이용한 반복으로 코드를 수정합니다.*/}
          {/*TIP: li 엘리먼트의 class명의 경우 선택된 tab 은 'submenu focused' 가 되며, 
                  나머지 2개의 tab은 'submenu' 가 됩니다.*/}
                   {menuArr.map((el, index) => {
            return <li key={index} 
                       className={`${index === currentTab ? 'submenu focused' : 'submenu'}`}
                       onClick={() => selectMenuHandler(index)}>{el.name}</li>
          })}
          //TabMenu 컴포넌트 내부에는 menuArr 배열의 내용을 
          //map 메소드를 이용해 반복적으로 생성한 li 엘리먼트들이 포함
          
          //currentTab 조건에 따라 submenu focused 클래스가 추가되며, 
          //그렇지 않을 경우에는 submenu 클래스가 추가
          
          //li 엘리먼트를 클릭할 경우, 해당 인덱스에 대한 selectMenuHandler 함수가 호출
      {/*}   <li className="submenu">{menuArr[0].name}</li>
          <li className="submenu">{menuArr[1].name}</li>
        <li className="submenu">{menuArr[2].name}</li>*/}
        </TabMenu>
        <Desc>
          {/*TODO: 아래 하드코딩된 내용 대신에, 현재 선택된 메뉴 따른 content를 표시하세요*/}
          <p>{menuArr[currentTab].content}</p>
          //Desc 컴포넌트에는 menuArr[currentTab] 배열에서 
          //현재 선택된 인덱스에 해당하는 객체의 content를 표시하는 <p> 엘리먼트가 작성
        </Desc>
      </div>
    </>
  );
};

tab

Tag.js

outline: transparent는 웹 페이지에서 포커스를 받은 요소에 대한 아웃라인을 숨기는 CSS 속성

filter 메소드는 주어진 함수가 true를 반환하는 배열 요소만을 모아 새로운 배열을 만든다.

import { useState } from 'react';
import styled from 'styled-components';

// TODO: Styled-Component 라이브러리를 활용해 여러분만의 tag 를 자유롭게 꾸며 보세요!

export const TagsInput = styled.div`
  margin: 8rem auto;
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  min-height: 48px;
  width: 480px;
  padding: 0 8px;
  border: 1px solid rgb(214, 216, 218);
  border-radius: 30px;

  > ul {
    display: flex;
    flex-wrap: wrap;
    padding: 0;
    margin: 8px 0 0 0;

    > .tag {
      width: auto;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
      padding: 0 8px;
      font-size: 14px;
      list-style: none;
      border-radius: 6px;
      margin: 0 8px 8px 0;
      background: pink;
      > .tag-close-icon {
        display: block;
        width: 16px;
        height: 16px;
        line-height: 16px;
        text-align: center;
        font-size: 14px;
        margin-left: 8px;
        color: black;
        border-radius: 50%;
        background: #fff;
        cursor: pointer;
      }
    }
  }

  > input {
    flex: 1;
    border: none;
    height: 46px;
    font-size: 14px; 
    padding: 4px 0 0 0;
    :focus {
      outline: transparent;
    }
  }

  &:focus-within {
    border: 1px solid ;
  }
`;

export const Tag = () => {
  const initialTags = ['CodeStates', 'kimcoding'];

  const [tags, setTags] = useState(initialTags);
  const removeTags = (indexToRemove) => {
    // TODO : 태그를 삭제하는 메소드를 완성하세요.
    setTags(tags.filter((tag) =>{
      return tag !== tags[indexToRemove]
    }));
  };
  // tag와 tags[indexToRemove]를 비교하여, 제거할 인덱스를 제외한 나머지 태그만을 반환
//addTags 함수는 event 객체를 인자로 받아 태그를 추가하는 함수
//그를 추가하는 것뿐만 아니라, 기존 태그가 중복되지 않도록 체크하고, 
//입력 값이 없을 경우 실행되지 않도록 막는 것
  const addTags = (event) => {
    // TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요.
    // 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
    // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
    // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // - 태그가 추가되면 input 창 비우기
    let value = event.target.value.trim();
    //event.target.value로 입력한 값을 가져와 trim() 메서드로 
    //앞뒤 공백을 제거한 후 value 변수에 할당
   
    if(event.key === 'Enter' && !tags.includes(value) && value){
      setTags([...tags, value]);
      event.target.value ="";
    }
    //이후, 입력한 값이 빈 문자열이 아니고, 
    //tags 배열에 없는 새로운 값이면 setTags 함수를 통해 새로운 값을 tags 배열에 추가
    else if(event.key === 'Enter' && !value){
      event.target.value ="";
    }
    //마지막으로, event.target.value를 빈 문자열로 초기화하여 input 창을 비움

  };

  return (
    <>
    {/*tags 배열을 사용하여 ul 태그 안에 li 태그를 생성하고
    , map 함수를 사용하여 각각의 태그에 대한 요소를 생성
    각 태그 안에는 tag-title 클래스를 가진 span 태그와 tag-close-icon 클래스를 가진 span 태그
    
    tag-title 클래스를 가진 span 태그는 태그 이름을 나타내고,
     tag-close-icon 클래스를 가진 span 태그는 삭제 버튼 역할*/}

      <TagsInput>
        <ul id="tags">
          {tags.map((tag, index) => (
            <li key={index} className="tag">
              <span className="tag-title">{tag}</span>
              <span className="tag-close-icon"
                
                       onClick={() => removeTags(index)}>&times;     
              </span>
              {/* TODO :  tag-close-icon이 tag-title 오른쪽에 x 로 표시되도록 하고,
                            삭제 아이콘을 click 했을 때 removeTags 메소드가 실행되어야 합니다. */}
            </li>
          ))}
        </ul>

{/*마지막으로, input 태그를 사용하여 새로운 태그를 입력
className 속성으로 tag-input 클래스를 할당
onKeyUp 이벤트를 사용하여 Enter 키 입력 시 addTags 함수를 실행하도록 설정*/ }
        <input
          className="tag-input"
          type="text"
          onKeyUp={(event) => {
              /* 키보드의 Enter 키에 의해 addTags 메소드가 실행되어야 합니다. */
              addTags(event)
          }}
          placeholder="Press enter to add tags"
        />
      </TagsInput>
    </>
  );
};

tag

'codestates > section3' 카테고리의 다른 글

과제2 - Cmarket redux  (0) 2023.02.24
Unit4 - [React] 상태 관리  (0) 2023.02.23
과제 - React Custom Component advanced  (0) 2023.02.22
과제2 - Figma 클론  (0) 2023.02.17
과제1 - Figma 컴포넌트 구현  (0) 2023.02.16
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함