티스토리 뷰

 

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

const deselectedOptions = [
  'rustic',
  'antique',
  'vinyl',
  'vintage',
  'refurbished',
  '신품',
  '빈티지',
  '중고A급',
  '중고B급',
  '골동품'
];
//드롭다운 메뉴에서 선택 가능한 검색어 목록

/* TODO : 아래 CSS를 자유롭게 수정하세요. */
const boxShadow = '0 4px 6px rgb(32 33 36 / 28%)';
const activeBorderRadius = '1rem 1rem 0 0';
const inactiveBorderRadius = '1rem 1rem 1rem 1rem';
//검색어 입력창과 드롭다운 메뉴에 적용될 그림자 스타일
//검색어 입력창과 드롭다운 메뉴의 활성화 상태에서 적용될 border radius 스타일
//inactiveBorderRadius : 검색어 입력창과 드롭다운 메뉴의 비활성화 상태에서 적용될 border radius 스타일

export const InputContainer = styled.div`
  margin-top: 8rem;
  background-color: pink;
  display: flex;
  flex-direction: row;
  padding: 1rem;
  border: 1px solid rgb(223, 225, 229);
  border-radius: ${inactiveBorderRadius};
  z-index: 3;
  box-shadow: 0;

  &:focus-within {
    box-shadow: ${boxShadow};
    border-radius: ${activeBorderRadius};
  }

  > 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:hover {
    background-color: lightgray;
    }  


  > li {
    padding: 0 1rem;
  }

  &.selected {
      background-color: lightgray;
    }
`;
//검색 결과를 보여주는 드롭다운 메뉴를 감싸는 컨테이너
// 드롭다운 메뉴 내부에는 검색 결과 항목이 포함


