티스토리 뷰

1
2
3

 

 

[1번 사진]에서 + 버튼을 누르면 [2번 사진]처럼 내가 원하는 이름으로 새로운 게시판을 만들게끔 만들려고 한다.

즉, + 버튼을 누르면 저렇게 타자를 칠 수 있는 SideForm을 만들 것이다.

그리고 [3번 사진]처럼 3개의 게시판 중 내가 클릭한 게시판은 Active한 스타일로 바뀌게끔 할 것이다.

💥 Board List 생성&스타일하기

type TBoardListProps = {
    activeBoardId: string
    setActiveBoardId: React.Dispatch<React.SetStateAction<string>>
}

const BoardList: FC<TBoardListProps> = ({
    activeBoardId,
    setActiveBoardId
}) => {

    const { boardArray } = useTypedSelector(state => state.boards)
    const [isFormOpen, setIsFormOpen] = useState(false)
    const inputRef = useRef<HTMLInputElement>(null)

    const handleClick = () => {
        setIsFormOpen(!isFormOpen)
        setTimeout(() => {
            inputRef.current?.focus()
        }, 0);
    }

    return (
        <div className={container}>
            <div className={title}>
                게시판:
            </div>
            {boardArray.map((board, index)=> (
                <div key={board.boardId} onClick={() => setActiveBoardId(boardArray[index].boardId)}
                    className={
                        clsx(
                            {
                                [boardItemActive]:
                                boardArray.findIndex(b => b.boardId === activeBoardId) === index
                            },
                            {
                                [boardItem]:
                                boardArray.findIndex(b => b.boardId === activeBoardId) !== index
                            }
                        )
                    }
                >
                    <div>
                        {board.boardName}
                    </div>
                </div>
            ))}
            <div className={addSection}>
                {
                    isFormOpen ? 
                        <SideForm inputRef={inputRef} setIsFormOpen={setIsFormOpen} />
                        :
                        <FiPlusCircle className={addButton} onClick={handleClick} />
                }
            </div>
        </div>
    )
}

export default BoardList

BoardList 파일의 코드이다.

clsx 라이브러리를 사용해 boardItemActive 클래스를 가지면 Active하게 보여주고,

boardItem 클래스를 가지면 일반으로 보여주게했다.

그리고, addSection 클래스에선 useState를 사용해 isFormOpen이 true이면 SideForm을,

false면 FiPlusCircle을 나타내라는 삼항연산자를 사용했다.

FiPlusCircle을 onclick하면 isFormOpen이 반대로 되게끔했다.

지금은 SideForm 디자인을 따로 해놓지 않아서 + 버튼을 누르면 SideForm 이라는 글자가 뜨게끔만 해놓았다.

💥 SideForm 생성&스타일하기

type TSideFormProps = {
    inputRef: React.RefObject<HTMLInputElement>
    setIsFormOpen: React.Dispatch<React.SetStateAction<boolean>>
}

const SideForm: FC<TSideFormProps> = ({
    setIsFormOpen,
    inputRef
}) => {
    const [inputText, setInputText] = useState('')
    const dispatch = useTypedDispatch()

    const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
        setInputText(e.target.value)
    }

    const handleOnBlur = () => {
        setIsFormOpen(false)
    }

    const handleClick = () => {
        if(inputText) {
            dispatch(
                addBoard({board: {
                    boardId: uuidv4(), 
                    boardName: inputText,
                    lists: []
                }
            })
            )

            dispatch(
                addLog({
                    logId: uuidv4(),
                    logMessage: `게시판 등록: ${inputText}`,
                    logAuthor: "User",
                    logTimestamp: String(Date.now())
                })
            )
        }
    }

    return (
        <div className={sideForm}>
            <input className={input}
                ref={inputRef}
                type="text"
                placeholder='새로운 게시판 등록하기'
                value={inputText}
                onChange={handleChange}
                onBlur={handleOnBlur}
            />
            <FiCheck className={icon} onMouseDown={handleClick}/> 
        </div>
    )
}

export default SideForm

SideForm의 파일이다.

+ 버튼을 눌렀을 때 + 버튼은 사라지고 SideForm이 나타나게 된다.

즉, SideForm에 있는 input과 Check버튼이 뜨게 된다.

뜰 때부터 Focus가 되어있으면 좋겠다 라고 생각해서 ref를 사용했다.

그래서 BoardList의 파일에 inputRef를 만들어서 SideForm 태그안에 넣어주었다.

inputRef.current?.focus() 코드만 썼을 때는 + 버튼을 눌러도 자동으로 Focus가 되지 않았다.

순위 및 시간차 때문에 그렇다고 하셔서 setTimeout을 이용해 바로 뜨게끔 만들어주었다.

