section3 custom-component

유희준·2023년 4월 20일
0

section3

목록 보기
2/5

modal.js

import { useState } from 'react'; //usestate 사용
import styled from 'styled-components'; //styled 사용

export const ModalBackdrop = styled.div` //모달 창 배경화면
//포지션을 고정시키고, z-index를 줌으로 뒤로 보냈다. 상하좌우 0값을 줌으로 화면전체 회색 화면이 차게 값줌. display 값은 그리드를 줌
  position: fixed; 
  z-index: 999;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(0,0,0,0.4);
  display: grid;
  place-items: center;
`;

export const ModalContainer = styled.div` //버튼이 들어갈 박스
  height: 15rem;
  text-align: center;
  margin: 120px auto;
`;

export const ModalBtn = styled.button`// 모달 버튼이다
// curosr: grab은 버튼에 마우스를 가져가면 움켜쥐는 아이콘이 뜬다
  background-color: #4000c7;
  text-decoration: none;
  border: none;
  padding: 20px;
  color: white;
  border-radius: 30px;
  cursor: grab;
`;

export const ModalView = styled.div.attrs(props => ({
  // attrs 메소드를 이용해서 아래와 같이 div 엘리먼트에 속성을 추가할 수 있습니다. "styled.div.attrs"는 "div" 요소의 속성을 설정하고, 이 경우에는 "role" 속성을 설정합니다. "props"는 컴포넌트에서 전달된 프로퍼티(property) 값을 나타냅니다.
  role: 'dialog'
}))`
    border-radius: 10px;
    background-color: #ffffff;
    width: 300px;
    height: 100px;
// span안에 close-btn css설정이다
    > span.close-btn {
      margin-top: 5px;
      cursor: pointer;
    }
// div안에 글귀이다
    > div.desc {
      margin-top: 25px;
      color: #4000c7;
    }
`;

export const Modal = () => { //Modal 컴포넌트 생성
  const [isOpen, setIsOpen] = useState(false);
  const openModalHandler = () => {
    setIsOpen(!isOpen); //모달 토글버튼 역할
  };
  return (
    <>
      <ModalContainer>
        <ModalBtn onClick={openModalHandler}> //버튼눌리면 작동
          {isOpen === false ? 'Open Modal' : 'Opened!'}
        </ModalBtn>
        {isOpen === true ? <ModalBackdrop onClick={openModalHandler}>
          <ModalView onClick={(e) => e.stopPropagation()}> //ModalView 컴포넌트가 클릭되었을 때 이벤트가 ModalView에서 처리되고, 상위 요소로 전파되지 않도록 방지하는 것
            <span onClick={openModalHandler} className='close-btn'>&times;</span>
            <div className='desc'>HELLO CODESTATES!</div>
          </ModalView>
        </ModalBackdrop> : null} // 재클릭시 null 값표현.
      </ModalContainer>
    </>
  );
};

tab.js

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

const TabMenu = styled.ul` //ul로 메뉴탭 표현
//배경에 submenu와 선택시 focused, 표시될 글이있다.(div.desc)
  background-color: #dcdcdc;
  color: rgba(73, 73, 73, 0.5);
  font-weight: bold;
  display: flex;
  flex-direction: row;
  justify-items: center;
  align-items: center;
  list-style: none;
  margin-bottom: 7rem;
  .submenu {
    width: 100%;
    padding: 15px 10px;
    cursor: pointer;
  }
  .focused {
    background-color: #4000c7;
    color: rgba(255, 255, 255, 1);
    transition: 0.3s;
  }
  & div.desc {
    text-align: center;
  }
`;

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

