From b171c9903e849733eb4bf077b48b791cd3644b3e Mon Sep 17 00:00:00 2001 From: suji8073 Date: Tue, 25 Mar 2025 02:21:51 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[chore]=20close,=20plus=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/close_20.svg | 4 ++++ src/assets/{plus.svg => plus_20.svg} | 0 2 files changed, 4 insertions(+) create mode 100644 src/assets/close_20.svg rename src/assets/{plus.svg => plus_20.svg} (100%) diff --git a/src/assets/close_20.svg b/src/assets/close_20.svg new file mode 100644 index 0000000..8515bbb --- /dev/null +++ b/src/assets/close_20.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/plus.svg b/src/assets/plus_20.svg similarity index 100% rename from src/assets/plus.svg rename to src/assets/plus_20.svg From beb99ec2f835ab79281bde8175c0c3f4f7413ad0 Mon Sep 17 00:00:00 2001 From: suji8073 Date: Tue, 25 Mar 2025 03:18:29 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[refactor]=20--layout-max-w=20CSS=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/globals.css b/src/app/globals.css index 9aa8a28..a69f454 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,8 +2,13 @@ @tailwind components; @tailwind utilities; +:root { + --layout-max-w: 480px; +} + @layer base { .layout { - @apply max-w-[480px] min-h-[100dvh] w-full mx-auto; + @apply mx-auto min-h-[100dvh] w-full; + max-width: var(--layout-max-w); } } From 0575f0a303c8c35795dae558aa1b1866adaf632b Mon Sep 17 00:00:00 2001 From: suji8073 Date: Tue, 25 Mar 2025 03:20:23 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[refactor]=20Storybook=20global.css=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=20-=20layout-max-w=20CS?= =?UTF-8?q?S=20=EB=B3=80=EC=88=98=EB=A5=BC=20100%=20=EB=A1=9C=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/preview.ts | 2 +- .storybook/storybook-globals.css | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .storybook/storybook-globals.css diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 607d7c3..879778b 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,5 +1,5 @@ import type { Preview } from '@storybook/react'; -import '../src/app/globals.css'; +import './storybook-globals.css'; const preview: Preview = { parameters: { diff --git a/.storybook/storybook-globals.css b/.storybook/storybook-globals.css new file mode 100644 index 0000000..bd74514 --- /dev/null +++ b/.storybook/storybook-globals.css @@ -0,0 +1,14 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --layout-max-w: 100%; +} + +@layer base { + .layout { + @apply mx-auto min-h-[100dvh] w-full; + max-width: var(--layout-max-w); + } +} From 0283b7c0b1aefa778e59506622c990efaf70f9dc Mon Sep 17 00:00:00 2001 From: suji8073 Date: Tue, 25 Mar 2025 03:24:09 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[refactor]=20ImageUploader=EB=A5=BC=20Botto?= =?UTF-8?q?mContent=EB=A1=9C=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20Floaing=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20-=20BottomSheet=20=EB=82=B4=20header=EC=99=80=20children?= =?UTF-8?q?=EC=9D=84=20=EA=B0=81=EA=B0=81=20BottomSheetHeader=EC=99=80=20B?= =?UTF-8?q?ottomSheetContent=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20-=20observerR?= =?UTF-8?q?ef(MutationObserver)=EB=A5=BC=20=ED=86=B5=ED=95=B4=20BottomShee?= =?UTF-8?q?t=20=EB=86=92=EC=9D=B4=20=EB=B3=80=ED=99=94=20=EA=B0=90?= =?UTF-8?q?=EC=A7=80=20-=20FloatingInput=EC=9D=B4=20BottomSheet=20?= =?UTF-8?q?=EC=9C=84=EC=97=90=20=EC=A0=95=ED=99=95=ED=9E=88=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/component/BottomContent.tsx | 194 ++++++++++++++++++ .../comment/component/ImageUploader.tsx | 98 --------- 2 files changed, 194 insertions(+), 98 deletions(-) create mode 100644 src/features/comment/component/BottomContent.tsx delete mode 100644 src/features/comment/component/ImageUploader.tsx diff --git a/src/features/comment/component/BottomContent.tsx b/src/features/comment/component/BottomContent.tsx new file mode 100644 index 0000000..debe6cd --- /dev/null +++ b/src/features/comment/component/BottomContent.tsx @@ -0,0 +1,194 @@ +import React, { useEffect, useRef, CSSProperties, ChangeEvent } from 'react'; +import { BottomSheet } from '@/components/atoms/bottomSheet'; +import Close from '@/assets/close_20.svg'; + +const DEFAULT_BOTTOM_SHEET_HEIGHT = 290; +const MAX_IMAGE = 9; + +interface BottomContentProps { + images: string[]; + setImages: (images: string[]) => void; + showGallery: boolean; + setShowGallery: (value: boolean) => void; + setBottomSheetHeight: (height: number) => void; +} + +export const BottomContent = ({ + images, + showGallery, + setImages, + setShowGallery, + setBottomSheetHeight, +}: BottomContentProps) => { + const observerRef = useRef(null); + const isFirstOpenRef = useRef(true); + + useEffect(() => { + if (!showGallery && observerRef.current) { + isFirstOpenRef.current = true; + observerRef.current.disconnect(); + observerRef.current = null; + } + }, [showGallery]); + + const observeHeightChanges = () => { + const bottomSheetElement = document.querySelector( + '[data-rsbs-root="true"]', + ) as HTMLElement; + + if (!bottomSheetElement) { + return; + } + + observerRef.current?.disconnect(); + + observerRef.current = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.attributeName === 'style') { + if (isFirstOpenRef.current) { + setBottomSheetHeight(DEFAULT_BOTTOM_SHEET_HEIGHT); + return; + } + + const computedStyle = getComputedStyle(bottomSheetElement); + const newHeight = parseInt( + computedStyle.getPropertyValue('--rsbs-overlay-h'), + 10, + ); + const translateY = parseInt( + computedStyle.getPropertyValue('--rsbs-overlay-translate-y'), + 10, + ); + + setBottomSheetHeight( + newHeight === DEFAULT_BOTTOM_SHEET_HEIGHT + ? newHeight - translateY + : newHeight, + ); + } + }); + }); + + observerRef.current.observe(bottomSheetElement, { + attributes: true, + attributeOldValue: true, + }); + }; + + const handleSpringStart = () => { + observeHeightChanges(); + }; + + const handleSpringEnd = () => { + if (showGallery) { + isFirstOpenRef.current = false; + } + }; + + return ( + setShowGallery(false)} + snapPoints={({ maxHeight }) => [DEFAULT_BOTTOM_SHEET_HEIGHT, maxHeight]} + defaultSnap={() => DEFAULT_BOTTOM_SHEET_HEIGHT} + blocking={false} + header={ + + } + > + + + ); +}; + +const BottomSheetHeader = ({ + images, + setImages, + setShowGallery, +}: { + images: string[] | null; + setImages: (images: string[]) => void; + setShowGallery: (value: boolean) => void; +}) => { + const openNativeGallery = () => { + document.getElementById('fileInput')?.click(); + }; + + const handleImageUpload = (event: ChangeEvent) => { + if (!event.target.files) return; + + const fileArray = Array.from(event.target.files).map((file) => + URL.createObjectURL(file), + ); + setImages(images ? [...images, ...fileArray] : [...fileArray]); + setShowGallery(true); + }; + + return ( +
+ + +
+ 사진 + + {images?.length ?? 0}/ + {MAX_IMAGE} + +
+ + +
+ ); +}; + +const BottomSheetContent = ({ + images, + setImages, +}: { + images: string[]; + setImages: (images: string[]) => void; +}) => { + return images.length ? ( +
+ {images.map((src, index) => ( +
+ {`User + +
+ ))} +
+ ) : ( +
+ + 앨범에서 사진을 찾아보세요. + +
+ ); +}; diff --git a/src/features/comment/component/ImageUploader.tsx b/src/features/comment/component/ImageUploader.tsx deleted file mode 100644 index 6764ab6..0000000 --- a/src/features/comment/component/ImageUploader.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { useState } from 'react'; -import Plus from '@/assets/plus.svg'; - -export const ImageUploader = () => { - const [images, setImages] = useState([]); - const [showGallery, setShowGallery] = useState(false); - - const handleImageUpload = (event: React.ChangeEvent) => { - if (!event.target.files) return; - - const files = Array.from(event.target.files); - const fileArray = files.map((file) => URL.createObjectURL(file)); - - setImages((prev) => [...prev, ...fileArray]); - setShowGallery(true); - }; - - const openNativeGallery = () => { - const fileInput = document.getElementById('fileInput') as HTMLInputElement; - if (fileInput) { - fileInput.click(); - } - }; - - const handleDeleteImage = (index: number) => { - setImages((prev) => prev.filter((_, i) => i !== index)); - }; - - const handleGalleryClose = () => { - // 선택한 사진 닫는 기능 - setImages([]); - setShowGallery(false); - }; - - const handleUploadImage = () => { - // upload Image : images 변수에 저장되어 있음 - setImages([]); - setShowGallery(false); - }; - - return ( - <> - - -
- -
- - {showGallery && ( -
-
- - 선택한 사진 - -
- -
- {images.map((src, index) => ( -
- 업로드 하고자 하는 사진 - -
- ))} -
-
- )} - - ); -}; From 7cd8ab572eca0e97bbe644320305d79711a46892 Mon Sep 17 00:00:00 2001 From: suji8073 Date: Tue, 25 Mar 2025 03:27:47 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[refactor]=20CommentBox=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20TextFieldBottom=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CommentInput, UploadBtn, ImageOpen view를 TextFieldBottom 컴포넌트로 분리 - BottomSheet의 height를 기반으로 상위 컴포넌트의 bottom 스타일을 동적으로 조정 - 업로드 버튼 클릭 시 텍스트/이미지 존재 여부에 따라 분기 처리하여 업로드 로직 수행 --- src/features/comment/component/CommentBox.tsx | 62 ++++++++++--------- .../comment/component/TextFieldBottom.tsx | 48 ++++++++++++++ 2 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 src/features/comment/component/TextFieldBottom.tsx diff --git a/src/features/comment/component/CommentBox.tsx b/src/features/comment/component/CommentBox.tsx index fb9779a..33b2b77 100644 --- a/src/features/comment/component/CommentBox.tsx +++ b/src/features/comment/component/CommentBox.tsx @@ -1,38 +1,44 @@ -import Upload from '@/assets/upload.svg'; -import UploadActive from '@/assets/upload_active.svg'; -import { CommentInput } from '@/components/molecules/comment'; -import { ImageUploader } from './ImageUploader'; +import { useState } from 'react'; +import { BottomContent } from './BottomContent'; +import { TextFieldBottom } from './TextFieldBottom'; -interface CommentBoxProps { - text: string; - setText: (text: string) => void; -} +export const CommentBox = () => { + const [text, setText] = useState(''); + const [images, setImages] = useState([]); + const [showGallery, setShowGallery] = useState(false); + const [bottomSheetHeight, setBottomSheetHeight] = useState(0); -export const CommentBox = ({ text, setText }: CommentBoxProps) => { - const handleUploadText = () => { - // text upload - setText(''); + const handleUpload = () => { + // text, image upload + if (text.trim()) { + setText(''); + } + if (showGallery && images.length > 0) { + setImages([]); + setShowGallery(false); + } }; return ( -
- - - + -
- {text.trim() ? : } -
+
); }; diff --git a/src/features/comment/component/TextFieldBottom.tsx b/src/features/comment/component/TextFieldBottom.tsx new file mode 100644 index 0000000..d4b7282 --- /dev/null +++ b/src/features/comment/component/TextFieldBottom.tsx @@ -0,0 +1,48 @@ +import { CommentInput } from '@/components/molecules/comment'; +import Close from '@/assets/close_20.svg'; +import Plus from '@/assets/plus_20.svg'; +import Upload from '@/assets/upload.svg'; +import UploadActive from '@/assets/upload_active.svg'; + +interface TextFieldBottomProps { + text: string; + setText: (text: string) => void; + showGallery: boolean; + setShowGallery: (value: boolean) => void; + handleUpload: () => void; +} + +export const TextFieldBottom = ({ + text, + setText, + showGallery, + setShowGallery, + handleUpload, +}: TextFieldBottomProps) => { + return ( +
+ + + + + +
+ ); +}; From 423099cedf0a56c766da14057efddec933371061 Mon Sep 17 00:00:00 2001 From: suji8073 Date: Tue, 25 Mar 2025 03:32:34 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[refactor]=20BottomSheet=20style=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/atoms/bottomSheet/style.css | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/atoms/bottomSheet/style.css b/src/components/atoms/bottomSheet/style.css index c5b03ff..609f9af 100644 --- a/src/components/atoms/bottomSheet/style.css +++ b/src/components/atoms/bottomSheet/style.css @@ -2,11 +2,10 @@ --rsbs-max-w: var(--layout-max-w); --rsbs-content-opacity: 1; --rsbs-backdrop-bg: rgba(0, 0, 0, 0.6); - --rsbs-bg: #1F2028; - --rsbs-handle-bg: #8C8EA6; - --rsbs-max-w: auto; - --rsbs-ml: env(safe-area-inset-left); - --rsbs-mr: env(safe-area-inset-right); + --rsbs-bg: #2c2e3a; + --rsbs-handle-bg: #8c8ea6; + --rsbs-ml: auto; + --rsbs-mr: auto; --rsbs-overlay-rounded: 16px; /* 스크롤바 없음 */ From dbb909dd5abc556660bc063339f163b871ae0784 Mon Sep 17 00:00:00 2001 From: suji8073 Date: Tue, 25 Mar 2025 03:32:50 +0900 Subject: [PATCH 7/8] [refactor] CommentBox Storybook update --- src/features/comment/component/CommentBox.stories.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/features/comment/component/CommentBox.stories.tsx b/src/features/comment/component/CommentBox.stories.tsx index f2522e6..f01a8ed 100644 --- a/src/features/comment/component/CommentBox.stories.tsx +++ b/src/features/comment/component/CommentBox.stories.tsx @@ -1,4 +1,3 @@ -import { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { CommentBox } from './CommentBox'; @@ -14,11 +13,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { - text: '', - }, - render: (args) => { - const [text, setText] = useState(args.text); - return ; + render: () => { + return ; }, }; From a2d4599f582dc39d1f0b5cb5f1691335e64b6c7d Mon Sep 17 00:00:00 2001 From: suji8073 Date: Fri, 28 Mar 2025 15:08:22 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[refactor]=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BottomSheetHeader 내 image 타입 string[]으로 제한 - input을 통한 카메라 앨범 접근 시 dom id 접근이 아닌 Ref로 접근하도록 수정 --- src/features/comment/component/BottomContent.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/features/comment/component/BottomContent.tsx b/src/features/comment/component/BottomContent.tsx index debe6cd..dd179b9 100644 --- a/src/features/comment/component/BottomContent.tsx +++ b/src/features/comment/component/BottomContent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, CSSProperties, ChangeEvent } from 'react'; +import React, { useEffect, useRef, ChangeEvent } from 'react'; import { BottomSheet } from '@/components/atoms/bottomSheet'; import Close from '@/assets/close_20.svg'; @@ -112,12 +112,13 @@ const BottomSheetHeader = ({ setImages, setShowGallery, }: { - images: string[] | null; + images: string[]; setImages: (images: string[]) => void; setShowGallery: (value: boolean) => void; }) => { + const inputRef = useRef(null); const openNativeGallery = () => { - document.getElementById('fileInput')?.click(); + inputRef.current?.click(); }; const handleImageUpload = (event: ChangeEvent) => { @@ -133,10 +134,10 @@ const BottomSheetHeader = ({ return (