하지만, 더 쉽게 하려면 input 태그안에 그냥 autoFocus 만 넣어도 동작된다.

굳이 먼 길 돌아가고싶지 않다면 autoFocus를 추천..한다😥

 

그리고 게시판을 추가할 때 boardArray가 추가되는 건데, 이건 BoardSlice 파일에서 해야한다.

이렇게 boardsSlice의 reducers에 적어주면된다.

boardArray에 push 메소드를 이용해서 추가해주는 작업이다.

log 역시 boardArray처럼 추가를 해주기 위해, 같은 작업을 해주었다.

똑같이 push를 이용해 logArray를 추가해주었다.

 

input 태그에 타이핑 했을 때만 진행이 될 수 있게 하기 위해 if 문을 사용해 inputText가 있으면~ 이라는 조건을 걸어주었다.

boardId와 logId는 유니크한 값을 갖기 위해 uuid 라이브러리 uuidv4를 호출해서 사용했다.

 

Check 버튼을 누르면 handleCheck가 사용되게끔 onClick 이벤트를 사용했는데 되지 않았다.

그 이유는 input 태그에 있는 onBlur 이벤트 때문에 그렇다.

onBlur 이벤트가 onClick보다 실행 순서가 빠르기 때문이다.

그래서 onBlur보다 실행순서가 빠른 onMouseDown으로 바꿔봤더니 실행이 잘 됐다.

그럼 이렇게 + 버튼을 누르면 '새로운 게시판 등록하기' 라는 SideForm과 Check 버튼이 나타나고,

ㅇ 이라고 타이핑을 한 뒤, Check 버튼을 누르면 저렇게 'ㅇ' 게시판이 만들어지게 된다.

💥 List, Task, Action Button, DropDownForm 마무리

type TListsContainerProps = {
    boardId: string
    lists: IList[]
}

const ListsContainer: FC<TListsContainerProps> = ({
    lists,
    boardId
}) => {
    return (
        <div className={listsContainer}>
            {
                lists.map(list => (
                    <List 
                        key={list.listId} 
                        list={list}
                        boardId={boardId}
                    />
                ))
            }
            <ActionButton />
        </div>
    )
}

export default ListsContainer

 - ListContainer.tsx 

 

type TListProps = {
    boardId: string
    list: IList
}

const List: FC<TListProps> = ({
    list,
    boardId
}) => {

    const dispatch = useTypedDispatch()

    const handleListDelete = (listId: string) => {
        dispatch(deleteList({boardId, listId}))
        dispatch(
            addLog({
                logId: v4(),
                logMessage: `리스트 삭제하기: ${list.listName}`,
                logAuthor: "User",
                logTimestamp: String(Date.now())
            })
        )
    }

    const handleTaskChange = (
        boardId: string,
        listId: string,
        taskId: string,
        task: ITask
    ) => {
        dispatch(setModalData({boardId, listId, task}))
        dispatch(setModalActive(true))
    }

    return (
        <div className={listWrapper}>
            <div className={header}>
                <div className={name}>{list.listName}</div>
                <GrSubtract className={deleteButton}
                onClick={() => handleListDelete(list.listId)}
                />
            </div>
                {list.tasks.map((task, index) => (
                    <div 
                    onClick={() => handleTaskChange(boardId, list.listId, task.taskId, task)}
                        key={task.taskId}
                    >
                        <Task 
                            taskName={task.taskName}
                            taskDescription={task.taskDescription}
                            boardId={boardId}
                            id={task.taskId}
                            index={index}
                        />
                    </div>
                ))}
                <ActionButton 
                boardId={boardId}
                listId={list.listId} />
        </div>
    )
}

export default List

 - List.tsx

 

type TTaskProps = {
    index: number
    id:  string
    boardId: string
    taskName: string
    taskDescription: string
}

const Task: FC<TTaskProps> = ({
    index,
    id,
    boardId,
    taskName,
    taskDescription
}) => {
    return (
        <div className={container}>
            <div className={title}>{taskName}</div>
            <div className={description}>{taskDescription}</div>
        </div>
    )
}

export default Task

 - Task.tsx

 

type TActionButtonProps = {
    boardId: string
    listId: string
    list?: boolean
}

const ActionButton: FC<TActionButtonProps> = ({
    boardId,
    listId,
    list
}) => {
    const [isFormOpen, setIsFormOpen] = useState(false)
    const buttonText = list ? "새로운 리스트 등록" : "새로운 일 등록"

    return isFormOpen ? (
        <DropDownForm
        setIsFormOpen={setIsFormOpen}
        list={list ? true : false}
        boardId={boardId}
        listId={listId}
        />
    )
        :
        (
            <div
                className={list ? listButton : taskButton}
                onClick={() => setIsFormOpen(true)}
            >
                <IoIosAdd />
                <p>{buttonText}</p>
            </div>
        )
}