export const Tab = () => {
  const [currentTab, setCurrentTab] = useState(0);

  const menuArr = [
    { name: 'Tab1', content: 'Tab menu ONE' },
    { name: 'Tab2', content: 'Tab menu TWO' },
    { name: 'Tab3', content: 'Tab menu THREE' }
  ]; //tab 1,2,3 배열로 생성

  const selectMenuHandler = (index) => {
    setCurrentTab(index);
  }; //index값 전달

  return (
    <>
      <div>
        <TabMenu>
          {menuArr.map((ele, index) => {
            return (
              <li
                key={index}
                className={currentTab === index ? 'submenu focused' : 'submenu'} //현재탭과 인덱스가 같으면 포커스된다. 
                onClick={() => selectMenuHandler(index)}
              >
                {ele.name}//클릭하게되면 인덱스에 맞는 이름이 나온다.
              </li>
            );
          })}
        </TabMenu>
        <Desc>
          <p>{menuArr[currentTab].content}</p> //현재탭에 만는 내용이 나온다.
        </Desc>
      </div>
    </>
  );
};

TOGGLE

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

const ToggleContainer = styled.div`
//ToggleContainer 안에 toggle-container와 toggle-circle을 넣어준다. transition으로 움직이듯한 효과를 준다.
    position: relative;
    margin-top: 8rem;
    left: 47%;
    cursor: pointer;
    > .toggle-container {
        width: 50px;
        height: 24px;
        border-radius: 30px;
        background-color: #8b8b8b;
        transition: all .2s ease;
        &.toggle--checked {
            background-color: #4000c7;
        }
    }
    > .toggle-circle {
        position: absolute;
        top: 1px;
        left: 1px;
        width: 22px;
        height: 22px;
        border-radius: 50%;
        background-color: #fafafa;
        transition: all .25s ease;
        &.toggle--checked {
            left: 27px;
        }
    }
`;

const Desc = styled.div`
  text-align: center;
  margin-top: 1rem;
`;

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

  const toggleHandler = () => {
    setisOn(!isOn);
  };

  return (
    <>
      <ToggleContainer onClick={toggleHandler}>
        <div className={`toggle-container ${isOn ? 'toggle--checked' : ''}`} /> //랜더링되면 &.toggle--checked 동작        <div className={`toggle-circle ${isOn ? 'toggle--checked' : ''}`} />
      </ToggleContainer>
      {isOn ? <Desc>Toggle Switch ON</Desc> : <Desc>Toggle Switch OFF</Desc>}
    </>
  );
};

Tag.js

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

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: 6px;
  > 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: #4000c7;
      > .tag-close-icon {
        display: block;
        width: 16px;
        height: 16px;
        line-height: 16px;
        text-align: center;
        font-size: 14px;
        margin-left: 8px;
        color: #4000c7;
        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 #4000c7;
  }
`;

export const Tag = () => {
  // const selectedTags = (tags) => console.log(tags);
  const initialTags = ['CodeStates', 'kimcoding'];

  const [tags, setTags] = useState(initialTags);
  const removeTags = (indexToRemove) => {
    setTags(tags.filter((_, index) => index !== indexToRemove));
  };

  const addTags = (event) => {
    const filtered = tags.filter((el) => el === event.target.value);
    if (event.target.value !== '' && filtered.length === 0) {
      setTags([...tags, event.target.value]);
      // selectedTags([...tags, event.target.value]);
      event.target.value = '';
    }
  };

  return (
    <>
      <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>
            </li>
          ))}
        </ul>
        <input className='tag-input' type='text' onKeyUp={(event) => (event.key === 'Enter' ? addTags(event) : null)} placeholder='Press enter to add tags' />
      </TagsInput>
    </>
  );
};

Autocomplete.js

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

const deselectedOptions = [
  'rustic',
  'antique',
  'vinyl',
  'vintage',
  'refurbished',
  '신품',
  '빈티지',
  '중고A급',
  '중고B급',
  '골동품'
];

const boxShadow = '0 4px 6px rgb(32 33 36 / 28%)';
const activeBorderRadius = '1rem 1rem 0 0';
const inactiveBorderRadius = '1rem 1rem 1rem 1rem';

export const InputContainer = styled.div`
  margin-top: 8rem;
  background-color: #ffffff;
  display: flex;
  flex-direction: row;
  padding: 1rem;
  border: 1px solid rgb(223, 225, 229);
  border-radius: ${(props) =>
    props.hasText ? activeBorderRadius : inactiveBorderRadius};
  z-index: 3;
  box-shadow: ${(props) => (props.hasText ? boxShadow : 0)};
  &:focus-within {
    box-shadow: ${boxShadow};
  }
  > input {
    flex: 1 0 0;
    background-color: transparent;
    border: none;
    margin: 0;
    padding: 0;
    outline: none;
    font-size: 16px;
  }
  > div.delete-button {
    cursor: pointer;
  }
