From f2d16b9e6d9a13e24c3ebb17849a20864f67761b Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Tue, 22 Jul 2025 23:54:11 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=83=88=20=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createpost/PhotoSection.styled.ts | 83 +++++++++++ src/components/createpost/PhotoSection.tsx | 68 +++++++++ .../createpost/PostContentSection.styled.ts | 39 +++++ .../createpost/PostContentSection.tsx | 31 ++++ .../createpost/PrivacyToggleSection.tsx | 28 ++++ .../createpost/TagSelectionSection.tsx | 30 ++++ src/pages/index.tsx | 2 + src/pages/post/CreatePost.styled.ts | 14 ++ src/pages/post/CreatePost.tsx | 137 ++++++++++++++++++ 9 files changed, 432 insertions(+) create mode 100644 src/components/createpost/PhotoSection.styled.ts create mode 100644 src/components/createpost/PhotoSection.tsx create mode 100644 src/components/createpost/PostContentSection.styled.ts create mode 100644 src/components/createpost/PostContentSection.tsx create mode 100644 src/components/createpost/PrivacyToggleSection.tsx create mode 100644 src/components/createpost/TagSelectionSection.tsx create mode 100644 src/pages/post/CreatePost.styled.ts create mode 100644 src/pages/post/CreatePost.tsx diff --git a/src/components/createpost/PhotoSection.styled.ts b/src/components/createpost/PhotoSection.styled.ts new file mode 100644 index 00000000..2513cdbb --- /dev/null +++ b/src/components/createpost/PhotoSection.styled.ts @@ -0,0 +1,83 @@ +import styled from '@emotion/styled'; +import { typography, semanticColors, colors } from '../../styles/global/global'; + +export const PhotoContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +export const PhotoGrid = styled.div` + display: flex; + gap: 12px; + align-items: center; +`; + +export const AddPhotoButton = styled.button` + width: 80px; + height: 80px; + border: 2px dashed ${colors.grey[300]}; + background-color: transparent; + border-radius: 8px; + color: ${semanticColors.text.secondary}; + font-size: 32px; + font-weight: ${typography.fontWeight.regular}; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + + &:hover:not(:disabled) { + border-color: ${semanticColors.text.primary}; + color: ${semanticColors.text.primary}; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +`; + +export const PhotoItem = styled.div` + position: relative; + width: 80px; + height: 80px; +`; + +export const PhotoImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 8px; +`; + +export const RemoveButton = styled.button` + position: absolute; + top: -8px; + right: -8px; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: ${semanticColors.button.fill.primary}; + color: ${semanticColors.text.primary}; + border: none; + font-size: 14px; + font-weight: ${typography.fontWeight.bold}; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; + + &:hover { + background-color: ${semanticColors.button.fill.background}; + } +`; + +export const PhotoCount = styled.div` + align-self: flex-end; + color: ${semanticColors.text.tertiary}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.regular}; +`; diff --git a/src/components/createpost/PhotoSection.tsx b/src/components/createpost/PhotoSection.tsx new file mode 100644 index 00000000..e98d7996 --- /dev/null +++ b/src/components/createpost/PhotoSection.tsx @@ -0,0 +1,68 @@ +import { useRef } from 'react'; +import { Section, SectionTitle } from '../../pages/group/CommonSection.styled'; +import { + PhotoContainer, + PhotoGrid, + PhotoItem, + AddPhotoButton, + PhotoImage, + RemoveButton, + PhotoCount, +} from './PhotoSection.styled'; + +interface PhotoSectionProps { + photos: File[]; + onPhotoAdd: (files: File[]) => void; + onPhotoRemove: (index: number) => void; +} + +const PhotoSection = ({ photos, onPhotoAdd, onPhotoRemove }: PhotoSectionProps) => { + const fileInputRef = useRef(null); + + const handleFileInputClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + const files = Array.from(e.target.files || []); + if (files.length > 0) { + onPhotoAdd(files); + } + // input 값 초기화 (같은 파일을 다시 선택할 수 있도록) + e.target.value = ''; + }; + + const createImageUrl = (file: File) => { + return URL.createObjectURL(file); + }; + + return ( +
+ 사진 추가 + + + = 3}> + + + + {photos.map((photo, index) => ( + + + onPhotoRemove(index)}>× + + ))} + + {photos.length}/3개 + + +
+ ); +}; + +export default PhotoSection; diff --git a/src/components/createpost/PostContentSection.styled.ts b/src/components/createpost/PostContentSection.styled.ts new file mode 100644 index 00000000..ff086eb6 --- /dev/null +++ b/src/components/createpost/PostContentSection.styled.ts @@ -0,0 +1,39 @@ +import styled from '@emotion/styled'; +import { typography, semanticColors, colors } from '../../styles/global/global'; + +export const TextAreaBox = styled.div` + position: relative; + display: flex; + flex-direction: column; +`; + +export const TextArea = styled.textarea` + width: 100%; + min-height: 100px; + padding: 16px; + background-color: ${semanticColors.background.primary}; + border: 1px solid ${colors.grey[300]}; + border-radius: 12px; + color: ${semanticColors.text.primary}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.regular}; + font-family: ${typography.fontFamily.primary}; + resize: none; + outline: none; + + &::placeholder { + color: ${semanticColors.text.secondary}; + } + + &:focus { + border-color: ${semanticColors.text.primary}; + } +`; + +export const CharacterCount = styled.div` + align-self: flex-end; + margin-top: 8px; + color: ${semanticColors.text.tertiary}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.regular}; +`; diff --git a/src/components/createpost/PostContentSection.tsx b/src/components/createpost/PostContentSection.tsx new file mode 100644 index 00000000..c3daed3b --- /dev/null +++ b/src/components/createpost/PostContentSection.tsx @@ -0,0 +1,31 @@ +import { Section, SectionTitle } from '../../pages/group/CommonSection.styled'; +import { TextAreaBox, TextArea, CharacterCount } from './PostContentSection.styled'; + +interface PostContentSectionProps { + content: string; + onContentChange: (value: string) => void; +} + +const PostContentSection = ({ content, onContentChange }: PostContentSectionProps) => { + const maxLength = 2000; + + return ( +
+ 글 작성 + +