feat: 독서모임 생성을 위한 모임 만들기 페이지 구현#32
Conversation
|
Caution Review failedThe pull request is closed. ## Walkthrough
이번 변경사항은 그룹 생성 페이지 및 관련 UI 컴포넌트의 신규 도입, 도서 검색 바텀시트, 장르/인원/기간/비공개 등 다양한 그룹 설정 섹션, 3D 휠 셀렉터, 스타일드 컴포넌트 도입, 라우트 추가, 그리고 필요한 라이브러리 의존성 추가로 구성되어 있습니다.
## Changes
| 파일 또는 경로 | 변경 요약 |
|---|---|
| package.json | `react-datepicker` 및 `@types/react-datepicker` 의존성 추가 |
| src/components/common/BookSearchBottomSheet/* | 도서 검색 바텀시트 컴포넌트 및 스타일 컴포넌트 신규 추가 |
| src/pages/group/CreateGroup.tsx, CreateGroup.styled.ts | 그룹 생성 페이지 컴포넌트 및 스타일 컴포넌트 신규 추가 |
| src/pages/group/CommonSection.styled.ts | 공통 섹션 스타일 컴포넌트 추가 |
| src/pages/group/components/BookSelectionSection.* | 도서 선택 섹션 컴포넌트 및 스타일 컴포넌트 신규 추가 |
| src/pages/group/components/GenreSelectionSection.* | 장르 선택 섹션 컴포넌트 및 스타일 컴포넌트 신규 추가 |
| src/pages/group/components/RoomInfoSection.* | 방 정보 입력 섹션 컴포넌트 및 스타일 컴포넌트 신규 추가 |
| src/pages/group/components/ActivityPeriodSection/* | 활동 기간 섹션, 3D 휠 셀렉터 컴포넌트 및 스타일 컴포넌트 신규 추가 |
| src/pages/group/components/MemberLimitSection.* | 인원 제한 섹션 컴포넌트 및 스타일 컴포넌트 신규 추가 |
| src/pages/group/components/PrivacySettingSection/* | 비공개 설정 토글, 비밀번호 입력 섹션 및 스타일 컴포넌트 신규 추가 |
| src/pages/index.tsx | `/group/create` 경로에 대한 라우트 추가 |
## Sequence Diagram(s)
```mermaid
sequenceDiagram
participant User
participant CreateGroupPage
participant BookSearchBottomSheet
participant SubSections
User->>CreateGroupPage: 접속 및 폼 입력
User->>BookSearchBottomSheet: 도서 검색 버튼 클릭
BookSearchBottomSheet-->>User: 도서 리스트 및 검색 제공
User->>BookSearchBottomSheet: 도서 선택
BookSearchBottomSheet->>CreateGroupPage: 선택 도서 전달
loop 설정 섹션
User->>SubSections: 장르/기간/인원/비공개 등 설정
end
User->>CreateGroupPage: 완료 버튼 클릭
CreateGroupPage-->>User: (추후) 그룹 생성 처리Possibly related PRs
Suggested reviewers
Poem
|
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (16)
src/pages/index.tsx (2)
12-12: 임포트 순서를 개선하세요.관련 기능별로 임포트를 그룹화하는 것이 좋습니다.
import Login from './login/Login'; import Signup from './signup/Signup'; import SignupGenre from './signup/SignupGenre'; import SignupNickname from './signup/SignupNickname'; import SignupDone from './signup/SignupDone'; + import CreateGroup from './group/CreateGroup';
24-24: 라우트 구조를 더 명확하게 구성하세요.라우트 경로를 일관성 있게 구성하고, 그룹 관련 라우트들을 함께 묶는 것을 고려해보세요.
<Route path="signupdone" element={<SignupDone />} /> - <Route path="group/create" element={<CreateGroup />} /> + <Route path="group"> + <Route path="create" element={<CreateGroup />} /> + </Route>src/pages/group/CommonSection.styled.ts (1)
4-11: 조건부 스타일링 방식을 개선하세요.템플릿 리터럴 대신 css 헬퍼를 사용하여 더 명확한 코드를 작성하는 것이 좋습니다.
+import styled, { css } from '@emotion/styled'; import { colors, typography, semanticColors } from '../../styles/global/global'; export const Section = styled.div<{ showDivider?: boolean }>` margin-bottom: 32px; - ${({ showDivider }) => - showDivider && - ` - border-bottom: 1px solid ${colors.darkgrey.dark}; - `} + + ${({ showDivider }) => + showDivider && + css` + border-bottom: 1px solid ${colors.darkgrey.dark}; + `} `;src/pages/group/components/GenreSelectionSection.styled.ts (1)
10-21: 호버 상태 및 비활성화 상태 스타일링 추가 고려현재 활성/비활성 상태만 구현되어 있는데, 사용자 경험 향상을 위해 호버 상태와 포커스 상태 스타일링을 추가하는 것을 권장합니다:
export const GenreButton = styled.button<{ active: boolean }>` background-color: ${({ active }) => active ? semanticColors.button.fill.primary : colors.grey[400]}; border: none; border-radius: 20px; padding: 8px 12px; color: ${semanticColors.text.primary}; font-size: ${typography.fontSize.xs}; font-weight: ${typography.fontWeight.regular}; cursor: pointer; transition: all 0.2s; + + &:hover:not(:disabled) { + opacity: 0.8; + } + + &:focus-visible { + outline: 2px solid ${semanticColors.button.fill.primary}; + outline-offset: 2px; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } `;src/pages/group/components/MemberLimitSection.tsx (1)
23-28: DateWheel 컴포넌트 재사용이 창의적임날짜 선택용 휠 컴포넌트를 숫자 선택에 재사용한 것이 창의적이고 효율적입니다. 하지만 컴포넌트명이
DateWheel인 점이 혼란을 야기할 수 있습니다.향후 재사용성을 고려하여 더 범용적인 이름으로 변경하는 것을 고려해보세요:
NumberWheel또는SelectionWheel로 리네이밍- 또는
DateWheel을WheelPicker로 일반화src/pages/group/components/BookSelectionSection.tsx (3)
16-16: 타입 안전성을 위해 Book 인터페이스를 정의하세요.인라인 객체 타입 대신 명시적인 Book 인터페이스를 정의하여 타입 안전성을 높이고 재사용성을 개선할 수 있습니다.
+interface Book { + cover: string; + title: string; + author: string; +} + interface BookSelectionSectionProps { - selectedBook: { cover: string; title: string; author: string } | null; + selectedBook: Book | null; onSearchClick: () => void; onChangeClick: () => void; }
37-37: 접근성을 위해 이미지 alt 텍스트를 개선하세요.스크린 리더 사용자를 위해 더 구체적인 alt 텍스트를 제공하는 것이 좋습니다.
- <img src={selectedBook.cover} alt={selectedBook.title} /> + <img src={selectedBook.cover} alt={`${selectedBook.title} 책 표지`} />
51-51: 일관성을 위해 인라인 스타일을 styled-components로 이동하세요.다른 컴포넌트들과 스타일 패턴을 일관되게 유지하기 위해 인라인 스타일을 styled-components로 이동하는 것이 좋습니다.
BookSelectionSection.styled.ts 파일에 추가:
export const SearchText = styled.span` color: ${semanticColors.text.secondary}; `;그리고 컴포넌트에서:
- <span style={{ color: semanticColors.text.secondary }}>검색해서 찾기</span> + <SearchText>검색해서 찾기</SearchText>src/pages/group/components/GenreSelectionSection.tsx (2)
11-11: 성능 최적화를 위해 genres 배열을 컴포넌트 외부로 이동하세요.컴포넌트가 리렌더링될 때마다 genres 배열이 재생성되는 것을 방지하기 위해 컴포넌트 외부에 정의하는 것이 좋습니다.
+ const GENRES = ['문학', '과학·IT', '사회과학', '인문학', '예술'] as const; + const GenreSelectionSection = ({ selectedGenre, onGenreSelect }: GenreSelectionSectionProps) => { - const genres = ['문학', '과학·IT', '사회과학', '인문학', '예술']; return ( <Section> <SectionTitle>책 장르</SectionTitle> <GenreButtonGroup> - {genres.map(genre => ( + {GENRES.map(genre => (
27-36: 일관성을 위해 인라인 스타일을 styled-components로 이동하세요.다른 컴포넌트들과의 스타일 패턴 일관성을 위해 인라인 스타일을 styled-components로 이동하는 것이 좋습니다.
GenreSelectionSection.styled.ts 파일에 추가:
export const GenreMessage = styled.div` color: ${semanticColors.text.point.green}; font-size: ${typography.fontSize.xs}; margin-top: 12px; text-align: right; `;그리고 컴포넌트에서:
- <div - style={{ - color: semanticColors.text.point.green, - fontSize: typography.fontSize.xs, - marginTop: '12px', - textAlign: 'right', - }} - > + <GenreMessage> {selectedGenre ? '1개만 선택 가능합니다.' : '책을 가장 잘 설명하는 장르를 하나 골라주세요.'} - </div> + </GenreMessage>src/pages/group/CreateGroup.tsx (1)
22-23: 동적 기본 날짜 값을 사용하세요.하드코딩된 날짜 값 대신 현재 날짜를 기반으로 한 동적 기본값을 사용하는 것이 좋습니다.
+ const getCurrentDate = () => { + const today = new Date(); + return { + year: today.getFullYear(), + month: today.getMonth() + 1, + day: today.getDate() + }; + }; + - const [startDate, setStartDate] = useState({ year: 2025, month: 1, day: 1 }); - const [endDate, setEndDate] = useState({ year: 2025, month: 1, day: 1 }); + const [startDate, setStartDate] = useState(getCurrentDate()); + const [endDate, setEndDate] = useState(getCurrentDate());src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (1)
40-83: 모의 데이터의 중복 항목들을 확인하세요.데모 목적으로 보이지만, 모의 데이터에 동일한 책들이 중복되어 있습니다 (예: '토마토 컵라면', '사슴', '호르몬 체인지'). 실제 구현 시에는 고유한 데이터를 사용해야 합니다.
src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.tsx (1)
122-154: 날짜 변경 시 재검증 로직을 개선할 수 있습니다.각 핸들러에서 종료일 재검증 로직이 중복되고 있습니다. 이를 별도 함수로 추출하여 중복을 줄일 수 있습니다.
다음과 같이 공통 재검증 로직을 추출할 수 있습니다:
+ const revalidateEndDate = () => { + const adjustedEndDate = validateAndAdjustDate(endDate, true); + if (JSON.stringify(adjustedEndDate) !== JSON.stringify(endDate)) { + onEndDateChange(adjustedEndDate); + } + }; const handleStartYearChange = (year: number) => { const newDate = validateAndAdjustDate({ ...startDate, year }); onStartDateChange(newDate); - - // 종료일도 재검증 - const adjustedEndDate = validateAndAdjustDate(endDate, true); - if (JSON.stringify(adjustedEndDate) !== JSON.stringify(endDate)) { - onEndDateChange(adjustedEndDate); - } + revalidateEndDate(); };src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts (3)
4-17: Overlay에 어두운 배경 컬러가 없어 시각적 깊이가 부족합니다
backdrop-filter만으로는 배경이 흰 화면일 때 거의 효과가 보이지 않습니다.rgba(0,0,0,.4)정도의 반투명 배경을 추가하면 모달 구분이 명확해집니다.backdrop-filter: blur(2px); + background-color: rgba(0, 0, 0, 0.4);
146-160: 스크롤바 커스텀 시margin-right: -16px트릭은 Safari 에서 문제될 수 있습니다
음수 마진은 일부 브라우저에서 클릭 영역 오프셋을 야기합니다. 가능하면padding-inline-end로 여유 공간을 확보하거나scrollbar-gutter: stable사용을 검토하세요.
63-76:SearchInput기본 글꼴 크기 단위 혼용 주의
font-size를 theme 값으로 지정하고 placeholder 에 다시 지정하고 있어 중복입니다. placeholder 는 상속되므로 별도 지정이 필요 없으며, 필요할 경우inherit로 통일해 유지보수성을 높이세요.- &::placeholder { - color: ${semanticColors.text.ghost}; - font-size: ${typography.fontSize.base}; - } + &::placeholder { + color: ${semanticColors.text.ghost}; + font-size: inherit; + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (8)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc/assets/books/deer.svgis excluded by!**/*.svgsrc/assets/books/hormone.svgis excluded by!**/*.svgsrc/assets/books/life.svgis excluded by!**/*.svgsrc/assets/books/tomato.svgis excluded by!**/*.svgsrc/assets/group/close.svgis excluded by!**/*.svgsrc/assets/group/search.svgis excluded by!**/*.svgsrc/assets/group/search_white.svgis excluded by!**/*.svg
📒 Files selected for processing (21)
package.json(1 hunks)src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts(1 hunks)src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx(1 hunks)src/pages/group/CommonSection.styled.ts(1 hunks)src/pages/group/CreateGroup.styled.ts(1 hunks)src/pages/group/CreateGroup.tsx(1 hunks)src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.styled.ts(1 hunks)src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.tsx(1 hunks)src/pages/group/components/ActivityPeriodSection/DateWheel.tsx(1 hunks)src/pages/group/components/ActivityPeriodSection/Wheel.styled.ts(1 hunks)src/pages/group/components/BookSelectionSection.styled.ts(1 hunks)src/pages/group/components/BookSelectionSection.tsx(1 hunks)src/pages/group/components/GenreSelectionSection.styled.ts(1 hunks)src/pages/group/components/GenreSelectionSection.tsx(1 hunks)src/pages/group/components/MemberLimitSection.styled.ts(1 hunks)src/pages/group/components/MemberLimitSection.tsx(1 hunks)src/pages/group/components/PrivacySettingSection.styled.ts(1 hunks)src/pages/group/components/PrivacySettingSection.tsx(1 hunks)src/pages/group/components/RoomInfoSection.styled.ts(1 hunks)src/pages/group/components/RoomInfoSection.tsx(1 hunks)src/pages/index.tsx(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (14)
src/pages/group/components/PrivacySettingSection.styled.ts (1)
src/styles/global/global.ts (2)
semanticColors(79-152)typography(56-76)
src/pages/group/CommonSection.styled.ts (1)
src/styles/global/global.ts (3)
colors(4-53)semanticColors(79-152)typography(56-76)
src/pages/group/components/MemberLimitSection.tsx (2)
src/pages/group/CommonSection.styled.ts (2)
Section(4-11)SectionTitle(13-18)src/pages/group/components/MemberLimitSection.styled.ts (3)
MemberLimitContainer(4-9)MemberWheelContainer(11-15)MemberText(17-21)
src/pages/group/components/RoomInfoSection.tsx (2)
src/pages/group/CommonSection.styled.ts (2)
Section(4-11)SectionTitle(13-18)src/pages/group/components/RoomInfoSection.styled.ts (3)
TextAreaBox(4-9)TextArea(11-28)CharacterCount(30-35)
src/pages/group/components/RoomInfoSection.styled.ts (1)
src/styles/global/global.ts (2)
semanticColors(79-152)typography(56-76)
src/pages/group/components/GenreSelectionSection.styled.ts (1)
src/styles/global/global.ts (3)
semanticColors(79-152)colors(4-53)typography(56-76)
src/pages/group/components/PrivacySettingSection.tsx (2)
src/pages/group/CommonSection.styled.ts (2)
Section(4-11)SectionTitle(13-18)src/pages/group/components/PrivacySettingSection.styled.ts (4)
PrivacyToggleContainer(4-8)PrivacyLabel(10-14)ToggleSwitch(16-25)ToggleSlider(27-36)
src/pages/group/components/GenreSelectionSection.tsx (3)
src/pages/group/CommonSection.styled.ts (2)
Section(4-11)SectionTitle(13-18)src/pages/group/components/GenreSelectionSection.styled.ts (2)
GenreButtonGroup(4-8)GenreButton(10-21)src/styles/global/global.ts (2)
semanticColors(79-152)typography(56-76)
src/pages/group/components/BookSelectionSection.styled.ts (1)
src/styles/global/global.ts (3)
semanticColors(79-152)colors(4-53)typography(56-76)
src/pages/group/CreateGroup.tsx (2)
src/pages/group/CreateGroup.styled.ts (1)
Container(4-14)src/pages/group/CommonSection.styled.ts (1)
Section(4-11)
src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.tsx (2)
src/pages/group/CommonSection.styled.ts (2)
Section(4-11)SectionTitle(13-18)src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.styled.ts (7)
DatePickerContainer(4-6)DateRangeContainer(8-15)DateGroup(17-23)DateUnitText(92-97)SeparatorText(99-106)ErrorText(116-122)InfoText(108-114)
src/pages/group/components/ActivityPeriodSection/Wheel.styled.ts (2)
src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.styled.ts (1)
WheelContainer(25-31)src/styles/global/global.ts (3)
colors(4-53)typography(56-76)semanticColors(79-152)
src/pages/group/components/ActivityPeriodSection/DateWheel.tsx (2)
src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.styled.ts (1)
WheelContainer(25-31)src/pages/group/components/ActivityPeriodSection/Wheel.styled.ts (7)
WheelContainer(4-12)WheelShadowTop(32-42)WheelInner(14-24)WheelSlides(26-30)WheelSlide(67-82)WheelLabel(57-65)WheelShadowBottom(44-55)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts (1)
src/styles/global/global.ts (3)
colors(4-53)semanticColors(79-152)typography(56-76)
🪛 ESLint
src/pages/group/CreateGroup.tsx
[error] 18-18: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
[error] 49-49: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/pages/group/components/ActivityPeriodSection/DateWheel.tsx
[error] 35-35: 'slideDegree' is assigned a value but never used.
(@typescript-eslint/no-unused-vars)
[error] 36-36: 'slidesPerView' is assigned a value but never used.
(@typescript-eslint/no-unused-vars)
[error] 150-150: 'offset' is assigned a value but never used.
(@typescript-eslint/no-unused-vars)
🔇 Additional comments (20)
package.json (1)
19-19: 보안 취약점 및 버전 호환성 직접 검증이 필요합니다현재
npm audit를 위한 lockfile 생성이 실패하여 자동 검사 결과를 확인할 수 없습니다. 아래 사항을 직접 검토해주세요.
package-lock.json또는yarn.lock을 정상 생성한 뒤 다시npm audit또는yarn audit를 실행react-datepicker@8.4.0가 최신 버전인지npm view react-datepicker version또는 공식 GitHub 릴리즈 페이지에서 확인- 해당 버전에 보고된 보안 취약점(CVE)이나 릴리즈 노트상의 breaking change 여부 검토
src/pages/group/CommonSection.styled.ts (1)
13-18: LGTM!
SectionTitle컴포넌트는 일관된 스타일을 적용하고 있으며, 전역 스타일 시스템을 잘 활용하고 있습니다.src/pages/group/components/PrivacySettingSection.tsx (1)
9-12: 인터페이스 정의가 명확하고 적절함Props 인터페이스가 명확하게 정의되어 있고, 불필요한 복잡성 없이 단순하게 구현되어 있습니다.
src/pages/group/components/GenreSelectionSection.styled.ts (1)
4-8: 잘 구조화된 버튼 그룹 레이아웃Flexbox를 사용한 버튼 그룹 레이아웃이 적절하게 구현되어 있습니다.
flex-wrap: wrap과gap: 12px를 통해 반응형 레이아웃을 지원합니다.src/pages/group/components/MemberLimitSection.styled.ts (1)
4-21: 깔끔하고 일관된 스타일링 구현Flexbox를 활용한 레이아웃이 명확하고 의미있게 구성되어 있습니다. 각 컨테이너의 역할이 명확하게 분리되어 있고, 전역 디자인 시스템을 일관되게 사용하고 있습니다.
src/pages/group/components/RoomInfoSection.styled.ts (3)
4-9: 적절한 컨테이너 스타일링투명한 배경과 최소한의 스타일링으로 컨테이너를 구성한 것이 적절합니다. 불필요한 스타일링을 피하고 기본 레이아웃만 제공하는 좋은 접근법입니다.
11-28: 브라우저 기본 스타일 초기화가 잘 구현됨textarea의 기본 스타일을 완전히 제거하고 커스텀 스타일을 적용한 것이 적절합니다. 특히
resize: none과outline: none처리, 그리고 플레이스홀더 스타일링이 잘 되어 있습니다.
30-35: 문자 수 표시 스타일링이 직관적임녹색 포인트 컬러와 우측 정렬을 통해 사용자가 문자 수 제한을 쉽게 인지할 수 있도록 구현되어 있습니다.
src/pages/group/components/MemberLimitSection.tsx (1)
14-16: 효율적인 배열 생성 구현
Array.from을 사용한 1-30 범위 배열 생성이 깔끔하고 직관적입니다. 성능상 문제없는 구현입니다.src/pages/group/components/RoomInfoSection.tsx (1)
11-50: 잘 구현된 컴포넌트입니다.텍스트 영역 입력 처리, 문자 제한, 실시간 문자 카운트 표시가 모두 올바르게 구현되어 있습니다. 컴포넌트 구조도 깔끔하고 React 모범 사례를 잘 따르고 있습니다.
src/pages/group/components/ActivityPeriodSection/Wheel.styled.ts (1)
4-82: 3D 휠 UI를 위한 스타일 컴포넌트가 잘 구현되었습니다.3D 변환, 원근감, 그림자 효과, z-index 관리가 모두 적절하게 구현되어 있습니다. 복잡한 3D 인터페이스를 위한 스타일 구조가 체계적으로 잘 조직되어 있습니다.
src/pages/group/CreateGroup.tsx (1)
62-62: 폼 유효성 검사 로직이 올바르게 구현되었습니다.책 선택 또는 책 제목 입력과 장르 선택을 모두 확인하는 검증 로직이 적절하게 구현되어 있습니다.
src/pages/group/components/BookSelectionSection.styled.ts (1)
1-84: 잘 구조화된 styled components 모듈입니다.코드가 깔끔하고 조건부 스타일링이 적절하게 적용되어 있습니다. 의미론적 색상과 타이포그래피를 일관되게 사용하고 있어 좋습니다.
src/pages/group/components/ActivityPeriodSection/DateWheel.tsx (1)
61-93: 인터랙션 로직이 잘 구현되어 있습니다.터치 및 마우스 드래그 이벤트 처리가 적절하게 구현되어 있고, 감도 조절과 경계 검사가 올바르게 수행되고 있습니다.
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (2)
92-103: 검색 필터링 로직이 잘 구현되어 있습니다.제목과 저자 모두에서 대소문자 구분 없이 검색하는 로직이 올바르게 구현되어 있습니다.
105-115: body 스크롤 관리가 적절하게 구현되어 있습니다.모달이 열릴 때 body 스크롤을 비활성화하고 cleanup 함수에서 복원하는 로직이 올바르게 구현되어 있습니다.
src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.tsx (2)
43-54: 날짜 차이 계산 로직이 올바르게 구현되어 있습니다.
calculateDaysDifference함수와useMemo를 활용한 성능 최적화가 적절하게 적용되어 있습니다. 시작일 포함(+1) 처리도 올바릅니다.
86-120: 포괄적인 날짜 유효성 검사 로직입니다.다양한 케이스를 고려한 날짜 유효성 검사가 잘 구현되어 있습니다. 월별 일수 조정, 과거 날짜 방지, 종료일 검증 등이 모두 처리되고 있습니다.
src/pages/group/components/ActivityPeriodSection/ActivityPeriodSection.styled.ts (2)
1-122: 잘 구조화된 styled components 모듈입니다.날짜 선택기와 휠 선택기를 위한 스타일링이 체계적으로 구현되어 있습니다. 의미론적 색상과 타이포그래피가 일관되게 사용되고 있어 좋습니다.
33-65: 스크롤바 숨김 처리가 적절하게 구현되어 있습니다.다양한 브라우저 호환성을 고려한 스크롤바 숨김 처리가 잘 구현되어 있고, 가상 요소를 활용한 선택 영역 표시도 적절합니다.
| "dependencies": { | ||
| "@emotion/react": "^11.14.0", | ||
| "@emotion/styled": "^11.14.0", | ||
| "@types/react-datepicker": "^7.0.0", |
There was a problem hiding this comment.
🛠️ Refactor suggestion
타입 정의 패키지를 devDependencies로 이동해야 합니다.
@types/react-datepicker는 개발 시에만 필요한 타입 정의이므로 devDependencies로 이동하는 것이 좋습니다.
- "@types/react-datepicker": "^7.0.0",그리고 devDependencies에 추가:
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
+ "@types/react-datepicker": "^7.0.0",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "@types/react-datepicker": "^7.0.0", | |
| { | |
| "dependencies": { | |
| "react-datepicker": "8.4.0" | |
| }, | |
| "devDependencies": { | |
| "@types/react": "^19.1.2", | |
| "@types/react-dom": "^19.1.2", | |
| "@types/react-datepicker": "^7.0.0" | |
| } | |
| } |
🤖 Prompt for AI Agents
In package.json at line 17, the dependency "@types/react-datepicker" is
currently listed under dependencies but should be moved to devDependencies
because it is only needed during development for type definitions. Remove it
from dependencies and add it to the devDependencies section instead.
| export const ToggleSlider = styled.div<{ active: boolean }>` | ||
| width: 20px; | ||
| height: 20px; | ||
| background-color: ${semanticColors.text.primary}; | ||
| border-radius: 50%; | ||
| position: absolute; | ||
| top: 4px; | ||
| left: ${({ active }) => (active ? '24px' : '4px')}; | ||
| transition: left 0.3s; | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
토글 슬라이더 상수화 및 계산 로직 개선이 필요합니다.
슬라이더의 위치 계산을 상수화하고 명확하게 해야 합니다.
+const SLIDER_DIMENSIONS = {
+ size: 20,
+ margin: 4,
+ transitionDuration: '0.3s',
+} as const;
+
export const ToggleSlider = styled.div<{ active: boolean }>`
- width: 20px;
- height: 20px;
+ width: ${SLIDER_DIMENSIONS.size}px;
+ height: ${SLIDER_DIMENSIONS.size}px;
background-color: ${semanticColors.text.primary};
border-radius: 50%;
position: absolute;
- top: 4px;
- left: ${({ active }) => (active ? '24px' : '4px')};
- transition: left 0.3s;
+ top: ${SLIDER_DIMENSIONS.margin}px;
+ left: ${({ active }) =>
+ active
+ ? `${TOGGLE_DIMENSIONS.width - SLIDER_DIMENSIONS.size - SLIDER_DIMENSIONS.margin}px`
+ : `${SLIDER_DIMENSIONS.margin}px`
+ };
+ transition: left ${SLIDER_DIMENSIONS.transitionDuration};
`;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/pages/group/components/PrivacySettingSection.styled.ts between lines 27
and 36, the left position values for the ToggleSlider are hardcoded inline.
Refactor by defining constants for the left positions (e.g., LEFT_ACTIVE and
LEFT_INACTIVE) and use these constants in the styled component. This improves
clarity and maintainability of the position calculation logic.
| export const ToggleSwitch = styled.div<{ active: boolean }>` | ||
| width: 48px; | ||
| height: 28px; | ||
| background-color: ${({ active }) => | ||
| active ? semanticColors.button.fill.primary : semanticColors.background.card}; | ||
| border-radius: 14px; | ||
| position: relative; | ||
| cursor: pointer; | ||
| transition: background-color 0.3s; | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
토글 스위치의 접근성과 상수화가 필요합니다.
하드코딩된 값들을 상수로 분리하고, 접근성을 위한 프로퍼티를 추가해야 합니다.
+const TOGGLE_DIMENSIONS = {
+ width: 48,
+ height: 28,
+ borderRadius: 14,
+ transitionDuration: '0.3s',
+} as const;
+
export const ToggleSwitch = styled.div<{ active: boolean }>`
- width: 48px;
- height: 28px;
+ width: ${TOGGLE_DIMENSIONS.width}px;
+ height: ${TOGGLE_DIMENSIONS.height}px;
background-color: ${({ active }) =>
active ? semanticColors.button.fill.primary : semanticColors.background.card};
- border-radius: 14px;
+ border-radius: ${TOGGLE_DIMENSIONS.borderRadius}px;
position: relative;
cursor: pointer;
- transition: background-color 0.3s;
+ transition: background-color ${TOGGLE_DIMENSIONS.transitionDuration};
+
+ &:focus {
+ outline: 2px solid ${semanticColors.button.fill.primary};
+ outline-offset: 2px;
+ }
`;🤖 Prompt for AI Agents
In src/pages/group/components/PrivacySettingSection.styled.ts around lines 16 to
25, the ToggleSwitch component uses hardcoded style values and lacks
accessibility attributes. Refactor by extracting hardcoded numeric and color
values into named constants for maintainability. Additionally, add appropriate
accessibility properties such as role="switch" and aria-checked attributes to
improve screen reader support and keyboard navigation.
| export const Container = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| background-color: ${semanticColors.background.primary}; | ||
| min-width: 360px; | ||
| max-width: 767px; | ||
| min-height: 100vh; | ||
| margin: 0 auto; | ||
| padding: 96px 20px 100px 20px; | ||
| box-sizing: border-box; | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
반응형 디자인과 상수화 개선이 필요합니다.
레이아웃 값들을 상수화하고 반응형 디자인을 더 명확하게 구성해야 합니다.
+const LAYOUT_CONSTANTS = {
+ minWidth: 360,
+ maxWidth: 767,
+ padding: {
+ top: 96,
+ horizontal: 20,
+ bottom: 100,
+ },
+} as const;
+
export const Container = styled.div`
display: flex;
flex-direction: column;
background-color: ${semanticColors.background.primary};
- min-width: 360px;
- max-width: 767px;
+ min-width: ${LAYOUT_CONSTANTS.minWidth}px;
+ max-width: ${LAYOUT_CONSTANTS.maxWidth}px;
min-height: 100vh;
margin: 0 auto;
- padding: 96px 20px 100px 20px;
+ padding: ${LAYOUT_CONSTANTS.padding.top}px ${LAYOUT_CONSTANTS.padding.horizontal}px ${LAYOUT_CONSTANTS.padding.bottom}px;
box-sizing: border-box;
+
+ @media (max-width: 480px) {
+ padding-left: 16px;
+ padding-right: 16px;
+ }
`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const Container = styled.div` | |
| display: flex; | |
| flex-direction: column; | |
| background-color: ${semanticColors.background.primary}; | |
| min-width: 360px; | |
| max-width: 767px; | |
| min-height: 100vh; | |
| margin: 0 auto; | |
| padding: 96px 20px 100px 20px; | |
| box-sizing: border-box; | |
| `; | |
| const LAYOUT_CONSTANTS = { | |
| minWidth: 360, | |
| maxWidth: 767, | |
| padding: { | |
| top: 96, | |
| horizontal: 20, | |
| bottom: 100, | |
| }, | |
| } as const; | |
| export const Container = styled.div` | |
| display: flex; | |
| flex-direction: column; | |
| background-color: ${semanticColors.background.primary}; | |
| min-width: ${LAYOUT_CONSTANTS.minWidth}px; | |
| max-width: ${LAYOUT_CONSTANTS.maxWidth}px; | |
| min-height: 100vh; | |
| margin: 0 auto; | |
| padding: ${LAYOUT_CONSTANTS.padding.top}px ${LAYOUT_CONSTANTS.padding.horizontal}px ${LAYOUT_CONSTANTS.padding.bottom}px; | |
| box-sizing: border-box; | |
| @media (max-width: 480px) { | |
| padding-left: 16px; | |
| padding-right: 16px; | |
| } | |
| `; |
🤖 Prompt for AI Agents
In src/pages/group/CreateGroup.styled.ts around lines 4 to 14, the layout values
such as widths, padding, and margins should be extracted into constants for
better maintainability. Additionally, refactor the styles to include clear
responsive design rules using media queries or similar techniques to adapt the
layout for different screen sizes. Define breakpoints as constants and apply
them to adjust container dimensions and padding accordingly.
| const PrivacySettingSection = ({ isPrivate, onToggle }: PrivacySettingSectionProps) => { | ||
| return ( | ||
| <Section> | ||
| <SectionTitle>공개 설정</SectionTitle> | ||
| <PrivacyToggleContainer> | ||
| <PrivacyLabel>비공개로 설정하기</PrivacyLabel> | ||
| <ToggleSwitch active={isPrivate} onClick={onToggle}> | ||
| <ToggleSlider active={isPrivate} /> | ||
| </ToggleSwitch> | ||
| </PrivacyToggleContainer> | ||
| </Section> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
접근성 개선이 필요한 토글 스위치 구현
현재 토글 스위치가 div 요소로 구현되어 있어 키보드 네비게이션과 스크린 리더 지원이 부족합니다. 다음과 같은 접근성 개선을 권장합니다:
- <ToggleSwitch active={isPrivate} onClick={onToggle}>
- <ToggleSlider active={isPrivate} />
- </ToggleSwitch>
+ <ToggleSwitch
+ active={isPrivate}
+ onClick={onToggle}
+ role="switch"
+ aria-checked={isPrivate}
+ aria-labelledby="privacy-label"
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onToggle();
+ }
+ }}
+ >
+ <ToggleSlider active={isPrivate} />
+ </ToggleSwitch>그리고 라벨에 id를 추가하세요:
- <PrivacyLabel>비공개로 설정하기</PrivacyLabel>
+ <PrivacyLabel id="privacy-label">비공개로 설정하기</PrivacyLabel>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const PrivacySettingSection = ({ isPrivate, onToggle }: PrivacySettingSectionProps) => { | |
| return ( | |
| <Section> | |
| <SectionTitle>공개 설정</SectionTitle> | |
| <PrivacyToggleContainer> | |
| <PrivacyLabel>비공개로 설정하기</PrivacyLabel> | |
| <ToggleSwitch active={isPrivate} onClick={onToggle}> | |
| <ToggleSlider active={isPrivate} /> | |
| </ToggleSwitch> | |
| </PrivacyToggleContainer> | |
| </Section> | |
| ); | |
| }; | |
| const PrivacySettingSection = ({ isPrivate, onToggle }: PrivacySettingSectionProps) => { | |
| return ( | |
| <Section> | |
| <SectionTitle>공개 설정</SectionTitle> | |
| <PrivacyToggleContainer> | |
| <PrivacyLabel id="privacy-label">비공개로 설정하기</PrivacyLabel> | |
| <ToggleSwitch | |
| active={isPrivate} | |
| onClick={onToggle} | |
| role="switch" | |
| aria-checked={isPrivate} | |
| aria-labelledby="privacy-label" | |
| tabIndex={0} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| onToggle(); | |
| } | |
| }} | |
| > | |
| <ToggleSlider active={isPrivate} /> | |
| </ToggleSwitch> | |
| </PrivacyToggleContainer> | |
| </Section> | |
| ); | |
| }; |
🤖 Prompt for AI Agents
In src/pages/group/components/PrivacySettingSection.tsx around lines 14 to 26,
the toggle switch is implemented using div elements, which lack keyboard
navigation and screen reader support. Replace the div-based toggle with a native
accessible input element of type checkbox, ensuring it can be focused and
toggled via keyboard. Also, add an id to the label and associate it with the
input using the htmlFor attribute to improve screen reader accessibility.
| const slideValues = () => { | ||
| if (!sliderState || !sliderState.slides) return []; | ||
|
|
||
| const offset = 0; | ||
| const valuesArray = []; | ||
|
|
||
| for (let i = 0; i < slides; i++) { | ||
| const slideData = sliderState.slides[i]; | ||
| if (!slideData) continue; | ||
|
|
||
| const distance = slideData.distance || 0; | ||
| const threshold = wheelSize / 2 + 1; | ||
| const rotate = Math.abs(distance) > threshold ? 180 : distance * (360 / wheelSize) * -1; | ||
|
|
||
| const isSelected = i === currentIndex; | ||
|
|
||
| const style = { | ||
| transform: `rotateX(${rotate}deg) translateZ(${radius}px)`, | ||
| WebkitTransform: `rotateX(${rotate}deg) translateZ(${radius}px)`, | ||
| }; | ||
|
|
||
| const value = values[i]; | ||
| valuesArray.push({ style, value, isSelected }); | ||
| } | ||
|
|
||
| return valuesArray; | ||
| }; |
There was a problem hiding this comment.
slideValues 함수의 미사용 변수를 제거하세요.
offset 변수가 선언되었지만 사용되지 않고 있습니다.
다음 diff를 적용하여 미사용 변수를 제거하세요:
const slideValues = () => {
if (!sliderState || !sliderState.slides) return [];
- const offset = 0;
const valuesArray = [];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const slideValues = () => { | |
| if (!sliderState || !sliderState.slides) return []; | |
| const offset = 0; | |
| const valuesArray = []; | |
| for (let i = 0; i < slides; i++) { | |
| const slideData = sliderState.slides[i]; | |
| if (!slideData) continue; | |
| const distance = slideData.distance || 0; | |
| const threshold = wheelSize / 2 + 1; | |
| const rotate = Math.abs(distance) > threshold ? 180 : distance * (360 / wheelSize) * -1; | |
| const isSelected = i === currentIndex; | |
| const style = { | |
| transform: `rotateX(${rotate}deg) translateZ(${radius}px)`, | |
| WebkitTransform: `rotateX(${rotate}deg) translateZ(${radius}px)`, | |
| }; | |
| const value = values[i]; | |
| valuesArray.push({ style, value, isSelected }); | |
| } | |
| return valuesArray; | |
| }; | |
| const slideValues = () => { | |
| if (!sliderState || !sliderState.slides) return []; | |
| const valuesArray = []; | |
| for (let i = 0; i < slides; i++) { | |
| const slideData = sliderState.slides[i]; | |
| if (!slideData) continue; | |
| const distance = slideData.distance || 0; | |
| const threshold = wheelSize / 2 + 1; | |
| const rotate = Math.abs(distance) > threshold | |
| ? 180 | |
| : distance * (360 / wheelSize) * -1; | |
| const isSelected = i === currentIndex; | |
| const style = { | |
| transform: `rotateX(${rotate}deg) translateZ(${radius}px)`, | |
| WebkitTransform: `rotateX(${rotate}deg) translateZ(${radius}px)`, | |
| }; | |
| const value = values[i]; | |
| valuesArray.push({ style, value, isSelected }); | |
| } | |
| return valuesArray; | |
| }; |
🧰 Tools
🪛 ESLint
[error] 150-150: 'offset' is assigned a value but never used.
(@typescript-eslint/no-unused-vars)
🤖 Prompt for AI Agents
In src/pages/group/components/ActivityPeriodSection/DateWheel.tsx between lines
147 and 173, the variable 'offset' is declared but never used in the slideValues
function. Remove the declaration of 'offset' to clean up the code and eliminate
the unused variable warning.
| export const BookItem = styled.div` | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| padding: 12px 0; | ||
| border-bottom: 1px solid ${colors.grey[400]}; | ||
| cursor: pointer; | ||
| `; |
There was a problem hiding this comment.
클릭 가능한 항목을 div 로 표현하면 접근성이 크게 저하됩니다
BookItem 은 실제 버튼 역할이므로 시맨틱 요소(<button>/<li role="button">)로 변경하거나 최소한 role="button" 과 tabIndex={0} 를 부여하고 키보드 Enter/Space 이벤트를 처리해야 합니다.
🤖 Prompt for AI Agents
In src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts
around lines 172 to 179, the BookItem styled component is a clickable div which
harms accessibility. To fix this, change the element to a semantic button or add
role="button" and tabIndex={0} to make it focusable, and implement keyboard
event handlers for Enter and Space keys to mimic button behavior.
| export const IconButton = styled.button` | ||
| background: none; | ||
| border: none; | ||
| cursor: pointer; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border-radius: 4px; | ||
| padding: 0; | ||
|
|
||
| img { | ||
| width: 24px; | ||
| height: 24px; | ||
| } | ||
|
|
||
| &:hover { | ||
| background-color: rgba(255, 255, 255, 0.1); | ||
| } | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
포커스 스타일이 없어 키보드 접근성이 떨어집니다
IconButton 은 hover 만 있고 :focus-visible 스타일이 없습니다. 시각적 포커스 표시를 추가해 스크린리더·키보드 사용자에게 명확히 알려주세요.
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
+
+ &:focus-visible {
+ outline: 2px solid ${semanticColors.text.point.green};
+ outline-offset: 2px;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const IconButton = styled.button` | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 4px; | |
| padding: 0; | |
| img { | |
| width: 24px; | |
| height: 24px; | |
| } | |
| &:hover { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| } | |
| `; | |
| export const IconButton = styled.button` | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 4px; | |
| padding: 0; | |
| img { | |
| width: 24px; | |
| height: 24px; | |
| } | |
| &:hover { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| } | |
| &:focus-visible { | |
| outline: 2px solid ${semanticColors.text.point.green}; | |
| outline-offset: 2px; | |
| } | |
| `; |
🤖 Prompt for AI Agents
In src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts
between lines 84 and 102, the IconButton styled component lacks a :focus-visible
style, reducing keyboard accessibility. Add a :focus-visible CSS rule to
IconButton that provides a clear visual focus indicator, such as an outline or
box-shadow, to improve usability for keyboard and screen reader users.
| export const Tab = styled.button<{ active: boolean }>` | ||
| background: none; | ||
| border: none; | ||
| color: ${({ active }) => (active ? semanticColors.text.primary : semanticColors.text.ghost)}; | ||
| font-size: ${typography.fontSize.sm}; | ||
| font-weight: ${typography.fontWeight.semibold}; | ||
| padding: 8px 0 8px 0; | ||
| cursor: pointer; | ||
| position: relative; | ||
|
|
||
| ${({ active }) => | ||
| active && | ||
| ` | ||
| &::after { | ||
| content: ''; | ||
| position: absolute; | ||
| bottom: -1px; | ||
| left: 0; | ||
| right: 0; | ||
| height: 2px; | ||
| background-color: ${semanticColors.text.primary}; | ||
| } | ||
| `} | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
탭 버튼에도 포커스·ARIA 속성이 필요합니다
Tab 컴포넌트는 시맨틱 버튼이지만 포커스 표시 및 aria-selected 처리 없이 컬러만 변경하고 있습니다. 스크린리더 지원을 위해 prop으로 aria-selected={active} 를 전달하고 :focus-visible 스타일을 추가하세요.
-export const Tab = styled.button<{ active: boolean }>`
+export const Tab = styled.button<{ active: boolean }>`
/* ... */
position: relative;
+
+ &:focus-visible {
+ outline: 2px solid ${semanticColors.text.point.green};
+ outline-offset: 2px;
+ }
`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const Tab = styled.button<{ active: boolean }>` | |
| background: none; | |
| border: none; | |
| color: ${({ active }) => (active ? semanticColors.text.primary : semanticColors.text.ghost)}; | |
| font-size: ${typography.fontSize.sm}; | |
| font-weight: ${typography.fontWeight.semibold}; | |
| padding: 8px 0 8px 0; | |
| cursor: pointer; | |
| position: relative; | |
| ${({ active }) => | |
| active && | |
| ` | |
| &::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: -1px; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background-color: ${semanticColors.text.primary}; | |
| } | |
| `} | |
| `; | |
| export const Tab = styled.button<{ active: boolean }>` | |
| background: none; | |
| border: none; | |
| color: ${({ active }) => | |
| active | |
| ? semanticColors.text.primary | |
| : semanticColors.text.ghost}; | |
| font-size: ${typography.fontSize.sm}; | |
| font-weight: ${typography.fontWeight.semibold}; | |
| padding: 8px 0 8px 0; | |
| cursor: pointer; | |
| position: relative; | |
| &:focus-visible { | |
| outline: 2px solid ${semanticColors.text.point.green}; | |
| outline-offset: 2px; | |
| } | |
| ${({ active }) => | |
| active && | |
| ` | |
| &::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: -1px; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background-color: ${semanticColors.text.primary}; | |
| } | |
| `} | |
| `; |
🤖 Prompt for AI Agents
In src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts
between lines 111 and 134, the Tab styled button lacks accessibility features.
Add the aria-selected attribute with the value of the active prop to the Tab
component and include a :focus-visible CSS style to provide a visible focus
indicator for keyboard users. This will improve screen reader support and
keyboard navigation accessibility.
| max-width: 767px; | ||
| margin: 0 auto; | ||
| background-color: ${colors.darkgrey.main}; | ||
| border-radius: 20px 20px 0 0; | ||
| z-index: 1001; | ||
| transform: translateY(${({ isVisible }) => (isVisible ? '0' : '100%')}); | ||
| transition: transform 0.3s ease; | ||
| max-height: 50vh; | ||
| overflow: hidden; | ||
| display: flex; | ||
| flex-direction: column; | ||
| `; | ||
|
|
||
| export const Content = styled.div` | ||
| padding: 20px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| height: 50vh; | ||
| `; |
There was a problem hiding this comment.
max-height와 내부 height가 동일해 내용이 넘칠 수 있습니다
BottomSheetContainer 는 max-height: 50vh, 내부 Content 도 height: 50vh 로 고정돼 헤더·탭·검색바 높이를 고려하지 못합니다. 스크롤 버그가 발생할 가능성이 높으니 Content 는 calc(50vh - 헤더높이) 식으로 계산하거나 flex:1 로 처리하세요.
-export const Content = styled.div`
- padding: 20px;
- display: flex;
- flex-direction: column;
- height: 50vh;
-`;
+export const Content = styled.div`
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ flex: 1; /* 부모 높이에서 헤더 등을 제외한 영역 채우기 */
+`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| max-width: 767px; | |
| margin: 0 auto; | |
| background-color: ${colors.darkgrey.main}; | |
| border-radius: 20px 20px 0 0; | |
| z-index: 1001; | |
| transform: translateY(${({ isVisible }) => (isVisible ? '0' : '100%')}); | |
| transition: transform 0.3s ease; | |
| max-height: 50vh; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| `; | |
| export const Content = styled.div` | |
| padding: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| height: 50vh; | |
| `; | |
| export const Content = styled.div` | |
| padding: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| flex: 1; /* 부모 높이에서 헤더 등을 제외한 영역 채우기 */ | |
| `; |
🤖 Prompt for AI Agents
In src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts
around lines 24 to 42, the Content component has a fixed height of 50vh which
matches the max-height of BottomSheetContainer, causing potential overflow and
scroll issues. To fix this, adjust Content's height to be calculated as 50vh
minus the header height using calc(), or replace the fixed height with flex: 1
to allow it to fill the remaining space flexibly within the container.
heeeeyong
left a comment
There was a problem hiding this comment.
전반적으로 컴포넌트화가 잘 되어있어서 보기 편했습니다. 저도 이런부분에서 공통적인 피드백을 받은것 같은데 5주차에 코드리팩토링할때 반영해보려고 합니다! 그리고 pages > group > components 폴더를 따로 하나 더 빼놓은 이유가 궁금합니다!
| setIsPrivate(!isPrivate); | ||
| }; | ||
|
|
||
| const isFormValid = (selectedBook || bookTitle.trim() !== '') && selectedGenre !== ''; |
| align-items: center; | ||
| justify-content: center; | ||
|
|
||
| /* 스크롤바 숨기기 */ |
There was a problem hiding this comment.
제가 맡은 페이지에서도 스크롤바를 숨겨야하는 상황이 있는데, 전역에 기본적으로 스크롤 바를 가리는 속성을 넣고 특정 컴포넌트에서 스크롤 바가 필요하다면 넣어서 쓰는걸로 하는게 좋을 것 같습니다!
ho0010
left a comment
There was a problem hiding this comment.
LGTM!
datepicker 커스텀 하느라 고생 많이 하셨겠네요..!
styled 컴포넌트 네이밍이 잘 되어있네요... 분리도 잘 되어있어서 재사용성도 좋은 것 같아요~ 전체적으로 스타일링이 일관성이 있어서 가독성이 좋은 것 같아요!
There was a problem hiding this comment.
현재 ActivityPeriodSection은 날짜 계산과 검증 로직, 상태 관리, UI 이렇게 많은 역할을 가지고 있는 것으로 보입니다. 리팩토링할 때 날짜 계산과 검증로직을 utils/date.ts로 따로 분리하면 어떨까 싶습니다! 또한, 상태 로직도 커스텀 훅으로 분리하면 각 객체의 역할이 더 분명해질 것 같아요!

#️⃣연관된 이슈
지라에 등록을 안해서..^^ 죄송죄송
📝작업 내용
독서모임 생성을 위한 모임 만들기 페이지를 구현했습니다.
주요 구현 기능
1. 책 선택 섹션 (BookSelectionSection)
2. 장르 선택 섹션 (GenreSelectionSection)
3. 방 정보 입력 섹션 (RoomInfoSection)
4. 활동 기간 설정 섹션 (ActivityPeriodSection)
5. 인원 제한 섹션 (MemberLimitSection)
6. 공개 설정 섹션 (PrivacySettingSection)
기술적 구현 사항
커스텀 날짜 휠 피커 (DateWheel)
(SPURT 코드를 조금 참고했음..^^)
폼 검증 로직
스크린샷 (선택)
2025-07-09.2.39.52.mov
💬리뷰 요구사항(선택)
휠 피커의 스크린 리더 지원과 키보드 네비게이션 개선이 필요한지 검토해주세요.
Summary by CodeRabbit
신규 기능
/group/create)가 추가되어, 사용자가 독서 모임을 직접 생성할 수 있습니다.스타일
기타
react-datepicker및 타입 정의 패키지가 추가되었습니다.