export const Autocomplete = () => {

  const [hasText, setHasText] = useState(false);
  const [inputValue, setInputValue] = useState('');
  const [options, setOptions] = useState(deselectedOptions);
 

  // useEffect를 아래와 같이 활용할 수도 있습니다.
  //useEffect를 활용하여 inputValue가 변경될 때마다 실행
  useEffect(() => {
    if (inputValue === '') {
      setHasText(false);
      setOptions([]);
    }
    //inputValue의 값이 비어있을 경우, hasText를 false로, options를 빈 배열로 설정

    if(inputValue !== ''){ //input값을 입력하면
      setOptions(deselectedOptions.filter((el) => { 
        return el.includes(inputValue)
        })
      )
    }
    // inputValue에 값이 입력되면 deselectedOptions 배열 중, 
    //inputValue가 포함된 항목들로 구성된 새로운 배열을 options에 설정
  }, [inputValue]);

  // TODO : input과 dropdown 상태 관리를 위한 handler가 있어야 합니다.
  const handleInputChange = (event) => {
  
    setInputValue(event.target.value); //inputValue를 입력된 값으로 바꿔준다.
    setHasText(true); 
  };

  const handleDropDownClick = (clickedOption) => {
 
    setInputValue(clickedOption)
  };

  const handleDeleteButtonClick = () => {

    setInputValue("")
  };

  // Advanced Challenge: 상하 화살표 키 입력 시 dropdown 항목을 선택하고, Enter 키 입력 시 input값을 선택된 dropdown 항목의 값으로 변경하는 handleKeyUp 함수를 만들고,
  // 적절한 컴포넌트에 onKeyUp 핸들러를 할당합니다. state가 추가로 필요한지 고민하고, 필요 시 state를 추가하여 제작하세요.

 

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

export const DropDown = ({ options, handleComboBox }) => {
  return (
    <DropDownContainer>
     
      {options.map((option, idx) => {
          return <li 
                   key={idx}
                   onClick={() => handleComboBox(option)}
             
                   >{option}</li>
        })}
    </DropDownContainer>
  );
};

 

:focus-within은 요소 내부의 하나 이상의 요소가 포커스를 받았을 때 적용되는 가상 클래스

&:focus-within은 CSS의 가상 클래스 중 하나로, 특정 요소 내부의 자식 요소 중 하나가 포커스를 받을 때 해당 요소에 스타일을 적용할 수 있는 방법

InputContainer 요소 내부의 input 요소에 포커스가 있을 때, InputContainer의 border-radius 속성이 변경되는 것을 확인

inputValue에 입력된 값이 options 배열에서 필터링된 결과로 추천 항목으로 출력되는 기능을 수행

autocomplete

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

export const InputBox = styled.div`
  text-align: center;
  display: inline-block;
  width: 150px;
  height: 30px;
  border: 1px #bbb dashed;
  border-radius: 10px;
  margin-left: 1rem;
`;

export const InputEdit = styled.input`
  text-align: center;
  display: inline-block;
  width: 150px;
  height: 30px;
`;

export const InputView = styled.div`
  text-align: center;
  align-items: center;
  margin-top: 3rem;

  div.view {
    margin-top: 3rem;
  }
`;
//MyInput 컴포넌트
//MyInput 컴포넌트는 입력된 값을 수정할 수 있는 입력 상자를 렌더링
//사용자가 MyInput의 값을 클릭하면 상자가 수정 가능한 상태로 전환되고, 
//수정이 완료되면 새로운 값이 저장
export const MyInput = ({ value, handleValueChange }) => {
  //컴포넌트는 MyInput 이라는 이름으로 선언되며, 입력값(value)과 입력값이 변경되었을 때 호출될 
  //콜백 함수(handleValueChange)를 속성(props)으로 받는다.
  const inputEl = useRef(null);
  //useRef를 사용하여 inputEl이라는 변수에 null 값을 할당
  // inputEl 변수는 input 요소를 참조한다.
  const [isEditMode, setEditMode] = useState(false);
  //현재 입력 상태(Edit 모드)인지를 나타내는 boolean 값
  const [newValue, setNewValue] = useState(value);
  //현재 입력된 값(value)을 나타내는 문자열

  useEffect(() => {
    if (isEditMode) {
      inputEl.current.focus();
    }
  }, [isEditMode]);
 //useEffect는 isEditMode가 변경될 때마다 실행된다.
 //isEditMode가 true이면, inputEl.current.focus()를 호출하여
 // input 요소에 자동으로 포커스를 설정
 //이를 통해 해당 요소를 클릭하면 자동으로 편집 모드로 전환

  useEffect(() => {
    setNewValue(value);
  }, [value]);
  //value의 값이 변경될 때마다 newValue 값을 업데이트

  const handleClick = () => {
    // TODO : isEditMode 상태를 변경합니다.
    setEditMode(true)
  };

  const handleBlur = () => {
    // TODO : Edit가 불가능한 상태로 변경합니다.
    setEditMode(false)
    handleValueChange(newValue);
  };

  const handleInputChange = (e) => {
    // TODO : 저장된 value를 업데이트합니다.
    setNewValue(e.target.value)
  };

  return (
    <InputBox>
      {isEditMode ? (
        <InputEdit
          type='text'
          value={newValue}
          ref={inputEl}
          // TODO : 포커스를 잃으면 Edit가 불가능한 상태로 변경되는 메소드가 실행되어야 합니다.
          onBlur={handleBlur}
          // TODO : 변경 사항이 감지되면 저장된 value를 업데이트 되는 메소드가 실행되어야 합니다.
          onChange={handleInputChange}
        />
        //입력값을 변경할 수 있는 입력 요소
      ) : (
        <span 
          onClick={handleClick}
        // TODO : 클릭하면 Edit가 가능한 상태로 변경되어야 합니다.
        >{newValue}</span>
        //isEditMode가 false일 때 표시되는 텍스트 요소
      )}
    </InputBox>
    //isEditMode가 true이면 InputEdit 요소가 표시되며,
    // false이면 newValue의 값을 텍스트로 가지는 span 요소가 표시
  );
}

const cache = {
  name: '김코딩',
  age: 20
};
// ClickToEdit 컴포넌트
//lickToEdit 컴포넌트는 MyInput을 사용하여 이름과 나이를 수정
//MyInput은 name 및 age state 값을 사용하여 값이 수정
export const ClickToEdit = () => {
  const [name, setName] = useState(cache.name);
  const [age, setAge] = useState(cache.age);

  return (
    <>
      <InputView>
        <label>이름</label>
        <MyInput value={name} handleValueChange={(newValue) => setName(newValue)} />
      </InputView>
      <InputView>
        <label>나이</label>
        <MyInput value={age} handleValueChange={(newValue) => setAge(newValue)} />
      </InputView>
      {/*handleValueChange 콜백 함수를 전달받는다. handleValueChange 함수는 MyInput 컴포넌트에서 값이 변경될 때마다 호출되며,
       해당 값이 상위 컴포넌트의 상태를 업데이트한다.*/}
      <InputView>
        <div className='view'>이름 {name} 나이 {age}</div>
      </InputView>
      {/* InputView 컴포넌트를 한 번 더 렌더링하여 name과 age 값을 보여준다.
      InputView 컴포넌트에는 div.view 클래스가 있으며, 
       이 클래스를 이용하여 스타일을 적용 */}
    </>
  );

ClickToEdit

 

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

과제2 - Cmarket redux  (0) 2023.02.24
Unit4 - [React] 상태 관리  (0) 2023.02.23
과제 - React Custom Component Bare  (0) 2023.02.21
과제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
글 보관함