1. Modal.js
import { useState } from "react";
import styled from "styled-components";
export const ModalContainer = styled.div`
// TODO : Modal을 감싸는 div css
display: flex;
justify-content: center;
align-items: center;
height: 100%;
`;
export const ModalBackdrop = styled.div`
// TODO : ModalPopup의 백그라운드 css
background-color: rgba(0, 0, 0, 0.3);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
`;
export const ModalBtn = styled.button`
/* transform: rotate(0.5turn); */
display: flex;
justify-content: center;
align-items: center;
background-color: #333;
text-decoration: none;
border: none;
padding: 20px;
color: white;
border-radius: 30px;
cursor: grab;
width: 150px;
height: 50px;
font-size: 16px;
`;
export const ModalView = styled.div.attrs((props) => ({
role: "dialog",
}))`
// TODO : Modal창 CSS를 구현합니다.
display: flex;
justify-content: center;
align-items: center;
background-color: white;
width: 300px;
height: 150px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
> button {
display: flex;
justify-content: end;
align-items: flex-end;
background-color: transparent;
border: none;
color: black;
font-size: 12px;
}
`;
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
const openModalHandler = () => {.
setIsOpen(!isOpen);
};
return (
<>
<ModalContainer>
<ModalBtn onClick={openModalHandler}>
{isOpen ? "Opened!" : "Open Modal"}
</ModalBtn>
{isOpen ? (
<ModalBackdrop onClick={openModalHandler}>
<ModalView
onClick={(e) => {
e.stopPropagation();
}}
>
<button onClick={openModalHandler}>X</button>
<div>ㅎㅇㅎㅇ</div>
</ModalView>
</ModalBackdrop>
) : null}
</ModalContainer>
</>
);
};
구현 Point
- styled-components의 css 작성 시 자식 노드는
> {}
사용
- styled.input.attrs()
- 삼항 연산자를 사용하여 true / false 값을 설정하는 방법
- 이벤트핸들러를 노드의 속성으로 부여할 때 빈 함수의 형태로 이벤트를 호출
2. 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;
&.toggle--checked {
background-color: gold;
transition: 0.5s;
}
// TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
}
> .toggle-circle {
position: absolute;
top: 1px;
left: 1px;
width: 22px;
height: 22px;
border-radius: 50%;
background-color: #ffffff;
// TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
&.toggle--checked {
left: 27px;
transition: 0.5s;
}
}
`;
const Desc = styled.div`
// TODO : 설명 부분의 CSS를 구현합니다.
display: flex;
align-items: center;
justify-content: center;
color: #1d1d1d;
font-size: 18px;
`;
export const Toggle = () => {
const [isOn, setisOn] = useState(false);
const toggleHandler = () => {
setisOn(!isOn);
};
return (
<>
<ToggleContainer
>
{}
{}
<div
onClick={toggleHandler}
className={`toggle-container ${isOn ? "toggle--checked" : ""}`}
/>
<div
onClick={toggleHandler}
className={`toggle-circle ${isOn ? "toggle--checked" : ""}`}
/>
</ToggleContainer>
{}
{}
<Desc>{isOn ? "Toggle Switch ON" : "Toggle Switch OFF"}</Desc>
</>
);
};
구현 Point
- 노드에 종속된 클래스나 상태를 추가할 때
&
사용.
3. Tab.js
import { useState } from "react";
import styled from "styled-components
const TabMenu = styled.ul`
background-color: #f2f2f2;
color: rgba(73, 73, 73, 0.5);
display: flex;
flex-direction: row;
justify-items: center;
align-items: center;
list-style: none;
margin-bottom: 7rem;
height: 60px;
.submenu {
position: relative;
display: inline-block;
font-size: 16px;
font-family: Arial, Helvetica, sans-serif;
font-weight: bold;
margin: 10px 10px 10px 30px;
color: #1d1d1d;
cursor: grab;
${"" }
}
.focused {
height: 1em;
border-bottom: 1px solid;
transition: 0.5s;
margin-top: 8px;
color: #ff7f00;
${"" };
}
& 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" },
];
const selectMenuHandler = (index) => {
setCurrentTab(index);
};
return (
<>
<div>
<TabMenu>
{}
{}
{menuArr.map((element, index) => {
return (
<li
key={index}
className={`${
currentTab === index ? "submenu focused" : "submenu"
}`}
onClick={() => selectMenuHandler(index)}
>
{element.name}
</li>
);
})}
</TabMenu>
<Desc>
{}
<p>{menuArr[currentTab].content}</p>
</Desc>
</div>
</>
);
};
구현 Point
- map 고차함수를 사용하여 배열 안의 객체 구조인 tab 메뉴를 불러옴
4. Tag.js
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: 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: var(--coz-purple-600);
> .tag-close-icon {
display: block;
width: 16px;
height: 16px;
line-height: 16px;
text-align: center;
font-size: 14px;
margin-left: 8px;
color: var(--coz-purple-600);
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 var(--coz-purple-600);
}
`;
export const Tag = () => {
const initialTags = ["CodeStates", "kimcoding"];
const [tags, setTags] = useState(initialTags);
const removeTags = (indexToRemove) => {
// TODO : 태그를 삭제하는 메소드를 완성하세요.
setTags(tags.filter((element, index) => index !== indexToRemove));
};
const addTags = (event) => {
if (
event.key === "Enter" &&
event.target.value !== "" &&
!tags.includes(event.target.value)
) {
setTags([...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
onClick={() => removeTags(index)}
className="tag-close-icon"
>
X
</span>
</li>
))}
</ul>
<input
className="tag-input"
type="text"
onKeyUp={(e) => {
addTags(e);
}}
placeholder="Press enter to add tags"
/>
</TagsInput>
</>
);
};
구현 Point
- 태그를 삭제하는 이벤트핸들러 작성 시 setTag의 인자로 filter()된 tag를 사용하여 출력된 X버튼이 가지고 있는 index값과 일치하는 태그는 걸러진다
- 태그를 추가할 때 조건문의 조건 설정을 명확하게 넣어주고
event.key === "Enter" && event.target.value !== "" && !tags.includes(event.target.value)
satTag의 인자로 spread된 새로운 배열을 전달setTags([...tags, event.target.value])
한다.