저번주 피그마 파트에서 배운 Modal, Toggle, Tab, Tag 를 이번주에 배운 CDD, styled Components, Storybook을 통해 React에서 UI 컴포넌트를 만드는 실습이었다!
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`
background-color: 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`
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를 구현합니다.
background-color: white;
width: 300px;
height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 30px;
>button {
background: none;
color: black;
font-size: 1rem;
border: none;
}
`;
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
const openModalHandler = () => {
// TODO : isOpen의 상태를 변경하는 메소드를 구현합니다.
setIsOpen(!isOpen)
};
return (
<>
<ModalContainer>
<ModalBtn onClick={openModalHandler}>
Open Modal
</ModalBtn>
{isOpen? (
<ModalBackdrop onClick={openModalHandler}>
<ModalView onClick={e=>e.stopPropagation()}>
<button>X</button>
<div>HELLO CODESTATES!</div>
</ModalView>
</ModalBackdrop>
) : null}
</ModalContainer>
</>
);
};
<div className={`toggle-container ${isOn ? "toggle--checked" : ""}`} />
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;
transition: 0.5s;
&.toggle--checked {
background-color: pulple;
transition: 0.5s;
}
}
> .toggle-circle {
position: absolute;
top: 1px;
left: 1px;
width: 22px;
height: 22px;
border-radius: 50%;
background-color: #ffffff;
transition: 0.5s;
&.toggle--checked {
left: 27px;
transition: 0.5s;
}
}
`;
const Desc = styled.div`
// TODO : 설명 부분의 CSS를 구현합니다.
text-align: center;
padding: 20px;
`;
export const Toggle = () => {
const [isOn, setisOn] = useState(false);
const toggleHandler = () => {
setisOn(!isOn)
};
return (
<>
<ToggleContainer>
<div onClick={toggleHandler} className={isOn? "toggle--checked toggle-container" : 'toggle-container'}/>
<div onClick={toggleHandler} className={isOn? "toggle--checked toggle-circle" : 'toggle-circle'}/>
</ToggleContainer>
<Desc>{isOn? 'Toggle Switch ON' : 'Toggle Switch OFF'}</Desc>
</>
);
};
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;
.submenu {
${'' /* 기본 Tabmenu 에 대한 CSS를 구현합니다. */}
padding: 10px;
width: calc(100% / 3);
transition: 0.5s;
}
.focused {
${'' /* 선택된 Tabmenu 에만 적용되는 CSS를 구현합니다. */}
background-color: var(--coz-purple-300);
transition: 0.5s;
}
& 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((el, index) => <li key={index} onClick={()=>selectMenuHandler(index)} className={index === currentTab ? 'submenu focused': "submenu"}>{el.name}</li>)}
</TabMenu>
<Desc>
<p>{menuArr[currentTab].content}</p>
</Desc>
</div>
</>
);
};
기본적으로 tags 배열 안의 모든 태그들이 화면에 보여야 합니다.
태그 이름 옆에 삭제 아이콘(x)이 표시되도록 하고, 아이콘을 클릭하면 해당 태그를 삭제하는 removeTags 메서드가 실행되어야 합니다.
removeTags 메서드가 삭제 아이콘(x)이 눌린 태그를 삭제하도록 removeTags 메서드를 완성해야 합니다.
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', 'abc', '가나다'];
const [tags, setTags] = useState(initialTags);
const removeTags = (indexToRemove) => {
// TODO : 태그를 삭제하는 메소드를 완성하세요.
setTags( tags.filter((el, index) => index !== indexToRemove) )
};
const addTags = (event) => {
console.log(event)
if (event.key === "Enter" && !tags.includes(event.target.value) && 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">&
</span>
</li>
))}
</ul>
<input
className="tag-input"
type="text"
onKeyUp={(event) => {
{
addTags(event)
}
}}
placeholder="Press enter to add tags"
/>
</TagsInput>
</>
);
};