티스토리 뷰
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}>×</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 배열에서 필터링된 결과로 추천 항목으로 출력되는 기능을 수행
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 클래스가 있으며,
이 클래스를 이용하여 스타일을 적용 */}
</>
);
'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 |