diff --git a/src/assets/svgs/editorDropIcnActive.svg b/src/assets/svgs/editorDropIcnActive.svg
new file mode 100644
index 00000000..04689d03
--- /dev/null
+++ b/src/assets/svgs/editorDropIcnActive.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/svgs/editorDropIcnActiveopen.svg b/src/assets/svgs/editorDropIcnActiveopen.svg
new file mode 100644
index 00000000..9955823e
--- /dev/null
+++ b/src/assets/svgs/editorDropIcnActiveopen.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/svgs/index.tsx b/src/assets/svgs/index.tsx
index 50adf66c..2617b593 100644
--- a/src/assets/svgs/index.tsx
+++ b/src/assets/svgs/index.tsx
@@ -47,4 +47,7 @@ export { default as CheckboxIc } from './postDetailCheckIc.svg?react';
export { default as QuestionDefaultIc } from './questionDefaultIc.svg?react';
export { default as QuestioHoverIc } from './questionHoverIc.svg?react';
+export { default as EditorDropIcnActiveIc } from './editorDropIcnActive.svg?react';
+export { default as EditorDropIcnActiveOpenIc } from './editorDropIcnActiveopen.svg?react';
export { default as GroupThumnailImgIc } from './groupThumnailImg.svg?react';
+
diff --git a/src/hooks/useClickOutside.tsx b/src/hooks/useClickOutside.tsx
new file mode 100644
index 00000000..e9eeba61
--- /dev/null
+++ b/src/hooks/useClickOutside.tsx
@@ -0,0 +1,25 @@
+import React, { useEffect } from 'react';
+
+const useClickOutside = (ref: React.RefObject, callback: () => void) => {
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ // 드롭다운이 열려있고, 드롭다운 외부가 클릭됐을 경우
+ if (ref.current && !ref.current.contains(event.target as Node)) {
+ // 실행할 함수를 인자로 받아와서 실행시켜 줌
+ callback();
+ }
+ };
+
+ // 클릭 이벤트가 발생하면 handleClickOutside를 실행시킨다
+ document.addEventListener('click', handleClickOutside);
+
+ // 이벤트 지워주기
+ return () => {
+ document.removeEventListener('click', handleClickOutside);
+ };
+ }, [ref, callback]);
+
+ return useClickOutside;
+};
+
+export default useClickOutside;
diff --git a/src/pages/postPage/PostPage.tsx b/src/pages/postPage/PostPage.tsx
index f3cde829..03613f11 100644
--- a/src/pages/postPage/PostPage.tsx
+++ b/src/pages/postPage/PostPage.tsx
@@ -1,10 +1,15 @@
import styled from '@emotion/styled';
+import DropDown from './components/DropDown';
import Editor from './components/Editor';
+import Spacing from '../../components/commons/Spacing';
const PostPage = () => {
return (
+
+
+
);
@@ -14,5 +19,6 @@ export default PostPage;
const PostPageWrapper = styled.div`
display: flex;
- justify-content: center;
+ flex-direction: column;
+ align-items: center;
`;
diff --git a/src/pages/postPage/components/DropDown.tsx b/src/pages/postPage/components/DropDown.tsx
new file mode 100644
index 00000000..a8614ab0
--- /dev/null
+++ b/src/pages/postPage/components/DropDown.tsx
@@ -0,0 +1,73 @@
+/* eslint-disable no-unused-vars */
+import { useState, useRef } from 'react';
+
+import styled from '@emotion/styled';
+
+import TopicDropDown from './TopicDropDown';
+import WriterDropDown from './WriterDropDown';
+
+export interface DropDownPropsType {
+ onClickListItem: (key: string, value: string) => void;
+ selectedValue: string;
+}
+
+const DropDown = () => {
+ // 드롭다운에서 선택된 값 저장 state
+ // 글감ID, 익명여부 저장 필요
+ // 가장 최신값으로 초기값 업데이트
+ const [selectedValues, setSelectedValues] = useState({
+ topic: '필명에 대하여',
+ writer: '작자미상',
+ });
+
+ const dropDownRef = useRef(null);
+
+ // 드롭다운 리스트 중 선택된 값 저장 이벤트 핸들러
+ const handleListItem = (key: string, value: string) => {
+ setSelectedValues((prev) => ({ ...prev, [key]: value }));
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+export default DropDown;
+
+const DropDownWrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 1.2rem;
+ align-items: center;
+ justify-content: flex-start;
+ width: 82.6rem;
+`;
+
+export const DropDownToggle = styled.div`
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ height: 4.4rem;
+ padding: 0.8rem 1.6rem;
+
+ background-color: ${({ theme }) => theme.colors.mileViolet};
+ cursor: pointer;
+ border: 1px solid transparent;
+ border-radius: 8px;
+
+ &:hover {
+ border: 1px solid ${({ theme }) => theme.colors.mainViolet};
+ }
+`;
+
+export const DropDownContent = styled.span<{ $contentWidth: number }>`
+ width: ${({ $contentWidth }) => `${$contentWidth}rem`};
+ margin-right: 1rem;
+
+ color: ${({ theme }) => theme.colors.mainViolet};
+ ${({ theme }) => theme.fonts.button2};
+`;
diff --git a/src/pages/postPage/components/Topic.tsx b/src/pages/postPage/components/Topic.tsx
new file mode 100644
index 00000000..adb77157
--- /dev/null
+++ b/src/pages/postPage/components/Topic.tsx
@@ -0,0 +1,95 @@
+/* eslint-disable no-unused-vars */
+import styled from '@emotion/styled';
+
+import React from 'react';
+
+interface TopicPropTypes {
+ topicId: number;
+ topicName: string;
+ onClickHandler: (key: string, value: string) => void;
+ selected: boolean;
+ onClickClose: (state: boolean) => void;
+}
+
+const ThisWeekTopic = (props: TopicPropTypes) => {
+ const { topicName, onClickHandler, selected, onClickClose } = props;
+ const handleListClick = (e: React.MouseEvent) => {
+ onClickHandler('topic', e.currentTarget.innerText);
+ onClickClose(false);
+ };
+ return (
+ <>
+ 최신 글감
+
+ {topicName}
+
+ >
+ );
+};
+
+const PrevFirstTopic = (props: TopicPropTypes) => {
+ const { topicName, onClickHandler, selected, onClickClose } = props;
+ const handleListClick = (e: React.MouseEvent) => {
+ onClickHandler('topic', e.currentTarget.innerText);
+ onClickClose(false);
+ };
+ return (
+ <>
+
+ 이전 글감
+
+ {topicName}
+
+ >
+ );
+};
+
+const PrevTopic = (props: TopicPropTypes) => {
+ const { topicName, onClickHandler, selected, onClickClose } = props;
+ const handleListClick = (e: React.MouseEvent) => {
+ onClickHandler('topic', e.currentTarget.innerText);
+ onClickClose(false);
+ };
+ return (
+
+ {topicName}
+
+ );
+};
+
+export { ThisWeekTopic, PrevFirstTopic, PrevTopic };
+
+// 최신 글감, 이전 글감
+const TopicLog = styled.span`
+ width: 100%;
+
+ color: ${({ theme }) => theme.colors.gray70};
+ ${({ theme }) => theme.fonts.body7};
+`;
+
+// 글감
+const Topic = styled.div<{ $selected: boolean }>`
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ width: 100%;
+ padding: 0.8rem 0 0.8rem 0.8rem;
+
+ color: ${({ $selected, theme }) => ($selected ? theme.colors.mainViolet : theme.colors.gray90)};
+
+ cursor: pointer;
+ border-radius: 6px;
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.gray20};
+ }
+
+ ${({ theme }) => theme.fonts.button2};
+`;
+
+const Divider = styled.div`
+ width: 100%;
+ margin-bottom: 0.6rem;
+
+ border: 1px solid ${({ theme }) => theme.colors.gray20};
+`;
diff --git a/src/pages/postPage/components/TopicDropDown.tsx b/src/pages/postPage/components/TopicDropDown.tsx
new file mode 100644
index 00000000..349fb8eb
--- /dev/null
+++ b/src/pages/postPage/components/TopicDropDown.tsx
@@ -0,0 +1,119 @@
+import styled from '@emotion/styled';
+
+import { useRef, useState } from 'react';
+
+import { DropDownToggle, DropDownContent, DropDownPropsType } from './DropDown';
+import { ThisWeekTopic, PrevFirstTopic, PrevTopic } from './Topic';
+
+import { TOPIC_DUMMY_DATA } from '../constants/topicConstants';
+
+import { EditorDropIcnActiveIc, EditorDropIcnActiveOpenIc } from '../../../assets/svgs';
+import useClickOutside from '../../../hooks/useClickOutside';
+
+const TopicDropDown = (props: DropDownPropsType) => {
+ const { onClickListItem, selectedValue } = props;
+
+ const [topicIsOpen, setTopicIsOpen] = useState(false);
+
+ // 드롭다운 리스트 부분 잡아오기
+ const dropDownRef = useRef(null);
+
+ // 토글 열림 닫힘만 핸들링하는 함수
+ const handleOnClick = () => {
+ setTopicIsOpen(!topicIsOpen);
+ };
+ // 커스텀 훅 전달 콜백 함수
+ const handleOutSideClick = () => {
+ setTopicIsOpen(false);
+ };
+ //커스텀 훅 사용
+ useClickOutside(dropDownRef, handleOutSideClick);
+
+ return (
+
+
+ {selectedValue}
+ {topicIsOpen ? : }
+
+
+ {TOPIC_DUMMY_DATA.map((item, idx) => {
+ if (idx === 0) {
+ return (
+
+ );
+ } else if (idx === 1) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ })}
+
+
+ );
+};
+
+export default TopicDropDown;
+
+const TopicDropDownWrapper = styled.div`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+`;
+
+const TopicListWrapper = styled.div<{ $isOpen: boolean }>`
+ position: absolute;
+ top: 4.4rem;
+ z-index: 3;
+
+ display: ${({ $isOpen }) => ($isOpen ? 'flex' : 'none')};
+ flex-direction: column;
+ gap: 0.6rem;
+ align-items: center;
+ justify-content: flex-start;
+ width: 36rem;
+ max-height: 37.1rem;
+ padding: 2rem;
+ overflow: hidden scroll;
+
+ background-color: ${({ theme }) => theme.colors.white};
+ border: 1px solid ${({ theme }) => theme.colors.gray50};
+ border-radius: 10px;
+
+ &::-webkit-scrollbar {
+ width: 0.4rem;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: ${({ theme }) => theme.colors.gray20};
+ background-clip: padding-box;
+ border: 20px solid ${({ theme }) => theme.colors.gray20};
+ border-radius: 4px;
+ }
+`;
diff --git a/src/pages/postPage/components/WriterDropDown.tsx b/src/pages/postPage/components/WriterDropDown.tsx
new file mode 100644
index 00000000..f0744314
--- /dev/null
+++ b/src/pages/postPage/components/WriterDropDown.tsx
@@ -0,0 +1,92 @@
+import styled from '@emotion/styled';
+
+import React, { useRef, useState } from 'react';
+
+import { DropDownToggle, DropDownContent, DropDownPropsType } from './DropDown';
+
+import { EditorDropIcnActiveIc, EditorDropIcnActiveOpenIc } from '../../../assets/svgs';
+import useClickOutside from '../../../hooks/useClickOutside';
+
+const WriterDropDown = (props: DropDownPropsType) => {
+ const { onClickListItem, selectedValue } = props;
+ const [writerIsOpen, setWriterIsOpen] = useState(false);
+
+ // 드롭다운 리스트 부분 잡아오기
+ const dropDownRef = useRef(null);
+ // 선택된 값 저장
+ const handleListClick = (e: React.MouseEvent) => {
+ onClickListItem('writer', e.currentTarget.innerText);
+ setWriterIsOpen(false);
+ };
+ // 필명 드롭다운 버튼 누르면 열림/닫힘
+ const handleOnClick = () => {
+ setWriterIsOpen(!writerIsOpen);
+ };
+ // 커스텀 훅 전달 함수
+ const handleOutSideClick = () => {
+ setWriterIsOpen(false);
+ };
+ // 커스텀 훅 사용
+ useClickOutside(dropDownRef, handleOutSideClick);
+
+ return (
+
+
+ {selectedValue}
+ {writerIsOpen ? : }
+
+
+
+ 작자미상
+
+
+ 필명
+
+
+
+ );
+};
+
+export default WriterDropDown;
+
+const WriterDropDownWrapper = styled.div`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+`;
+
+const WriterListWrapper = styled.div<{ $isOpen: boolean }>`
+ position: absolute;
+ top: 4.3rem;
+ right: 7rem;
+ z-index: 3;
+ display: ${({ $isOpen }) => ($isOpen ? 'flex' : 'none')};
+ flex-direction: column;
+ gap: 0.6rem;
+ align-items: center;
+ justify-content: center;
+ width: 14.8rem;
+ padding: 2rem;
+
+ background-color: ${({ theme }) => theme.colors.white};
+ border: 1px solid ${({ theme }) => theme.colors.gray50};
+ border-radius: 8px;
+`;
+
+const WriterList = styled.div<{ $selected: boolean }>`
+ width: 10.8rem;
+ padding: 0.6rem 0 0.6rem 1rem;
+
+ color: ${({ $selected, theme }) => ($selected ? theme.colors.mainViolet : theme.colors.gray90)};
+
+ background-color: ${({ theme }) => theme.colors.white};
+ cursor: pointer;
+ border-radius: 6px;
+ ${({ theme }) => theme.fonts.button2};
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.gray20};
+ }
+`;
diff --git a/src/pages/postPage/constants/topicConstants.ts b/src/pages/postPage/constants/topicConstants.ts
new file mode 100644
index 00000000..46acfe48
--- /dev/null
+++ b/src/pages/postPage/constants/topicConstants.ts
@@ -0,0 +1,29 @@
+import { topicType } from '../types/topicType';
+
+// data.data.topics
+export const TOPIC_DUMMY_DATA: topicType[] = [
+ {
+ topicId: 1,
+ topicName: '필명에 대하여',
+ },
+ {
+ topicId: 2,
+ topicName: '다현이에 대하여',
+ },
+ {
+ topicId: 3,
+ topicName: '서진이에 대하여',
+ },
+ {
+ topicId: 4,
+ topicName: '다은이에 대하여',
+ },
+ {
+ topicId: 5,
+ topicName: '희정이에 대하여',
+ },
+ {
+ topicId: 6,
+ topicName: '지원이에 대하여',
+ },
+];
diff --git a/src/pages/postPage/types/topicType.ts b/src/pages/postPage/types/topicType.ts
new file mode 100644
index 00000000..da15ed60
--- /dev/null
+++ b/src/pages/postPage/types/topicType.ts
@@ -0,0 +1,4 @@
+export interface topicType {
+ topicId: number;
+ topicName: string;
+}