Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
31b9b83
Style: 불필요한 코드 제거
iyeseung707-dev Nov 12, 2025
de72ae1
Feat: 메시지 페이지 UI 디자인, 드롭다운 구현
iyeseung707-dev Nov 14, 2025
f3a8d7d
Feat: from. input 반응형 구현
iyeseung707-dev Nov 14, 2025
18d9661
Feat: reach text editor 기능 구현
iyeseung707-dev Nov 14, 2025
3d0ccea
Feat: 프로필 이미지 선택하기 UI 구현
iyeseung707-dev Nov 15, 2025
cc249f3
Fix: Quill 임포트 및 등록 문제 해결
iyeseung707-dev Nov 15, 2025
f1318cb
Feat: 프로필 이미지 API 연동
iyeseung707-dev Nov 17, 2025
7118beb
Feat: 롤링페이퍼 헤더 UI 구현
jungwon123 Nov 6, 2025
424f3ea
Feat: 롤링페이퍼 헤더 UI 태블릿,모바일 반응형 구현
jungwon123 Nov 7, 2025
5cf2a45
Feat: 롤링페이퍼 헤더 이모지 기능 구현
jungwon123 Nov 8, 2025
70474b0
Feat: upstream develop 브랜치 merge, 카카오톡 공유기능 구현
jungwon123 Nov 8, 2025
1ddb7ac
Feat: 공유하기 UI 수정, 프로필 이미지와 몇명 작성했는지 표시하는 기능 구현
jungwon123 Nov 10, 2025
6cd31c8
Feat: 리뷰 수정 및, 카드 목록 UI 구현
jungwon123 Nov 12, 2025
e2000a8
Chore: PR 빌드 오류로 인한 재푸시
jungwon123 Nov 12, 2025
7589a09
Refactor: ToastProvider 성능 최적화
BZzzzi Nov 12, 2025
8333f6d
Feat: 리스트 페이지 라우팅 추가
BZzzzi Nov 12, 2025
032b2fd
Refactor: 임시 페이지 라우트 경로 수정
BZzzzi Nov 12, 2025
e06ecc3
Refactor: 임시 페이지 및 헤더 버튼 링크 경로 재설정
BZzzzi Nov 12, 2025
6735707
Feat: 메시지 페이지 UI 디자인, 드롭다운 구현
iyeseung707-dev Nov 14, 2025
c8e6a67
Feat: reach text editor 기능 구현
iyeseung707-dev Nov 14, 2025
c21aca5
Merge branch 'feature/message-page' of github.com:BZzzzi/rolling-fron…
BZzzzi Nov 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 86 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
},
"dependencies": {
"axios": "^1.13.2",
"quill": "^2.0.3",
"emoji-picker-react": "^4.15.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-quill-new": "^3.6.0",
"react-infinite-scroll-component": "^6.1.0",
"react-router": "^7.9.5",
"styled-components": "^6.1.19",
Expand Down
6 changes: 4 additions & 2 deletions src/components/common/header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const ContainWrapper = styled.div`
border-bottom: 1px solid #ededed;
z-index: 1003;

${props => props.$isRollingPage && media.small`
${(props) =>
props.$isRollingPage &&
media.small`
display: none;
`}
`;
Expand Down Expand Up @@ -45,7 +47,7 @@ const ButtonWrapper = styled.div`

export default function Header({ showButton }) {
const location = useLocation();
const isRollingPage = location.pathname.startsWith('/post/');
const isRollingPage = location.pathname.startsWith("/post/");

return (
<>
Expand Down
8 changes: 7 additions & 1 deletion src/components/common/modal-layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ const CloseButton = styled.button`
* 공통 모달 레이아웃 컴포넌트
* 책임: 모달의 기본 구조와 레이아웃 제공
*/
export default function ModalLayout({ isOpen, onClose, title, children, showCloseButton = true }) {
export default function ModalLayout({
isOpen,
onClose,
title,
children,
showCloseButton = true,
}) {
if (!isOpen) return null;

return (
Expand Down
159 changes: 159 additions & 0 deletions src/components/message/drop-down.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import React, { useState, useRef, useEffect, useCallback } from "react";
import styled, { css } from "styled-components";
import { colors } from "@/styles/colors";
import { font } from "@/styles/font";

import ARROW_ICON from "@/assets/icons/arrow-right.svg";

const DropDownWrapper = styled.div`
position: relative;
width: 320px;
`;

const DropDownTrigger = styled.button`
width: 100%;
height: 50px;
display: flex;
justify-content: space-between;
align-items: center;

padding: 12px 16px;
border-radius: 8px;
border: 1px solid ${colors.gray[300]};
background-color: #fff;

${font.regular16}
text-align: left;

outline: none;

color: ${({ $currentValue, defaultValue, $isInitialLoad }) => {
if ($isInitialLoad && $currentValue === defaultValue) {
return colors.gray[500];
}
return colors.gray[900];
}};

${({ $isOpen }) =>
$isOpen &&
css`
border: 2px solid ${colors.gray[500]};
padding: 11px 15px;
`}
`;

const ArrowImage = styled.img`
width: 16px;
height: 16px;
transform: rotate(${({ $isOpen }) => ($isOpen ? "270deg" : "90deg")});
transition: transform 0.2s;
`;

const DropDownMenuContainer = styled.ul`
list-style: none;
margin: 10px 1px;
padding: 1px;

position: absolute;
top: 100%;
left: 0;
z-index: 10;
width: 320px;
max-height: 220px;
overflow-y: auto;

background-color: #ffffff;
border: 1px solid ${colors.gray[300]};
border-radius: 8px;

box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.08);
`;

const DropDownItem = styled.li`
height: 50px;
display: flex;
align-items: center;
padding: 12px 16px;

${font.regular16}
color: ${colors.gray[900]};

&:hover {
background-color: ${colors.gray[100]};
}
`;

function DropDown({ id, name, defaultValue, value, onChange, options }) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
const currentValue = value;
const [isInitialLoad, setIsInitialLoad] = useState(true);

const handleClickOutside = useCallback((event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
}, []);

useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [handleClickOutside]);

const handleItemClick = (optionValue) => {
onChange({ target: { name: name, value: optionValue } });
setIsOpen(false);
setIsInitialLoad(false);
};

const handleTriggerClick = () => {
setIsOpen((prev) => !prev);
if (isInitialLoad) {
setIsInitialLoad(false);
}
};

const selectedOption = options.find((opt) => opt.value === currentValue) || {
label: defaultValue,
value: defaultValue,
};

return (
<DropDownWrapper ref={dropdownRef}>
<DropDownTrigger
id={id}
name={name}
type="button"
onClick={handleTriggerClick}
$isOpen={isOpen}
$currentValue={currentValue}
defaultValue={defaultValue}
$isInitialLoad={isInitialLoad}
aria-haspopup="listbox"
aria-expanded={isOpen}
>
<span>{selectedOption.label}</span>
<ArrowImage src={ARROW_ICON} alt="드롭다운 화살표" $isOpen={isOpen} />
</DropDownTrigger>

{isOpen && (
<DropDownMenuContainer role="listbox" aria-labelledby={id}>
{options.map((option) => (
<DropDownItem
key={option.value}
role="option"
aria-selected={option.value === currentValue}
onClick={() => handleItemClick(option.value)}
>
{option.label}
</DropDownItem>
))}
</DropDownMenuContainer>
)}
</DropDownWrapper>
);
}

export default DropDown;
Loading