Module
import { useState } from 'react';
import styled from 'styled-components';
export const ModalContainer = styled.div`
// TODO : Modal을 구현하는데 전체적으로 필요한 CSS를 구현합니다.
display: flex;
justify-content: center;
align-items: center;
height: 100%;
position : relative;
`;
export const ModalBackdrop = styled.div`
// TODO : Modal이 떴을 때의 배경을 깔아주는 CSS를 구현합니다.
position: fixed;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
top:0;
left:0;
right:0;
bottom:0;
margin:0;
background-color: rgba(0,0,0,0.4);
z-index:1;
`;
export const ModalBtn = styled.button`
background-color: var(--coz-purple-600);
text-decoration: none;
border: none;
padding: 20px;
color: white;
border-radius: 30px;
cursor: grab;
`;
export const ModalView = styled.div.attrs((props) => ({
role: 'dialog',
}))`
// TODO : Modal창 CSS를 구현합니다.
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 1rem;
background-color: #fff;
width: 500px;
height: 200px;
border: 3px solid lightblue;
>.close-btn{
position: absolute;
top:2px;
right:7px;
cursor: pointer;
}
`;
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
const openModalHandler = (e) => {
setIsOpen(!isOpen)
};
return (
<>
<ModalContainer>
<ModalBtn onClick={openModalHandler}
>
{isOpen ? 'Opened' : 'Open Modal'}
{}
{}
</ModalBtn>
{isOpen === false ? null :
<ModalBackdrop onClick={openModalHandler}>
<ModalView onClick={e => e.stopPropagation()}>
<div className='close-btn' onClick={openModalHandler}>×</div>
<div>Hello</div>
<div>World</div>
</ModalView>
</ModalBackdrop>
}
{}
</ModalContainer>
</>
);
};
Toggle
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, #8b8b8b 50%, blue 50%) right;
background-size: 200%;
transition: 1s;
&.toggle--checked{
background-position: left;
background: linear-gradient(to right, blue 50%, #8b8b8b 50%) left;
background-size: 200%;
transition: 1s;
}
}
> .toggle-circle {
position: absolute;
top: 1px;
left: 1px;
width: 22px;
height: 22px;
border-radius: 50%;
background-color: #ffffff;
// TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
transition: 0.5s;
&.toggle--checked {
left: 27px;
transition: 0.5s;
}
}
`;
const Desc = styled.div`
// TODO : 설명 부분의 CSS를 구현합니다.
display: flex;
justify-content: center;
margin-top: 0.5rem;
`;
export const Toggle = () => {
const [isOn, setIsOn] = useState(false);
const toggleHandler = () => {
setIsOn(!isOn)
};
return (
<>
<ToggleContainer
onClick={toggleHandler}>
{}
{}
<div className={`toggle-container ${isOn ? 'toggle--checked' : null}`} />
<div />
<div className={`toggle-circle ${isOn ? 'toggle--checked' : null}`} />
<div />
</ToggleContainer>
{}
{}
{isOn ?
<Desc><div className='switch--On'>Toggle Switch On</div></Desc> :
<Desc><div className='switch--Off'>Toggle Switch Off</div></Desc>
}
</>
);
};
Tab
import { useState } from 'react';
import styled from 'styled-components';
const TabMenu = styled.ul`
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;
height: 40px;
.submenu {
${'' }
display: flex;
justify-content: center;
flex-grow: 1;
cursor: pointer;
}
.focused {
${'' }
background-color: blue;
color: white;
height: 100%;
display: flex;
align-items: center;
transition: 0.5s;
}
& div.desc {
text-align: center;
}
`;
const Desc = styled.div`
text-align: center;
`;
export const Tab = () => {
const menuArr = [
{ name: 'Tab1', content: 'Tab menu ONE' },
{ name: 'Tab2', content: 'Tab menu TWO' },
{ name: 'Tab3', content: 'Tab menu THREE' },
];
const [currentTab, setCurrentTab] = useState(0)
const selectMenuHandler = (index) => {
console.log(index)
setCurrentTab(index);
};
return (
<>
<div>
<TabMenu>
{}
{}
{menuArr.map((item, key) => {
return <li key={key}
className={`${key === currentTab ? 'submenu focused' : 'submenu'}`}
onClick={() => selectMenuHandler(key)}>{item.name}</li>
})}
</TabMenu>
<Desc>
{}
<p>{menuArr[currentTab].content}</p>
</Desc>
</div>
</>
);
};
Tag
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 initialTags = ['CodeStates', 'kimcoding'];
const [tags, setTags] = useState(initialTags);
const removeTags = (indexToRemove) => {
setTags(tags.filter(tag => {
return tag !== tags[indexToRemove]
}))
};
const addTags = (e) => {
const value = e.target.value.trim();
if (e.key === 'Enter' && !tags.includes(value) && value) {
setTags([...tags, value])
e.target.value = ''
} else if (e.key === 'Enter' && !value) {
e.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)}>×
{}
</span>
</li>
))}
</ul>
<input
className="tag-input"
type="text"
onKeyUp={(e) => addTags(e)}
placeholder="Press enter to add tags"
/>
</TagsInput>
</>
);
};
AutoCompite
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: ${activeBorderRadius};
z-index: 3;
box-shadow: 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: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);
const [selected, setSelected] = useState(-1);
useEffect(() => {
if (inputValue === '') {
setHasText(false);
setOptions([])
} else {
if (inputValue !== '') {
setOptions(deselectedOptions.filter(e => {
return e.includes(inputValue)
}))
setHasText(true);
}
}
}, [inputValue]);
const handleInputChange = (e) => {
setInputValue(e.target.value);
setHasText(true)
}
const handleDropDownClick = (clickedOption) => {
setInputValue(clickedOption)
};
const handleDeleteButtonClick = () => {
setInputValue('')
};
const handleKeyUp = (event) => {
if (hasText) {
if (event.key === 'ArrowDown' && options.length - 1 > selected) {
setSelected(selected + 1);
}
if (event.key === 'ArrowUp' && selected >= 0) {
setSelected(selected - 1);
}
if (event.key === 'Enter' && selected >= 0) {
handleDropDownClick(options[selected])
setSelected(-1);
}
}
}
return (
<div className='autocomplete-wrapper'>
{/* TODO : input 엘리먼트를 작성하고 input값(value)을 state와 연결합니다.
handleInputChange 함수와 input값 변경 시 호출될 수 있게 연결합니다. */}
{}
<InputContainer>
<input type='text'
value={inputValue}
defaultValue={inputValue}
onChange={handleInputChange}
onKeyUp={handleKeyUp}
>
</input>
<div className='delete-button' onClick={handleDeleteButtonClick}>×</div>
</InputContainer>
{}
{hasText && <DropDown options={options} handleComboBox={handleDropDownClick} selected={selected} />}
</div>
);
};
export const DropDown = ({ options, handleComboBox, selected }) => {
return (
<DropDownContainer>
{}
{options.map((item, key) => {
return (<li
key={key}
onClick={() => handleComboBox(item)}
className={selected === key ? 'selected' : ''}
>{item}</li>
)
})}
</DropDownContainer>
);
};
ClickToEdit
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;
}
`;
export const MyInput = ({ value, handleValueChange }) => {
const inputEl = useRef(null);
const [isEditMode, setEditMode] = useState(false);
const [newValue, setNewValue] = useState(value);
useEffect(() => {
if (isEditMode) {
inputEl.current.focus();
}
}, [isEditMode]);
useEffect(() => {
setNewValue(value);
}, [value]);
const handleClick = () => {
setEditMode(true)
};
const handleBlur = () => {
setEditMode(false)
handleValueChange(newValue);
};
const handleInputChange = (e) => {
setNewValue(e.target.value)
};
return (
<InputBox>
{}
{isEditMode ? (
<InputEdit
type='text'
value={newValue}
ref={inputEl}
onBlur={handleBlur}
onChange={handleInputChange}
/>
) : (
<span
onClick={handleClick}
>{newValue}</span>
)}
</InputBox>
);
}
const cache = {
name: '김코딩',
age: 20
};
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>
<InputView>
<div className='view'>이름 {name} 나이 {age}</div>
</InputView>
</>
);
};