export default ActionButton

 - ActionButton.tsx

 

ActionButton은 위의 List와 Task와는 다른 점이 있는데

buttonText = list ? "새로운 리스트 등록" : "새로운 일 등록" 이 부분이다.

buttonText 변수 하나로 두 개의 스타일을 만들기 위해 넣은 부분이다.

즉, list가 true 이면 "새로운 리스트 등록" 이라는 버튼이 나타나고, false이면 "새로운 일 등록" 버튼이 나타나는 것이다.

그래서 이렇게 같은 변수이지만 다르게 나타낼 수 있는 장점이 있다.

원래 같았으면 2개의 버튼을 만들어서 스타일링 했을 것 같은데 이 부분은 새롭게 배워서 굉장히 재밌는 부분이었다.

대신 스타일은 다르게 적어줘야 한다.

이렇게 listButton , taskButton으로 나누어서 클래스로 나눠서 설정이 가능한 것이다.

 

type TDropDownFormProps = {
    boardId: string
    listId: string
    setIsFormOpen: React.Dispatch<React.SetStateAction<boolean>>
    list?: boolean
}

const DropDownForm: FC<TDropDownFormProps> = ({
    boardId,
    list,
    listId,
    setIsFormOpen
}) => {
    const dispatch = useTypedDispatch()

    const [text, setText] = useState('') // 값을 기억하기 위한 state
    const formPlaceholder = list ?
    "리스트의 제목을 입력하세요" :
    "일의 제목을 입력하세요"

    const buttonTitle = list ?
    "리스트 추가하기" : 
    "일 추가하기"

    const handleTextChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
        setText(e.target.value)
    }

    const handleButtonClick = () => {
        if(text) {
            if(list) {
                dispatch(
                    addList({
                        boardId,
                        list: {listId: v4(), listName: text, tasks: []}
                    })
                )

                dispatch(
                    addLog({
                        logId: v4(),
                        logMessage: `리스트 생성하기: ${text}`,
                        logAuthor: "User",
                        logTimestamp: String(Date.now())
                    })
                )
            } else {
                dispatch(
                    addTask({
                        boardId,
                        listId,
                        task: {
                            taskId: v4(),
                            taskName: text,
                            taskDescription: "",
                            taskOwner: "User"
                        }
                    })
                )

                dispatch(
                    addLog({
                        logId: v4(),
                        logMessage: `일 생성하기: ${text}`,
                        logAuthor: "User",
                        logTimestamp: String(Date.now())
                    })
                )
            }
        }
    }

    return (
        <div className={list ? listForm : taskForm}>
            <textarea
                className={input}
                value={text}
                onChange={handleTextChange}
                autoFocus
                placeholder={formPlaceholder}
                onBlur={() => setIsFormOpen(false)}
            />
            <div className={buttons}>
                <button
                    className={button}
                    onMouseDown={handleButtonClick}>
                    {buttonTitle}
                </button>
                <FiX className={close}/>
            </div>
        </div>
    )
}

export default DropDownForm

 - DropDownForm.tsx

 

DropDownForm도 ActionButton과 똑같이 삼항 연산자를 이용해 다른 스타일을 나타나게끔 만들었다.

이렇게 "새로운 리스트 등록", "새로운 일 등록"을 눌렀을 대 비슷한 스타일이지만,

"일 추가하기", "리스트 추가하기" 글자가 다르게 나타나며,

"일의 제목을 입력하세요", "리스트의 제목을 입력하세요" 라는 placeholder 역시 다르게 나타난다.

둘의 클래스만 다를 뿐 폼을 2개로 만든 것이 아니다.

ActionButton과 똑같이 taskForm, listForm 의 스타일만 나뉘게 되고,

이 클래스를 이용해서 다르게 나타낼 수 있는 장점이 있다.

진짜 유용하게 쓸 수 있는 부분인 것 같다.. 나중에 시간내서 오늘의 강의는 한 번 더 들을 것 같다.

 - 결과물

 

이렇게 새로운 Task와 새로운 List를 만들 수 있는 폼과, 버튼, 리스트 폼을 제작했다.

이제 내일 강의는 아마 새로운 Task를 눌렀을 때 나타나는 수정창? 수정폼? 을 만들 것으로 예상된다.

원래 21일 강의인데 사정상 오늘 강의를 듣고 제작했다..

22일 23일 24일까지 강의가 있는데 이 부분은 낼부터 얼른 만들어야 할 것 같다😥

 

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함