모달 기능을 활용해 페이지 위에 새로운 창을 띄워 콘텐츠를 보여줄 수 있다. 모달은 일반 팝업창과 다르게 이용자가 버튼 또는 링크를 클릭했을 때만 동작하며 모달을 닫기 전까지는 기존 화면과 상호작용할 수 없다.
import { useState } from "react";
import styled from "styled-components";
export const ModalContainer = styled.div` //기본적으로 해당 모달 컴포넌트의 상위 요소
display: flex;
justify-content: center;
align-items: center;
height: 100%;
`;
export const ModalBackdrop = styled.div` //=> 화면에 구현되는 ModalView의 배경으로 ModalView를 포함함
// TODO : Modal이 떴을 때의 배경을 깔아주는 CSS를 구현합니다.
background: rgba(0, 0, 0, 0.5);
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
`;
export const ModalBtn = styled.button` // ModalBtn 버튼을 클릭하면 ModalView와 ModalBackdrop가 화면에 구현이 된다.
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) => ({
// attrs 메소드를 이용해서 아래와 같이 div 엘리먼트에 속성을 추가할 수 있습니다.
role: "dialog",
}))`
// TODO : Modal창 CSS를 구현합니다.
background-color: white;
width: 300px;
height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
//=> isOpen을 통해 열리고 닫고를 정한다
const openModalHandler = () => {
// TODO : isOpen의 상태를 변경하는 메소드를 구현합니다.
setIsOpen(!isOpen);
};
//=> openModalHandler 함수를 통해 isOpen의 boolean 값을 조절한다.
return (
<>
<ModalContainer>
<ModalBtn onClick={openModalHandler}>
{isOpen ? "opened" : "plz click"}
</ModalBtn>
{isOpen ? (
// 모달 배경화면을 누르면 setIsOpen(!isOpen)가 된다.
<ModalBackdrop onClick={openModalHandler}>
<ModalView>
<div className="close-btn">×</div>모달
</ModalView>
</ModalBackdrop>
) : null}
</ModalContainer>
</>
);
};
탭은 인터페이스 요소 중 하나이며, 탭 인터페이스가 적용된 경우 한 창에 여러 문서를 열 수 있으며, 한 창에 한 탭만 표시하여 사용자가 현재 어떤 문서를 보고 있는지 알 수 있다.
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-content: center;
align-items: center;
list-style: none;
margin-bottom: 7rem;
.submenu {
width: 200px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
background: #f2f2f2;
border: 1px solid #424448;
cursor: pointer;
}
.focused {
background: #cffc;
}
& div.desc {
text-align: center;
}
`;
const Desc = styled.div`
text-align: center;
`;
export const Tab = () => {
// TIP: Tab Menu 중 현재 어떤 Tab이 선택되어 있는지 확인하기 위한
// currentTab 상태와 currentTab을 갱신하는 함수가 존재해야 하고, 초기값은 0 입니다.
const [currentTab, changeTab] = useState(0);
//=> currentTab은 현재 선택된 탭을 표시한다.
const menuArr = [
{ name: "Tab1", content: "Tab menu ONE" },
{ name: "Tab2", content: "Tab menu TWO" },
{ name: "Tab3", content: "Tab menu THREE" },
];
//=> 배열에 각탭의 name과 content를 저장함
const selectMenuHandler = (index) => {
changeTab(index);
};
//=> 배열의 인덱스를 통해 섹션을 구분함
return (
<>
<div>
<TabMenu>
//=> map을 통해 menuArr을 li로 변환한다.
{menuArr.map((ele, index) => {
return (
<li
key={index}
className={currentTab === index ? "submenu focused" : "submenu"}
onClick={() => selectMenuHandler(index)}
>
{ele.name}
</li>
);
})}
{/*TIP: li 엘리먼트의 class명의 경우 선택된 tab 은 'submenu focused' 가 되며,
나머지 2개의 tab은 'submenu' 가 됩니다.*/}
</TabMenu>
<Desc>
{/*TODO: 아래 하드코딩된 내용 대신에, 현재 선택된 메뉴 따른 content를 표시하세요*/}
<h1>{menuArr[currentTab].content}</h1>
</Desc>
</div>
</>
);
};
Toggle
on/off를 설정할 때 사용하는 버튼으로 사용자가 on/off시 변화하는 시각적인 효과를 통해 on/off 여부를 알 수 있게 한다.
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를 구현합니다.
}
> .toggle-container.toggle--checked {
background: #fcf;
}
> .toggle-circle { //=> 시각적 변화를 통해 ON/OFF를 알려주는 버튼모양
position: absolute;
top: 1px;
left: 1px;
width: 22px;
height: 22px;
border-radius: 50%;
background-color: #ffffff;
// TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
}
> .toggle-circle.toggle--checked {
left: 26px;
}
`;
const Desc = styled.div`
// TODO : 설명 부분의 CSS를 구현합니다.
`;
export const Toggle = () => {
const [isOn, setisOn] = useState(false);
//=> isOn을 통해 on,off 여부를 정함
const toggleHandler = () => {
setisOn(!isOn);boolean 값을 조절한다.
// toggleHandler함수를 통해 isOn의 boolean 값을 조절한다.
};
return (
<>
<ToggleContainer
// TODO : 클릭하면 토글이 켜진 상태(isOn)를 boolean 타입으로 변경하는 메소드가 실행되어야 합니다.
onClick={toggleHandler}
>
{/* TODO : 아래에 div 엘리먼트 2개가 있습니다. 각각의 클래스를 'toggle-container', 'toggle-circle' 로 지정하세요. */}
{/* TIP : Toggle Switch가 ON인 상태일 경우에만 toggle--checked 클래스를 div 엘리먼트 2개에 모두 추가합니다. 조건부 스타일링을 활용하세요. */}
<div className={`toggle-container ${isOn ? "toggle--checked" : ""}`} />
<div className={`toggle-circle ${isOn ? "toggle--checked" : ""}`} />
</ToggleContainer>
<Desc>토글 스위치 {isOn ? "ON" : "OFF"}</Desc>
{/* TIP: Toggle Switch가 ON인 상태일 경우에 Desc 컴포넌트 내부의 텍스트를 'Toggle Switch ON'으로, 그렇지 않은 경우 'Toggle Switch OFF'가 됩니다. 조건부 렌더링을 활용하세요. */}
</>
);
};
블로그나 게시판에서 게시물을 작성 시 해당 게시물의 특징을 나타낼 수 있는 키워드를 붙이는 기능 이러한 태그는 게시물의 분류에 사용하거나, 검색어로서 기능을 수행한다
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: var(--coz-purple-600);
>.tag-title{
color:white
//=> 태그 글자색
}
> .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 [value, setValue] = useState();
const removeTags = (indexToRemove) => {
// TODO : 태그를 삭제하는 메소드를 완성하세요.
setTags(tags.filter((ele, i) => i !== indexToRemove));
};
const addTags = (event) => {
// TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요.
// 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
// - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
// - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
// - 태그가 추가되면 input 창 비우기
if (event.key === "Enter" && !tags.includes(value) && value) {
setTags([...tags, value]);
setValue();
} else if (event.key === "Enter" && !value) {
setValue();
}
};
const changeHandler = (ele) => {
setValue(ele.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
{/* TODO : tag-close-icon이 tag-title 오른쪽에 x 로 표시되도록 하고,
삭제 아이콘을 click 했을 때 removeTags 메소드가 실행되어야 합니다. */}
</span>
</li>
))}
</ul>
<input
className="tag-input"
type="text"
onKeyUp={(event) => addTags(event)}
onChange={changeHandler}
value={value || ""}
placeholder="Press enter to add tags"
/>
</TagsInput>
</>
);
};