`;

export const DropDownContainer = styled.ul`
  background-color: #ffffff;
  display: block;
  margin-left: auto;
  margin-right: auto;
  list-style-type: none;
  margin-block-start: 0;
  margin-block-end: 0;
  margin-inline-start: 0px;
  margin-inline-end: 0px;
  padding-inline-start: 0px;
  margin-top: -1px;
  padding: 0.5rem 0;
  border: 1px solid rgb(223, 225, 229);
  border-radius: 0 0 1rem 1rem;
  box-shadow: ${boxShadow};
  z-index: 3;
  > li {
    padding: 0 1rem;
    &:hover {
      background-color: #eee;
    }
    &.selected {
      background-color: #ebe5f9;
    }
  }
`;

export const Autocomplete = () => {
  const [hasText, setHasText] = useState(false);
  const [inputValue, setInputValue] = useState('');
  const [options, setOptions] = useState(deselectedOptions);
  const [selected, setSelected] = useState(-1);

  useEffect(() => {
    if (inputValue === '') {
      setHasText(false);
    }
  }, [inputValue]);

  const handleInputChange = (event) => {
    const { value } = event.target;
    if (value.includes('\\')) return;

    // input에 텍스트가 있는지 없는지 확인하는 코드
    value ? setHasText(true) : setHasText(false);

    // updateText
    setInputValue(value);

    // dropdown을 위한 기능
    const filterRegex = new RegExp(value, 'i');
    const resultOptions = deselectedOptions.filter((option) =>
      option.match(filterRegex)
    );
    setOptions(resultOptions);
  };

  const handleDropDownClick = (clickedOption) => {
    setInputValue(clickedOption);
    const resultOptions = deselectedOptions.filter(
      (option) => option === clickedOption
    );
    setOptions(resultOptions);
  };

  const handleDeleteButtonClick = () => {
    setInputValue('');
  };

  const handleKeyUp = (event) => {
    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState#example
    // eslint-disable-next-line
    if (event.getModifierState("Fn") || event.getModifierState("Hyper") || event.getModifierState("OS") || event.getModifierState("Super") || event.getModifierState("Win")) return; if (event.getModifierState("Control") + event.getModifierState("Alt") + event.getModifierState("Meta") > 1) return;
    if (hasText) {
      if (event.code === 'ArrowDown' && options.length - 1 > selected) {
        setSelected(selected + 1);
      }
      if (event.code === 'ArrowUp' && selected >= 0) {
        setSelected(selected - 1);
      }
      if (event.code === 'Enter' && selected >= 0) {
        handleDropDownClick(options[selected]);
        setSelected(-1);
      }
    }
  };

  return (
    <div className='autocomplete-wrapper' onKeyUp={handleKeyUp}>
      <InputContainer hasText={hasText}>
        <input
          type='text'
          className='autocomplete-input'
          onChange={handleInputChange}
          value={inputValue}
        />
        <div className='delete-button' onClick={handleDeleteButtonClick}>
          &times;
        </div>
      </InputContainer>
      {hasText ? (
        <DropDown
          options={options}
          handleDropDownClick={handleDropDownClick}
          selected={selected}
        />
      ) : null}
    </div>
  );
};

export const DropDown = ({ options, handleDropDownClick, selected }) => {
  return (
    <DropDownContainer>
      {options.map((option, idx) => (
        <li
          key={idx}
          onClick={() => handleDropDownClick(option)}
          className={selected === idx ? 'selected' : ''}
        >
          {option}
        </li>
      ))}
    </DropDownContainer>
  );
};
profile
매일 뭐든하기

0개의 댓글