<추가/수정 사항>
※수정 버튼 클릭 시 모달 창이 나오도록 구현
※pointer-events CSS를 이용해 모달 창 개/폐 여부에 따라 페이지 버튼 클릭 이벤트 허용/방지
-모달 창이 열리면 모달 창 외부 버튼을 클릭이 안되며 그대로 모달 창 닫힘
※이벤트 리스너를 이용해 모달 창 외부를 클릭하면 모달 창이 닫힘
-mousedown : HTML 요소 위에 마우스를 클릭 시 이벤트 발생
-.current.contains(event.target) : 특정 영역 외 클릭을 감지
※제목/내용/카테고리 수정
※수정 모달 창을 열 시 , 이미 저장된 제목/내용/카테고리를 기본 value로 지정
component
-Input.JSX
-Button.JSX
-Textarea.JSX
-DiaryWrite.JSX
-DiaryBlock.JSX
-Category.JSX
-CategoryButton.JSX
-Header.JSX
-Sidebar.JSX
-EditDiary.JSX
module
-Diary.JSX
css
-Diary.css
-Category.css
component - <EditDiary.JSX> : 일기 수정 모달 창
import React, {useEffect} from 'react'
import '../css/Diary.css'
import Input from './Input'
import Textarea from './Textarea'
import Button from './Button'
import Category from './Category'
function EditDiary({
editTitle,
editContent,
changeEditTitle,
changeEditContent,
closeEditModal,
editCategory,
changeEditCategory,
submitEditDiary
}) {
useEffect(() => {
document.body.style = `pointer-events: none` //모달 창 열면 페이지 전체 클릭 이벤트 방지
return () => (document.body.style = `pointer-events: auto`) //모달 창 닫으면 페이지 전체 클릭 이벤트 허용
})
return (
<div className="writeDiv">
<Button className={'closeWriteDiary'} onClick={closeEditModal} name={'X'} />
<h1 className="writeMain">Today Diary</h1>
<form>
<div>
<label>카테고리</label>
<Category value={editCategory} onChange={changeEditCategory} />
</div>
<div className="writeTitleDiv">
<label>제목</label>
<Input
className={'writeTitleInput'}
type={'text'}
name={'title'}
value={editTitle}
onChange={changeEditTitle}
/>
</div>
<div className="writeContentDiv">
<label>내용</label>
<Textarea
className={'writeContentTextarea'}
name={'content'}
value={editContent}
onChange={changeEditContent}
/>
</div>
<Button
className={'submitButton'}
type={'submit'}
name={'저장'}
onClick={submitEditDiary}
/>
</form>
</div>
)
}
export default EditDiary
component - <DiaryBlock.JSX > : 이벤트 리스너를 통해 모달 창 외부 클릭 감지
import React, {useEffect, useRef, useState} from 'react'
import '../css/Diary.css'
import Button from './Button'
import EditDiary from './EditDiary'
function DiaryBlock({title, content, id, name, date, deleteDiary, editDiary, modalBool}) {
const [editTitle, setEditTitle] = useState(title) //수정 모달 창에 저장된 제목을 value로 지정
const [editContent, setEditContent] = useState(content)//수정 모달 창에 저장된 내용을 value로 지정
const [editCategory, setEditCategory] = useState(name) //수정 모달 창에 저장된 카테고리를을 value로 지정
const [editBool, setEditBool] = useState(false)
const modalWindow = useRef() //문서에서 모달 창 영역을 지정
const changeEditTitle = event => {
setEditTitle(event.target.value)
}
const changeEditContent = event => {
setEditContent(event.target.value)
}
const changeEditCategory = event => {
setEditCategory(event.target.value)
}
const handleDelete = () => {
deleteDiary(id)
}
const handleEdit = () => {
setEditBool(true)
}
const submitEditDiary = event => {
event.preventDefault()
if (editBool === false) {
setEditBool(true)
} else {
setEditBool(false)
editDiary(id, editTitle, editContent, editCategory)
//id, 수정한 제목/내용/카테고리를 상위 컴포넌트로 넘김
}
}
const closeEditModal = () => {
setEditBool(false)
}
useEffect(() => {
const modalOutClick = event => {
if (!modalWindow.current || modalWindow.current.contains(event.target)) return
//모달창이 존재하지 않거나 모달창 내부를 클릭할 경우, 함수 실행 안함
setEditBool(false) //모달창 외부를 클릭할 경우 모달창 꺼짐는 함수 실행
}
document.addEventListener('mousedown', modalOutClick)
//마우스 다운 이벤트가 발생할 때 modalOutClick 함수를 실행하는 이벤트 리스너를 문서 전체에 등록
return () => {
document.removeEventListener('mousedown', modalOutClick)
} //컴포넌트가 언마운트되거나 업데이트될 때 실행 때 마우스 다운 이벤트 리스너를 제거
}, [modalWindow])
return (
<div className="blockDiv" key={id} name={name}>
<div className="blockTitleDiv">
<h3 className="blockTitle">{title}</h3>
<Button
className={'deleteButton'}
onClick={modalBool ? null : handleDelete}
name={'삭제'}
/>
<Button
className={'editButton'}
onClick={modalBool ? null : handleEdit}
name={'수정'}
/>
</div>
<div className="blockDateDiv">
<p>{date}</p>
</div>
<p>----------------------------------------------------------------------</p>
<div ref={modalWindow}>
{editBool ? (
<EditDiary
editTitle={editTitle}
editContent={editContent}
editCategory={editCategory}
changeEditTitle={changeEditTitle}
changeEditContent={changeEditContent}
changeEditCategory={changeEditCategory}
submitEditDiary={submitEditDiary}
closeEditModal={closeEditModal}
/>
) : (
<p>{content}</p>
)}
</div>
</div>
)
}
export default DiaryBlock
module - <Diary.JSX> : 일기 제목/내용/카테고리 수정
import React, {useEffect, useRef, useState} from 'react'
import '../css/Diary.css'
import DiaryWrite from '../component/DiaryWrite'
import DiaryBlock from '../component/DiaryBlock'
import Button from '../component/Button'
import Header from '../component/Header'
import Sidebar from '../component/Sidebar'
function Diary() {
const [diary, setDiary] = useState(JSON.parse(localStorage.getItem('Diary')) || [])
const [categoryDiary, setCategoryDiary] = useState(diary)
const [categoryValue, setCategoryValue] = useState('none')
const [modalBool, setModalBool] = useState(false)
const diaryKey = useRef(parseInt(localStorage.getItem('DiaryKey')) || 0)
const changeCategoryValue = event => {
setCategoryValue(event.target.value)
}
const addDiary = (title, content) => {
if (categoryValue === 'none') {
alert('카테고리를 지정해주세요.')
} else {
const newDiary = {
title,
content,
name: categoryValue,
id: diaryKey.current,
date: new Date().toLocaleString()
}
diaryKey.current += 1
setDiary([newDiary, ...diary])
setCategoryValue('none')
setModalBool(false)
}
}
const deleteDiary = id => {
const remainDiary = diary.filter(remove => remove.id !== id)
setDiary(remainDiary)
}
const editDiary = (id, title, content, name) => { //제목, 내용, 카테고리 수정
setDiary(
diary.map(edit =>
edit.id === id ? {...edit, title: title, content: content, name: name} : edit
//지정한 id의 제목, 내용, 카테고리만 수정
)
)
}
const allDivDiaryBtn = () => {
setCategoryDiary(diary)
}
const routineDivDiaryBtn = () => {
setCategoryDiary(diary.filter(divide => divide.name === 'routine'))
}
const studyDivDiaryBtn = () => {
setCategoryDiary(diary.filter(divide => divide.name === 'study'))
}
const travelDivDiaryBtn = () => {
setCategoryDiary(diary.filter(divide => divide.name === 'travel'))
}
const gameDivDiaryBtn = () => {
setCategoryDiary(diary.filter(divide => divide.name === 'game'))
}
const openModal = event => {
setModalBool(true)
event.stopPropagation()
}
const closeModal = () => {
setModalBool(false)
setCategoryValue('none')
}
useEffect(() => {
localStorage.setItem('Diary', JSON.stringify(diary))
localStorage.setItem('DiaryKey', parseInt(diaryKey.current))
setCategoryDiary(diary)
}, [diary])
return (
<div className={modalBool ? 'diaryDiv' : null} onClick={closeModal}>
<div>
<Header />
</div>
<div>
<div>
<Sidebar
allDivDiaryBtn={modalBool ? null : allDivDiaryBtn}
routineDivDiaryBtn={modalBool ? null : routineDivDiaryBtn}
studyDivDiaryBtn={modalBool ? null : studyDivDiaryBtn}
travelDivDiaryBtn={modalBool ? null : travelDivDiaryBtn}
gameDivDiaryBtn={modalBool ? null : gameDivDiaryBtn}
/>
</div>
<div className="mainDiv">
<div>
<Button
className="openWriteDiary"
name={'글쓰기 +'}
onClick={modalBool ? null : openModal}
/>
</div>
<div onClick={event => event.stopPropagation()}>
{modalBool ? (
<DiaryWrite
addDiary={addDiary}
closeModal={closeModal}
categoryValue={categoryValue}
changeCategory={changeCategoryValue}
/>
) : null}
</div>
<div className="gridDiv">
{categoryDiary.map(diaryData => (
<DiaryBlock
key={diaryData.id}
name={diaryData.name}
date={diaryData.date}
{...diaryData}
deleteDiary={deleteDiary}
editDiary={editDiary}
modalBool={modalBool}
/>
))}
</div>
</div>
</div>
</div>
)
}
export default Diary
Diary.css : pointer-events로 모달 창 내부만 클릭 허용
.headerDiv {
display: flex;
border-bottom: 1px solid black;
top: 0;
width: 100%;
background-color: white;
position: fixed;
z-index: 1;
}
.headerTitle {
margin: 20px 0px 20px 70px;
padding: 0;
font-size: 40px;
}
.sidebarDiv {
position: fixed;
border-right: 1px solid black;
height: 100vh;
width: 270px;
padding-top: 100px;
top: 0;
}
.mainDiv {
margin: 120px 0px 20px 300px;
}
.gridDiv {
display: grid;
grid-template-columns: 500px 500px;
}
.writeDiv {
border: 1px solid black;
display: inline-block;
padding: 20px;
position: absolute;
background-color: white;
left: 37%;
top: 10%;
box-shadow: rgba(0, 0, 0, 0.7) 0 0 0 9999px;
z-index: 1;
pointer-events: auto; //모달 창 열 시 해당 위치(모달창)만 포인트 이벤트 동작
}
.writeMain {
text-align: center;
margin-left: 30px;
}
.writeTitleDiv {
margin-top: 20px;
}
.writeTitleInput {
width: 400px;
height: 30px;
font-size: 15px;
}
.writeContentDiv {
margin-top: 20px;
}
.writeContentTextarea {
width: 400px;
height: 100px;
resize: none;
font-size: 15px;
}
.blockDiv {
border: 1px solid rgb(211, 211, 211);
background-color: rgb(211, 211, 211);
width: 450px;
padding: 10px;
margin-top: 20px;
}
.blockTitleDiv {
display: flex;
justify-content: flex-start;
align-items: center;
}
.blockTitle {
margin-right: auto;
margin-top: 10px;
margin-bottom: 0;
}
.blockDateDiv {
font-size: 15px;
font-style: italic;
}
.diaryDiv {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.openWriteDiary {
margin-top: 10px 20px 0px 0px;
padding: 7px 15px 7px 15px;
background-color: black;
color: white;
border: none;
}
.closeWriteDiary {
float: right;
background: none;
border: none;
font-size: 20px;
}
.submitButton {
margin-top: 20px;
width: 400px;
height: 40px;
font-size: 20px;
}
.deleteButton {
font-size: 15px;
padding: 3px 10px 3px 10px;
margin-right: 10px;
margin-top: 10px;
background-color: rgb(211, 211, 211);
}
.editButton {
font-size: 15px;
padding: 3px 10px 3px 10px;
margin-right: 30px;
margin-top: 10px;
background-color: rgb(211, 211, 211);
}
'개인 공부 > 게시판' 카테고리의 다른 글
9. 페이지네이션 (0) | 2024.02.19 |
---|---|
7. 페이지 구성 (0) | 2024.02.13 |
6. 모달 창 구현 (0) | 2024.02.10 |
5. 카테고리, 작성 날짜 (0) | 2024.01.31 |
4. EditDiary (0) | 2024.01.29 |