-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/component/tech stack2/DEVING-49 기술스택 선택 컴포넌트 #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Walkthrough이번 PR은 기술 스택 미리보기 페이지와 관련된 다양한 React 컴포넌트 및 유틸리티 함수들을 새로 도입하고 수정하는 작업입니다. 주요 변경 사항으로는 기술 선택 기능 구현을 위한 Page, TechSelector, 여러 보조 컴포넌트들(예: CategoryTabs, TechButton 등), 커스텀 훅 useTechSelection의 추가와 관련 타입 및 유틸리티 함수의 도입, 그리고 특정 아이콘의 색상 조정이 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as 사용자
participant P as Page
participant TS as TechSelector
participant H as useTechSelection
participant TB as TechButton
U->>P: 페이지 요청
P->>TS: TechSelector 렌더링
TS->>H: 초기 선택 상태 설정
U->>TB: 기술 버튼 클릭
TB->>H: 선택/해제 처리 요청
H->>TS: 상태 업데이트 후 콜백 실행
TS->>P: 선택 업데이트 알림
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
CI Status Report검사 결과
✅ 모든 검사가 통과되었습니다. |
Codecov ReportAll modified and coverable lines are covered by tests ✅ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (19)
src/components/ui/tech-stack/tech-stack-components/TechButton.tsx (3)
25-40: 버튼 접근성 향상이 필요합니다.버튼 컴포넌트의 접근성을 높이기 위해 aria 속성을 추가하는 것이 좋습니다.
<button className={`flex items-center gap-1 rounded-full border px-2 py-1 text-xs transition-all hover:shadow-md lg:gap-2 lg:px-3 lg:py-1.5 lg:text-sm ${isClicked ? 'bg-white' : ''} ${isMaxReached ? 'cursor-not-allowed opacity-50' : ''}`} onClick={() => onClick(name)} disabled={isMaxReached} title={isMaxReached ? '최대 5개까지만 선택할 수 있습니다' : ''} + aria-pressed={isClicked} + aria-label={`${name} 기술 선택하기`} >
27-29: transition-all보다 구체적인 속성 전환 지정이 필요합니다.성능 최적화를 위해
transition-all대신 실제로 전환되는 구체적인 속성만 지정하는 것이 좋습니다.- text-xs transition-all hover:shadow-md lg:gap-2 lg:px-3 lg:py-1.5 lg:text-sm + text-xs transition-[background-color,opacity,box-shadow,color] duration-200 hover:shadow-md lg:gap-2 lg:px-3 lg:py-1.5 lg:text-sm
35-38: 인라인 스타일 대신 className을 사용하는 것이 좋습니다.인라인 스타일 대신 Tailwind의
text-[color]또는 동적 클래스를 사용하는 것이 일관성 있는 스타일링을 위해 좋습니다.- <p style={{ color: iconColor }} className="font-medium"> + <p className={`font-medium`} style={{ color: iconColor }}>참고: 완전히 인라인 스타일을 제거하려면 동적 색상 적용을 위한 별도의 유틸리티 함수나 방식이 필요할 수 있습니다.
src/app/preview/tech-stack3/page.tsx (2)
19-25: 불필요한 div 래퍼를 제거하는 것이 좋습니다.TechSelector 주변의 div 래퍼가 추가적인 스타일이나 기능을 제공하지 않는다면 제거할 수 있습니다.
return ( - <div> <TechSelector maxSelections={5} onSelectionChange={handleSelectionChange} /> - </div> );
14-17: 코드 주석 스타일 개선이 필요합니다.주석이 명확하지만, JSDoc 스타일을 사용하면 기능과 가능한 확장 부분을 더 명확하게 문서화할 수 있습니다.
- // 여기서 필요한 상태 업데이트 또는 API 호출 등을 수행할 수 있습니다. - // 예: 선택된 기술 정보를 서버에 전송 + /** + * TODO: 여기에 선택된 기술 정보를 처리하는 로직 추가 + * - 상태 업데이트 + * - API 호출 (예: 선택된 기술 정보를 서버에 전송) + */src/components/ui/tech-stack/tech-stack-components/TabButton.tsx (2)
18-22: 하드코딩된 색상 값을 상수나 테마 변수로 분리하는 것이 좋습니다.하드코딩된 색상 값(#C586C0)은 상수나 테마 변수로 분리하는 것이 일관성과 유지보수성을 높입니다.
- ? 'border-b-2 border-[#C586C0] text-[#C586C0]' + ? 'border-b-2 border-primary text-primary'이 경우 Tailwind 설정에 primary 색상을 정의하거나, 컴포넌트에 색상 props를 추가하는 방법도 고려할 수 있습니다.
18-22: Tailwind 클래스를 더 체계적으로 구성하는 것이 좋습니다.가독성을 높이기 위해 Tailwind 클래스를 레이아웃, 스타일링, 반응형 순으로 그룹화하는 것이 좋습니다.
- className={`px-2 py-2 font-medium sm:px-4 ${ + className={`px-2 py-2 sm:px-4 font-medium ${또는 @apply 지시문을 사용하여 클래스를 더 체계적으로 구성할 수도 있습니다.
src/types/techStack.ts (2)
4-7: JSDoc 형식으로 주석을 작성하는 것이 좋습니다.주석을 JSDoc 형식으로 변경하면 더 나은 IDE 지원과 문서화가 가능합니다.
- // 클릭된 버튼 상태를 위한 타입 + /** + * 클릭된 버튼의 상태를 추적하기 위한 타입 + * 키는 버튼 이름, 값은 클릭 상태를 나타냄 + */ export type ClickedButtonsState = { [key: string]: boolean; };
9-14: IconComponent 타입에 더 많은 유연성 추가가 필요합니다.IconComponent 타입이 잘 정의되어 있지만, 더 많은 유연성을 위해 추가 속성을 허용하는 것이 좋을 수 있습니다.
- export type IconComponent = React.ComponentType<{ + export type IconComponent = React.ComponentType<{ size?: number; color?: string; className?: string; + [key: string]: any; // 추가 props를 허용 }>;참고: 타입 안전성을 유지하기 위해서는 구체적인 추가 속성을 정의하는 것이 더 좋을 수 있습니다.
src/components/ui/tech-stack/tech-stack-components/TechButtonList.tsx (1)
22-44: 스크롤바 스타일링에 접근성 개선이 필요합니다.스크롤바 커스터마이징은 잘 되어 있지만, 몇 가지 개선할 점이 있습니다:
- 스크롤바 컬러 값을 Tailwind 색상 변수로 통일하면 좋겠습니다
- 스크롤바 커스터마이징에 대한 주석 설명이 있으면 유지보수가 쉬워질 것 같습니다
- 접근성을 위해 키보드 탐색이 가능하도록 역할(role) 속성을 추가하는 것이 좋겠습니다
- style={{ - scrollbarWidth: 'thin', - scrollbarColor: '#a0aec0 transparent', - }} + style={{ + scrollbarWidth: 'thin', + scrollbarColor: 'var(--color-gray-500) transparent', + }} + role="list" + aria-label="기술 스택 목록"src/components/ui/tech-stack/tech-stack-components/SelectedTechButton.tsx (1)
20-39: 삭제 버튼 스타일 및 접근성 개선이 필요합니다.삭제 버튼 구현에 몇 가지 개선할 점이 있습니다:
- Tailwind 클래스 순서가 일관성이 없습니다 (
hover:bg-gray-200 ml-1- 마진이 먼저 와야 합니다)- X 아이콘의 텍스트 색상이 흰색인데, 배경에 따라 대비가 충분하지 않을 수 있습니다
<button onClick={() => onRemove(name)} - className="hover:bg-gray-200 ml-1 cursor-pointer rounded-full p-1" + className="ml-1 cursor-pointer rounded-full p-1 hover:bg-gray-200" aria-label={`${name} 선택 해제`} > - <X size={12} className="text-white" /> + <X size={12} className="text-gray-600" /> </button>src/util/getIconDetail.ts (3)
14-18: getIconColor 함수의 오류 처리 개선이 필요합니다.아이콘 색상을 가져오는 함수에서 아이콘을 찾지 못했을 때 기본 색상을 반환하는 것은 좋지만, 개발 및 디버깅을 위해 경고 메시지를 로깅하는 것이 좋을 것 같습니다.
export const getIconColor = (iconName: string): string => { const icon = ICON_LIST.find((icon) => icon.name === iconName); + if (!icon) { + console.warn(`아이콘을 찾을 수 없습니다: ${iconName}. 기본 색상을 사용합니다.`); + } return icon ? icon.color : '#000000'; };
31-64: getIconsByCategory 함수의 중복 코드 리팩토링이 필요합니다.각 카테고리별로 비슷한 코드가 반복되고 있습니다. 코드 중복을 줄이고 유지보수성을 향상시키기 위해 리팩토링이 필요합니다.
export const getIconsByCategory = ( activeCategory: CategoryType, ): IconWithComponent[] => { + // 모든 아이콘을 반환하는 경우 if (activeCategory === 'all') { return ICON_LIST.map((icon) => ({ ...icon, component: AllIcons[`${icon.name}Icon` as keyof typeof AllIcons], })); } + // 특정 카테고리의 아이콘만 필터링 + const filteredIcons = ICON_LIST.filter((icon) => icon.category === activeCategory); + + // 카테고리에 따른 아이콘 레지스트리 선택 + let iconRegistry; + switch(activeCategory) { + case 'frontend': iconRegistry = frontend; break; + case 'backend': iconRegistry = backend; break; + case 'design': iconRegistry = design; break; + default: return []; + } + + // 필터링된 아이콘에 컴포넌트 추가 + return filteredIcons.map((icon) => ({ + ...icon, + component: iconRegistry[`${icon.name}Icon` as keyof typeof iconRegistry], + })); - switch (activeCategory) { - case 'all': - return ICON_LIST.map((icon) => ({ - ...icon, - component: AllIcons[`${icon.name}Icon` as keyof typeof AllIcons], - })); - case 'frontend': - return ICON_LIST.filter((icon) => icon.category === 'frontend').map( - (icon) => ({ - ...icon, - component: frontend[`${icon.name}Icon` as keyof typeof frontend], - }), - ); - case 'backend': - return ICON_LIST.filter((icon) => icon.category === 'backend').map( - (icon) => ({ - ...icon, - component: backend[`${icon.name}Icon` as keyof typeof backend], - }), - ); - case 'design': - return ICON_LIST.filter((icon) => icon.category === 'design').map( - (icon) => ({ - ...icon, - component: design[`${icon.name}Icon` as keyof typeof design], - }), - ); - default: - return []; - } };
67-69: isLightColor 함수의 개선이 필요합니다.현재 함수는 정확히 '#FFFFFF' 문자열만 확인하는데, 이는 다른 형식의 흰색(예: 'rgb(255,255,255)', '#fff')이나 매우 밝은 색상을 감지하지 못합니다. 더 강력한 검사 로직이 필요합니다.
export const isLightColor = (color: string): boolean => { - return color === '#FFFFFF'; + // HEX 코드를 RGB로 변환하는 함수 + const hexToRgb = (hex: string): number[] => { + // 축약형 변환 (#fff -> #ffffff) + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + const fullHex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b); + + // RGB 값 추출 + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex); + return result + ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] + : [0, 0, 0]; + }; + + // 정확히 흰색인 경우 + if (color.toUpperCase() === '#FFFFFF' || color.toLowerCase() === '#fff') { + return true; + } + + // RGB 형식인 경우 처리 + if (color.startsWith('rgb')) { + const rgbMatch = color.match(/\d+/g); + if (rgbMatch && rgbMatch.length >= 3) { + const [r, g, b] = rgbMatch.map(Number); + // 매우 밝은 색상 검사 (예: 모든 채널이 240 이상) + return r >= 240 && g >= 240 && b >= 240; + } + } + + // HEX 형식인 경우 처리 + if (color.startsWith('#')) { + const [r, g, b] = hexToRgb(color); + // 매우 밝은 색상 검사 + return r >= 240 && g >= 240 && b >= 240; + } + + return false; };src/components/ui/tech-stack/tech-stack-components/CategoryTabs.tsx (2)
18-27: 카테고리 배열은 컴포넌트 외부로 이동하는 것이 좋습니다.현재 categories 배열은 컴포넌트 내부에 정의되어 있어 렌더링마다 새로 생성됩니다. 이 배열을 컴포넌트 외부로 이동하거나 useMemo로 감싸면 불필요한 재생성을 방지할 수 있습니다.
-const CategoryTabs = ({ - activeCategory, - onCategoryChange, - onReset, -}: CategoryTabsProps): JSX.Element => { - const categories: Array<{ - id: CategoryType; - label: string; - smallText: string; - }> = [ - { id: 'all', label: '전체', smallText: 'All' }, - { id: 'frontend', label: '프론트엔드', smallText: 'Front' }, - { id: 'backend', label: '백엔드', smallText: 'Back' }, - { id: 'design', label: '디자인', smallText: 'UI/UX' }, - ]; +const categories: Array<{ + id: CategoryType; + label: string; + smallText: string; +}> = [ + { id: 'all', label: '전체', smallText: 'All' }, + { id: 'frontend', label: '프론트엔드', smallText: 'Front' }, + { id: 'backend', label: '백엔드', smallText: 'Back' }, + { id: 'design', label: '디자인', smallText: 'UI/UX' }, +]; + +const CategoryTabs = ({ + activeCategory, + onCategoryChange, + onReset, +}: CategoryTabsProps): JSX.Element => {
32-41: onClick 핸들러에 useCallback 적용 고려TabButton의 onClick 핸들러가 매 렌더링마다 새로 생성됩니다. 컴포넌트가 자주 리렌더링되는 경우 useCallback을 사용하여 성능을 최적화할 수 있습니다.
+import React, { useCallback } from 'react'; // ... 코드 생략 ... + const handleCategoryChange = useCallback( + (categoryId: CategoryType) => { + onCategoryChange(categoryId); + }, + [onCategoryChange] + ); return ( <div className="flex items-center justify-between border-b"> <div className="flex text-white"> {categories.map((category) => ( <TabButton key={category.id} active={activeCategory === category.id} - onClick={() => onCategoryChange(category.id)} + onClick={() => handleCategoryChange(category.id)} smallText={category.smallText} > {category.label} </TabButton> ))} </div>src/components/ui/tech-stack/TechSelector.tsx (1)
38-40: 컴포넌트 스타일의 재사용성 고려현재 배경색과 최소 높이를 컴포넌트 내부에서 하드코딩하고 있습니다. 이 컴포넌트가 여러 페이지에서 재사용된다면, 이러한 스타일 속성은 props로 받거나 외부에서 제공하는 것이 더 유연한 사용을 가능하게 할 수 있습니다.
- return ( - <div className="bg-gray-50 min-h-screen p-10"> - <div className="mx-auto max-w-6xl"> + return ( + <div className="p-10"> + <div className="mx-auto max-w-6xl">src/hooks/useTechSelection.ts (2)
22-30: 선택된 아이콘 목록 계산 최적화
getSelectedIconNames함수에서 filter와 map을 연속해서 사용하는 부분은 성능 최적화를 위해 reduce로 변경할 수 있습니다. 이는 배열을 한 번만 순회하도록 합니다.- // 선택된 아이콘 이름 목록 가져오기 - const getSelectedIconNames = (): string[] => { - return Object.entries(clickedButtons) - .filter(([, isClicked]) => isClicked) - .map(([name]) => name); - }; - - const selectedNames = getSelectedIconNames(); - const selectedCount = selectedNames.length; + // 선택된 아이콘 이름 목록 가져오기 + const getSelectedIconNames = (): string[] => { + return Object.entries(clickedButtons).reduce<string[]>((acc, [name, isClicked]) => { + if (isClicked) acc.push(name); + return acc; + }, []); + }; + + const selectedNames = getSelectedIconNames(); + const selectedCount = selectedNames.length;
50-54: ESLint 비활성화 주석 개선 가능eslint-disable-next-line 주석을 사용하는 대신, 모든 종속성을 명시적으로 추가하는 것이 좋습니다. 이 경우, 빈 배열 대신
[selectedNames]를 종속성으로 추가할 수 있습니다.- // 컴포넌트 마운트 시 초기 선택 상태 설정 - useEffect(() => { - prevSelectionRef.current = selectedNames; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // 컴포넌트 마운트 시 초기 선택 상태 설정 + useEffect(() => { + if (prevSelectionRef.current.length === 0) { + prevSelectionRef.current = selectedNames; + } + }, [selectedNames]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/app/preview/tech-stack3/page.tsx(1 hunks)src/components/ui/Icon/IconData.ts(3 hunks)src/components/ui/tech-stack/TechSelector.tsx(1 hunks)src/components/ui/tech-stack/tech-stack-components/CategoryTabs.tsx(1 hunks)src/components/ui/tech-stack/tech-stack-components/SelectedTechButton.tsx(1 hunks)src/components/ui/tech-stack/tech-stack-components/SelectedTechList.tsx(1 hunks)src/components/ui/tech-stack/tech-stack-components/TabButton.tsx(1 hunks)src/components/ui/tech-stack/tech-stack-components/TechButton.tsx(1 hunks)src/components/ui/tech-stack/tech-stack-components/TechButtonList.tsx(1 hunks)src/hooks/useTechSelection.ts(1 hunks)src/types/techStack.ts(1 hunks)src/util/getIconDetail.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/components/ui/Icon/IconData.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: check
🔇 Additional comments (16)
src/components/ui/tech-stack/tech-stack-components/TechButton.tsx (1)
1-44: 반응형 디자인 접근 방식이 잘 구현되었습니다.Tailwind의 lg: 접두사를 사용하여 다양한 화면 크기에 맞게 컴포넌트를 조정한 점이 좋습니다. 모바일 및 데스크톱 환경 모두에서 적절한 크기와 간격이 유지됩니다.
src/app/preview/tech-stack3/page.tsx (1)
6-18: 상태 관리 접근 방식이 명확하게 구현되었습니다.상위 컴포넌트에서 상태를 관리하고 하위 컴포넌트에 props로 전달하는 방식이 잘 구현되었습니다. 이는 React의 단방향 데이터 흐름 원칙을 잘 따르고 있습니다.
src/components/ui/tech-stack/tech-stack-components/TabButton.tsx (1)
24-27: 반응형 디자인 접근 방식이 잘 구현되었습니다.
hidden및sm:inline클래스를 사용한 반응형 디자인 접근 방식이 좋습니다. 화면 크기에 따라 다른 텍스트를 표시하는 방법이 간결하게 구현되었습니다.src/types/techStack.ts (2)
16-19: IconWithComponent 인터페이스가 잘 설계되었습니다.기존 IconConfig를 확장하여 component 속성을 추가한 접근 방식이 명확하고 타입 안전성을 잘 유지하고 있습니다.
21-22: CategoryType의 유니온 타입 접근 방식이 효과적입니다.기존 카테고리 타입에 'all' 옵션을 추가한 유니온 타입 접근 방식이 타입 안전성을 유지하면서도 필요한 유연성을 제공합니다.
src/components/ui/tech-stack/tech-stack-components/TechButtonList.tsx (1)
45-60: 기술 버튼 목록 렌더링이 잘 구현되어 있습니다.기술 스택 아이콘을 맵핑하여 버튼 컴포넌트로 렌더링하는 구현이 깔끔하게 잘 되어 있습니다.
isMaxReached로직을 통해 최대 선택 개수를 효과적으로 제한하고 있습니다.src/components/ui/tech-stack/tech-stack-components/SelectedTechButton.tsx (1)
5-18: 컴포넌트 구조와 반응형 디자인이 잘 구현되어 있습니다.기술 스택 버튼 컴포넌트의 구조가 명확하고, 텍스트를 모바일에서는 숨기고 데스크톱에서만 표시하는 반응형 디자인이 잘 적용되어 있습니다. 또한
aria-label을 사용하여 접근성도 고려한 점이 좋습니다.src/components/ui/tech-stack/tech-stack-components/SelectedTechList.tsx (1)
16-18: 선택된 항목이 없을 때의 처리가 잘 되어 있습니다.선택된 항목이 없을 때 최소 높이를 유지하는 빈 div를 반환하여 레이아웃이 급격하게 변하는 것을 방지한 점이 좋습니다.
src/components/ui/tech-stack/tech-stack-components/CategoryTabs.tsx (1)
44-53: 리셋 버튼도 useCallback 적용 및 접근성 개선리셋 버튼의 onClick 핸들러도 마찬가지로 useCallback을 적용하면 좋습니다. 또한 title 속성에 한글을 사용하고 있는데, 이는 매우 좋은 접근성 제공 방식입니다.
src/components/ui/tech-stack/TechSelector.tsx (4)
1-9: 좋은 임포트 구조 및 컴포넌트 분리컴포넌트 구성이 잘 되어 있고, 임포트 구조도 명확합니다. 커스텀 훅과 유틸리티 함수를 활용하여 관심사를 잘 분리하였습니다.
42-46: 컴포넌트 구조와 데이터 흐름이 깔끔합니다선택된 기술 목록을 표시하는
SelectedTechList컴포넌트에 필요한 props를 잘 전달하고 있습니다. 이렇게 컴포넌트를 분리하면 코드의 가독성과 유지보수성이 향상됩니다.
49-53: 간결한 카테고리 탭 구현카테고리 탭 컴포넌트로 필요한 props를 명확하게 전달하고 있습니다. 활성 카테고리 상태 관리와 상태 변경 함수를 잘 분리했습니다.
56-62: 기술 버튼 목록 컴포넌트 구현이 적절합니다
TechButtonList컴포넌트로 필요한 데이터와 콜백 함수를 잘 전달하고 있습니다. 컴포넌트 분리와 관심사 분리가 잘 이루어졌습니다.src/hooks/useTechSelection.ts (3)
18-19: useRef 훅 사용이 적절합니다이전 선택 값을 저장하기 위해 useRef를 사용한 것은 좋은 방법입니다. 불필요한 리렌더링을 방지하면서 값을 기억할 수 있습니다.
31-48: 선택 변경 감지 로직이 효율적입니다선택 변경이 실제로 있을 때만 외부 콜백을 호출하는 로직이 잘 구현되어 있습니다. 선택 배열의 길이와 내용을 비교하여 정확하게 변경을 감지합니다.
57-80: 버튼 클릭 핸들러 로직이 명확합니다
handleButtonClick함수의 로직이 명확하고 가독성이 좋습니다. 이미 선택된 항목은 해제하고, 최대 선택 개수를 초과하지 않도록 검사하는 로직이 잘 구현되어 있습니다.
| const handleSelectionChange = (selection: string[]) => { | ||
| setSelectedTechs(selection); | ||
| console.log('Selected technologies:', selection); | ||
|
|
||
| // 여기서 필요한 상태 업데이트 또는 API 호출 등을 수행할 수 있습니다. | ||
| // 예: 선택된 기술 정보를 서버에 전송 | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
프로덕션 코드에서 console.log 제거가 필요합니다.
프로덕션 환경에서는 console.log 문을 제거하는 것이 좋습니다. 디버깅 코드를 남겨두면 성능에 영향을 미칠 수 있으며 민감한 정보가 노출될 가능성이 있습니다.
const handleSelectionChange = (selection: string[]) => {
setSelectedTechs(selection);
- console.log('Selected technologies:', selection);
// 여기서 필요한 상태 업데이트 또는 API 호출 등을 수행할 수 있습니다.
// 예: 선택된 기술 정보를 서버에 전송
};📝 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 handleSelectionChange = (selection: string[]) => { | |
| setSelectedTechs(selection); | |
| console.log('Selected technologies:', selection); | |
| // 여기서 필요한 상태 업데이트 또는 API 호출 등을 수행할 수 있습니다. | |
| // 예: 선택된 기술 정보를 서버에 전송 | |
| }; | |
| const handleSelectionChange = (selection: string[]) => { | |
| setSelectedTechs(selection); | |
| // 여기서 필요한 상태 업데이트 또는 API 호출 등을 수행할 수 있습니다. | |
| // 예: 선택된 기술 정보를 서버에 전송 | |
| }; |
| return ( | ||
| <button | ||
| className={`px-2 py-2 font-medium sm:px-4 ${ | ||
| active | ||
| ? 'border-b-2 border-[#C586C0] text-[#C586C0]' | ||
| : 'hover:text-Cgray500' | ||
| }`} | ||
| onClick={onClick} | ||
| > | ||
| <span className="hidden sm:inline">{children}</span> | ||
| <span className="sm:hidden">{smallText}</span> | ||
| </button> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
접근성 속성을 추가하는 것이 필요합니다.
탭 버튼에 적절한 aria 속성과 role을 추가하여 접근성을 향상시킬 필요가 있습니다.
<button
className={`px-2 py-2 font-medium sm:px-4 ${
active
? 'border-b-2 border-[#C586C0] text-[#C586C0]'
: 'hover:text-Cgray500'
}`}
onClick={onClick}
+ role="tab"
+ aria-selected={active}
+ tabIndex={active ? 0 : -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.
| return ( | |
| <button | |
| className={`px-2 py-2 font-medium sm:px-4 ${ | |
| active | |
| ? 'border-b-2 border-[#C586C0] text-[#C586C0]' | |
| : 'hover:text-Cgray500' | |
| }`} | |
| onClick={onClick} | |
| > | |
| <span className="hidden sm:inline">{children}</span> | |
| <span className="sm:hidden">{smallText}</span> | |
| </button> | |
| ); | |
| return ( | |
| <button | |
| className={`px-2 py-2 font-medium sm:px-4 ${ | |
| active | |
| ? 'border-b-2 border-[#C586C0] text-[#C586C0]' | |
| : 'hover:text-Cgray500' | |
| }`} | |
| onClick={onClick} | |
| role="tab" | |
| aria-selected={active} | |
| tabIndex={active ? 0 : -1} | |
| > | |
| <span className="hidden sm:inline">{children}</span> | |
| <span className="sm:hidden">{smallText}</span> | |
| </button> | |
| ); |
| return ( | ||
| <div className="min-h-8"> | ||
| <div className="bg-gray-102 mb-2 flex flex-col gap-2 rounded-md"> | ||
| <div className="flex flex-wrap gap-2"> | ||
| {selectedNames.map((name) => ( | ||
| <SelectedTechButton | ||
| key={name} | ||
| name={name} | ||
| color={getIconColor(name)} | ||
| onRemove={onRemove} | ||
| /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
CSS 클래스명 오류와 시맨틱 HTML 사용 개선이 필요합니다.
선택된 기술 목록을 표시하는 컴포넌트에 몇 가지 개선할 점이 있습니다:
bg-gray-102클래스는 Tailwind CSS에서 유효하지 않습니다. 일반적으로 50 또는 100 단위로 증가합니다(예:bg-gray-100,bg-gray-200).- 목록을 표시할 때
ul과li같은 시맨틱 HTML 요소를 사용하면 접근성이 향상됩니다.
return (
<div className="min-h-8">
- <div className="bg-gray-102 mb-2 flex flex-col gap-2 rounded-md">
- <div className="flex flex-wrap gap-2">
+ <div className="mb-2 flex flex-col gap-2 rounded-md bg-gray-100">
+ <ul className="flex flex-wrap gap-2" role="list" aria-label="선택된 기술 목록">
{selectedNames.map((name) => (
+ <li key={name}>
<SelectedTechButton
- key={name}
name={name}
color={getIconColor(name)}
onRemove={onRemove}
/>
+ </li>
))}
- </div>
+ </ul>
</div>
</div>
);📝 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.
| return ( | |
| <div className="min-h-8"> | |
| <div className="bg-gray-102 mb-2 flex flex-col gap-2 rounded-md"> | |
| <div className="flex flex-wrap gap-2"> | |
| {selectedNames.map((name) => ( | |
| <SelectedTechButton | |
| key={name} | |
| name={name} | |
| color={getIconColor(name)} | |
| onRemove={onRemove} | |
| /> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| return ( | |
| <div className="min-h-8"> | |
| <div className="mb-2 flex flex-col gap-2 rounded-md bg-gray-100"> | |
| <ul className="flex flex-wrap gap-2" role="list" aria-label="선택된 기술 목록"> | |
| {selectedNames.map((name) => ( | |
| <li key={name}> | |
| <SelectedTechButton | |
| name={name} | |
| color={getIconColor(name)} | |
| onRemove={onRemove} | |
| /> | |
| </li> | |
| ))} | |
| </ul> | |
| </div> | |
| </div> | |
| ); |
| return { | ||
| clickedButtons, | ||
| selectedCount, | ||
| selectedNames, | ||
| handleButtonClick, | ||
| handleReset, | ||
| handleRemoveSelection, | ||
| setClickedButtons, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
setClickedButtons 직접 노출 검토
hook의 반환 값에 setClickedButtons를 직접 포함시키는 것은 캡슐화를 깨뜨릴 수 있습니다. 사용처에서 실수로 이 함수를 직접 호출하면 hook의 내부 로직을 우회할 수 있습니다. 필요한 경우가 아니라면 제거하는 것이 좋습니다.
- return {
- clickedButtons,
- selectedCount,
- selectedNames,
- handleButtonClick,
- handleReset,
- handleRemoveSelection,
- setClickedButtons,
- };
+ return {
+ clickedButtons,
+ selectedCount,
+ selectedNames,
+ handleButtonClick,
+ handleReset,
+ handleRemoveSelection,
+ };📝 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.
| return { | |
| clickedButtons, | |
| selectedCount, | |
| selectedNames, | |
| handleButtonClick, | |
| handleReset, | |
| handleRemoveSelection, | |
| setClickedButtons, | |
| }; | |
| return { | |
| clickedButtons, | |
| selectedCount, | |
| selectedNames, | |
| handleButtonClick, | |
| handleReset, | |
| handleRemoveSelection, | |
| }; |
lee1nna
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CategoryTabs는 categories 랑 primary 색상 값 props로 받아서 다른 곳에서도 사용할 수 있도록 (ex. 마이페이지 탭) 추후 리팩토링 되어도 좋을 것 같아용
고생하셨습니다 !!! 👍
Lee-Dong-Seok
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다👍👍👍👍👍👍👍👍
📝 주요 작업 내용
모임리스트페이지, 마이페이지, 모임생성페이지 에서 사용되는 공통컴포넌트 작업입니다.
해당 컴포넌트를 생성하기 위한 세부 컴포넌트 와 통합컴포넌트 같이 작업하였습니다.
📺 스크린샷
기본 디자인


5개 전부 선택되었을 때 - 아이콘 버튼 hover시, title로 최대5개 까지 선택 문구 보입니다.
모바일 반응형 - 선택리스트 아키노만 보여지고 카테고리 수정하였습니다.

🔗 참고 사항
현재 마크업 진행하시는 팀원들 빠르게 사용위해서 dev로 병합 pr 올렸습니다.
💬 리뷰 요구사항
타입을 제외한 커밋 순 대로 봐주시면 감사합니다.
📃 관련 이슈
DEVING-49
Summary by CodeRabbit