-
Notifications
You must be signed in to change notification settings - Fork 0
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
[Feat] Dropdown 컴포넌트 추가 #121
base: develop
Are you sure you want to change the base?
Conversation
Walkthrough이번 변경 사항은 웹 애플리케이션과 UI 패키지에 드롭다운 컴포넌트를 추가하는 작업입니다. 웹 페이지에서는 Home 함수 내에 드롭다운을 추가하고, 상태를 관리하기 위한 Changes
Sequence Diagram(s)sequenceDiagram
participant U as 사용자
participant DT as DropdownTrigger
participant DI as DropdownItem
participant DR as DropdownRoot
U->>DT: 트리거 클릭
DT->>DR: 드롭다운 열림 (isOpen 토글)
U->>DI: 옵션 선택
DI->>DR: onValueChange 호출 및 상태 업데이트
DR-->>DT: 선택 값 반영, 드롭다운 닫힘
Suggested labels
Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure. 🔧 ESLint
apps/web/src/app/page.tsxOops! Something went wrong! :( ESLint: 9.17.0 Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@repo/eslint-config' imported from /eslint.config.mjs packages/ui/src/components/Dropdown/Dropdown.tsxOops! Something went wrong! :( ESLint: 9.17.0 Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@repo/eslint-config' imported from /eslint.config.mjs packages/ui/src/components/Dropdown/Dropdown.context.tsOops! Something went wrong! :( ESLint: 9.17.0 Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@repo/eslint-config' imported from /eslint.config.mjs
✨ 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 (
|
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: 7
🧹 Nitpick comments (11)
packages/ui/src/components/Icon/assets/IconClock.tsx (2)
3-3
: 사용되지 않는 인터페이스를 제거하세요.
IconClockProps
인터페이스는SVGProps<SVGSVGElement>
를 확장하지만 추가 속성이 없고 실제로 사용되지 않습니다.-interface IconClockProps extends React.SVGProps<SVGSVGElement> {}
18-18
: 아이콘 색상을 props로 커스터마이징할 수 있도록 개선하세요.현재 하드코딩된 색상 값은 재사용성을 제한할 수 있습니다. props를 통해 색상을 전달받도록 수정하는 것이 좋습니다.
- fill="#788391" + fill={props.fill || "#788391"}packages/ui/src/components/Dropdown/Dropdown.context.ts (2)
4-12
:value
타입을 더 구체적으로 정의하면 좋을 것 같습니다.현재
value
가string | string[]
타입으로 정의되어 있는데, 배열이 필요한 상황이 명확하지 않습니다. 단일 선택만 지원한다면string
타입으로 충분할 것 같습니다.- value: string | string[]; + value: string;
18-22
: 에러 메시지를 더 자세하게 작성하면 좋을 것 같습니다.현재 에러 메시지가 영어로 되어 있고, 문제 해결을 위한 구체적인 정보가 부족합니다.
- 'Dropdown compound components must be used inside <Dropdown/>' + 'Dropdown 컴포넌트의 하위 컴포넌트들은 반드시 <Dropdown/> 내부에서 사용되어야 합니다. ' + + '현재 컴포넌트가 Dropdown 컨텍스트 외부에서 사용되고 있습니다.'packages/ui/src/components/Dropdown/DropdownTrigger.tsx (1)
33-33
: 기본값 처리 로직을 상수로 분리하면 좋을 것 같습니다.하드코딩된 문자열을 상수로 분리하면 재사용성과 유지보수성이 향상될 것 같습니다.
+const DEFAULT_PLACEHOLDER = '옵션을 선택하세요.'; + - {children || value || placeholder || '옵션을 선택하세요.'} + {children || value || placeholder || DEFAULT_PLACEHOLDER}packages/ui/src/components/Dropdown/Dropdown.css.ts (1)
18-19
: 단위 사용을 일관되게 하면 좋을 것 같습니다.
boxShadow
에서는rem
단위를 사용하고 있는데,borderRadius
에서는 숫자만 사용하고 있습니다. 일관성을 위해 통일하면 좋을 것 같습니다.- borderRadius: tokens.radius[12], + borderRadius: `${tokens.radius[12]}rem`,packages/ui/src/components/Dropdown/DropdownItem.tsx (1)
31-42
: ARIA 속성 추가가 필요합니다.메뉴 아이템의 접근성을 향상시키기 위해 추가적인 ARIA 속성이 필요합니다.
다음과 같이 ARIA 속성을 추가하는 것을 제안합니다:
<div ref={ref} className={`${className} ${dropdownItem}`} role="menuitem" tabIndex={0} + aria-selected={context?.value === value} onClick={handleClick} {...props} > {children} </div>
packages/ui/src/components/Dropdown/Dropdown.tsx (1)
8-38
: 예제 코드 문서화 개선이 필요합니다.현재 예제 코드는 기본적인 사용법만 보여주고 있습니다. 더 다양한 시나리오와 속성 사용에 대한 예제가 필요합니다.
다음과 같은 추가 예제를 문서에 포함하는 것을 제안합니다:
/** * @example * * // 1. 기본 메뉴 모드 (현재 예제) * * // 2. 사용자 정의 트리거와 함께 사용 * <Dropdown> * <Dropdown.Trigger> * <Button variant="secondary"> * <Icon name="settings" /> * 설정 * </Button> * </Dropdown.Trigger> * <Dropdown.Content> * {/* ... */} * </Dropdown.Content> * </Dropdown> * * // 3. 오른쪽 정렬과 함께 사용 * <Dropdown> * <Dropdown.Trigger /> * <Dropdown.Content align="right"> * {/* ... */} * </Dropdown.Content> * </Dropdown> * * // 4. 비활성화된 아이템 예제 * <Dropdown> * <Dropdown.Trigger /> * <Dropdown.Content> * <Dropdown.Item value="option1">Option 1</Dropdown.Item> * <Dropdown.Item value="option2" disabled>Option 2</Dropdown.Item> * </Dropdown.Content> * </Dropdown> */packages/ui/src/components/Dropdown/DropdownContent.tsx (1)
57-67
: 접근성 속성 추가가 필요합니다.드롭다운 컨텐츠의 접근성을 향상시키기 위한 ARIA 속성이 누락되어 있습니다.
다음과 같이 ARIA 속성을 추가하는 것을 제안합니다:
<div ref={mergeRefs(ref, contentRef)} className={`${className} ${dropdownContent} ${positionAbove ? dropdownContentAbove : ''} ${ align === 'right' ? dropdownContentRight : dropdownContentLeft }`} + role="menu" + aria-orientation="vertical" + aria-hidden={!isOpen} {...props} > {children} </div>apps/web/src/app/page.tsx (2)
32-36
: 불필요한 handleSelect 함수 제거 필요
handleSelect
함수가 정의되어 있지만 사용되지 않고 있습니다.onValueChange
에 직접setSelectedValue
를 전달하고 있으므로 이 함수는 제거해도 됩니다.const [selectedValue, setSelectedValue] = useState('Select an option'); - const handleSelect = (value: string) => { - setSelectedValue(value); - };
610-623
: 영문 placeholder 한글화 검토사용자 인터페이스의 일관성을 위해 영문 placeholder를 한글로 변경하는 것을 고려해보세요.
- placeholder="Select an option" + placeholder="옵션을 선택하세요" - const [selectedValue, setSelectedValue] = useState('Select an option'); + const [selectedValue, setSelectedValue] = useState('옵션을 선택하세요');
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
packages/ui/src/assets/icons/IconClock.svg
is excluded by!**/*.svg
📒 Files selected for processing (12)
apps/web/src/app/page.tsx
(2 hunks)packages/ui/package.json
(1 hunks)packages/ui/src/components/Dropdown/Dropdown.context.ts
(1 hunks)packages/ui/src/components/Dropdown/Dropdown.css.ts
(1 hunks)packages/ui/src/components/Dropdown/Dropdown.tsx
(1 hunks)packages/ui/src/components/Dropdown/DropdownContent.tsx
(1 hunks)packages/ui/src/components/Dropdown/DropdownItem.tsx
(1 hunks)packages/ui/src/components/Dropdown/DropdownRoot.tsx
(1 hunks)packages/ui/src/components/Dropdown/DropdownTrigger.tsx
(1 hunks)packages/ui/src/components/Dropdown/index.tsx
(1 hunks)packages/ui/src/components/Icon/assets.ts
(2 hunks)packages/ui/src/components/Icon/assets/IconClock.tsx
(1 hunks)
🔇 Additional comments (3)
packages/ui/src/components/Icon/assets.ts (1)
15-15
: 변경사항이 적절합니다!새로운 아이콘이 알파벳 순서에 맞게 잘 추가되었습니다.
Also applies to: 52-52
packages/ui/src/components/Dropdown/index.tsx (1)
1-7
: 내보내기가 깔끔하게 구성되어 있습니다!컴포넌트와 타입들이 명확하게 구조화되어 있어 사용하기 편리할 것 같습니다.
packages/ui/package.json (1)
90-93
: 구성이 올바르게 되어있습니다!Dropdown 컴포넌트의 export 구성이 다른 컴포넌트들과 일관된 패턴을 따르고 있으며, types와 import 경로가 올바르게 지정되어 있습니다.
fill-rule="evenodd" | ||
clip-rule="evenodd" | ||
d="M12.8571 12.2951C12.8571 12.3234 12.8434 12.3483 12.8409 12.3757C12.836 12.4338 12.8242 12.491 12.8057 12.5463C12.7747 12.6488 12.724 12.7443 12.6566 12.8274C12.6189 12.8729 12.5794 12.9131 12.5331 12.9509C12.5117 12.968 12.4989 12.9929 12.4757 13.0083L8.42486 15.7083C8.2357 15.8296 8.00645 15.8718 7.78648 15.8258C7.56652 15.7798 7.37342 15.6492 7.24879 15.4622C7.12416 15.2752 7.07795 15.0467 7.12013 14.826C7.16231 14.6052 7.28949 14.4099 7.47429 14.282L11.1429 11.8366V7.83371C11.1429 7.60639 11.2332 7.38837 11.3939 7.22762C11.5547 7.06688 11.7727 6.97657 12 6.97657C12.2273 6.97657 12.4453 7.06688 12.6061 7.22762C12.7668 7.38837 12.8571 7.60639 12.8571 7.83371V12.2951ZM12 3.5C7.02943 3.5 3 7.52943 3 12.5C3 17.4706 7.02943 21.5 12 21.5C16.9706 21.5 21 17.4706 21 12.5C21 7.52943 16.9706 3.5 12 3.5Z" |
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
SVG 속성명을 React 컨벤션에 맞게 수정하세요.
React에서 SVG 속성은 camelCase를 사용해야 합니다.
- fill-rule="evenodd"
- clip-rule="evenodd"
+ fillRule="evenodd"
+ clipRule="evenodd"
📝 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.
fill-rule="evenodd" | |
clip-rule="evenodd" | |
d="M12.8571 12.2951C12.8571 12.3234 12.8434 12.3483 12.8409 12.3757C12.836 12.4338 12.8242 12.491 12.8057 12.5463C12.7747 12.6488 12.724 12.7443 12.6566 12.8274C12.6189 12.8729 12.5794 12.9131 12.5331 12.9509C12.5117 12.968 12.4989 12.9929 12.4757 13.0083L8.42486 15.7083C8.2357 15.8296 8.00645 15.8718 7.78648 15.8258C7.56652 15.7798 7.37342 15.6492 7.24879 15.4622C7.12416 15.2752 7.07795 15.0467 7.12013 14.826C7.16231 14.6052 7.28949 14.4099 7.47429 14.282L11.1429 11.8366V7.83371C11.1429 7.60639 11.2332 7.38837 11.3939 7.22762C11.5547 7.06688 11.7727 6.97657 12 6.97657C12.2273 6.97657 12.4453 7.06688 12.6061 7.22762C12.7668 7.38837 12.8571 7.60639 12.8571 7.83371V12.2951ZM12 3.5C7.02943 3.5 3 7.52943 3 12.5C3 17.4706 7.02943 21.5 12 21.5C16.9706 21.5 21 17.4706 21 12.5C21 7.52943 16.9706 3.5 12 3.5Z" | |
fillRule="evenodd" | |
clipRule="evenodd" | |
d="M12.8571 12.2951C12.8571 12.3234 12.8434 12.3483 12.8409 12.3757C12.836 12.4338 12.8242 12.491 12.8057 12.5463C12.7747 12.6488 12.724 12.7443 12.6566 12.8274C12.6189 12.8729 12.5794 12.9131 12.5331 12.9509C12.5117 12.968 12.4989 12.9929 12.4757 13.0083L8.42486 15.7083C8.2357 15.8296 8.00645 15.8718 7.78648 15.8258C7.56652 15.7798 7.37342 15.6492 7.24879 15.4622C7.12416 15.2752 7.07795 15.0467 7.12013 14.826C7.16231 14.6052 7.28949 14.4099 7.47429 14.282L11.1429 11.8366V7.83371C11.1429 7.60639 11.2332 7.38837 11.3939 7.22762C11.5547 7.06688 11.7727 6.97657 12 6.97657C12.2273 6.97657 12.4453 7.06688 12.6061 7.22762C12.7668 7.38837 12.8571 7.60639 12.8571 7.83371V12.2951ZM12 3.5C7.02943 3.5 3 7.52943 3 12.5C3 17.4706 7.02943 21.5 12 21.5C16.9706 21.5 21 17.4706 21 12.5C21 7.52943 16.9706 3.5 12 3.5Z" |
ref={(node) => { | ||
if (typeof ref === 'function') { | ||
ref(node); | ||
} else if (ref) { | ||
(ref as any).current = node; | ||
} | ||
if (triggerRef) { | ||
(triggerRef as any).current = node; | ||
} | ||
}} |
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
ref 처리 로직에서 타입 안전성을 개선해야 합니다.
현재 코드에서 as any
타입 단언을 사용하고 있는데, 이는 타입 안전성을 해칠 수 있습니다.
다음과 같이 개선하는 것을 추천드립니다:
- if (typeof ref === 'function') {
- ref(node);
- } else if (ref) {
- (ref as any).current = node;
- }
- if (triggerRef) {
- (triggerRef as any).current = node;
- }
+ // ref 병합을 위한 유틸리티 함수 사용
+ if (node) {
+ if (typeof ref === 'function') ref(node);
+ else if (ref) ref.current = node;
+ if (triggerRef) triggerRef.current = node;
+ }
Committable suggestion skipped: line range outside the PR's diff.
const handleClick = ( | ||
event: React.MouseEvent<HTMLDivElement, MouseEvent> | ||
) => { | ||
if (onClick) onClick(event); | ||
if (context && value !== undefined) { | ||
context.onValueChange?.(value); | ||
// 아이템 클릭 시 드롭다운 닫기 | ||
context.setIsOpen(false); | ||
} | ||
}; |
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
키보드 접근성 개선이 필요합니다.
현재 구현에서는 클릭 이벤트만 처리되고 있습니다. 키보드 사용자를 위해 키보드 이벤트 처리가 필요합니다.
다음과 같이 키보드 이벤트를 추가하는 것을 제안합니다:
const handleClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
if (onClick) onClick(event);
if (context && value !== undefined) {
context.onValueChange?.(value);
context.setIsOpen(false);
}
};
+const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ if (onClick) onClick(event as unknown as React.MouseEvent);
+ if (context && value !== undefined) {
+ context.onValueChange?.(value);
+ context.setIsOpen(false);
+ }
+ }
+};
그리고 컴포넌트에 이벤트 핸들러를 추가:
<div
ref={ref}
className={`${className} ${dropdownItem}`}
role="menuitem"
tabIndex={0}
onClick={handleClick}
+ onKeyDown={handleKeyDown}
{...props}
>
📝 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 handleClick = ( | |
event: React.MouseEvent<HTMLDivElement, MouseEvent> | |
) => { | |
if (onClick) onClick(event); | |
if (context && value !== undefined) { | |
context.onValueChange?.(value); | |
// 아이템 클릭 시 드롭다운 닫기 | |
context.setIsOpen(false); | |
} | |
}; | |
const handleClick = ( | |
event: React.MouseEvent<HTMLDivElement, MouseEvent> | |
) => { | |
if (onClick) onClick(event); | |
if (context && value !== undefined) { | |
context.onValueChange?.(value); | |
// 아이템 클릭 시 드롭다운 닫기 | |
context.setIsOpen(false); | |
} | |
}; | |
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => { | |
if (event.key === 'Enter' || event.key === ' ') { | |
event.preventDefault(); | |
if (onClick) onClick(event as unknown as React.MouseEvent<HTMLDivElement, MouseEvent>); | |
if (context && value !== undefined) { | |
context.onValueChange?.(value); | |
context.setIsOpen(false); | |
} | |
} | |
}; | |
return ( | |
<div | |
ref={ref} | |
className={`${className} ${dropdownItem}`} | |
role="menuitem" | |
tabIndex={0} | |
onClick={handleClick} | |
onKeyDown={handleKeyDown} | |
{...props} | |
> | |
{/* ... other component rendering ... */} | |
</div> | |
); |
const contextValue: DropdownContextValue = isSelectMode | ||
? { | ||
value: value!, | ||
onValueChange: onValueChange!, | ||
placeholder, | ||
isOpen, | ||
setIsOpen, | ||
triggerRef, | ||
} | ||
: { value: '', isOpen, setIsOpen, triggerRef }; |
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
타입 안전성 개선이 필요합니다.
contextValue
생성 시 타입 단언(!.)을 사용하고 있습니다. 이는 런타임 에러의 위험이 있습니다.
다음과 같이 타입 안전한 방식으로 리팩토링하는 것을 제안합니다:
-const contextValue: DropdownContextValue = isSelectMode
- ? {
- value: value!,
- onValueChange: onValueChange!,
- placeholder,
- isOpen,
- setIsOpen,
- triggerRef,
- }
- : { value: '', isOpen, setIsOpen, triggerRef };
+const contextValue: DropdownContextValue = {
+ value: isSelectMode ? value ?? '' : '',
+ onValueChange: isSelectMode ? onValueChange : undefined,
+ placeholder: isSelectMode ? placeholder : undefined,
+ isOpen,
+ setIsOpen,
+ triggerRef,
+};
📝 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 contextValue: DropdownContextValue = isSelectMode | |
? { | |
value: value!, | |
onValueChange: onValueChange!, | |
placeholder, | |
isOpen, | |
setIsOpen, | |
triggerRef, | |
} | |
: { value: '', isOpen, setIsOpen, triggerRef }; | |
const contextValue: DropdownContextValue = { | |
value: isSelectMode ? value ?? '' : '', | |
onValueChange: isSelectMode ? onValueChange : undefined, | |
placeholder: isSelectMode ? placeholder : undefined, | |
isOpen, | |
setIsOpen, | |
triggerRef, | |
}; |
useLayoutEffect(() => { | ||
function updatePosition() { | ||
if (isOpen && triggerRef.current && contentRef.current) { | ||
const triggerRect = triggerRef.current.getBoundingClientRect(); | ||
const contentRect = contentRef.current.getBoundingClientRect(); | ||
|
||
if (triggerRect.bottom + contentRect.height > window.innerHeight) { | ||
setPositionAbove(true); | ||
} else { | ||
setPositionAbove(false); | ||
} | ||
} | ||
} | ||
|
||
updatePosition(); | ||
|
||
window.addEventListener('scroll', updatePosition, true); | ||
window.addEventListener('resize', updatePosition); | ||
|
||
return () => { | ||
window.removeEventListener('scroll', updatePosition, true); | ||
window.removeEventListener('resize', updatePosition); | ||
}; | ||
}, [isOpen, triggerRef]); |
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
성능 최적화가 필요합니다.
현재 구현에서는 스크롤과 리사이즈 이벤트가 발생할 때마다 위치를 계산합니다. 이는 성능에 영향을 줄 수 있습니다.
다음과 같이 디바운스를 적용하는 것을 제안합니다:
+import { debounce } from '@repo/ui/utils';
+
useLayoutEffect(() => {
- function updatePosition() {
+ const updatePosition = debounce(() => {
if (isOpen && triggerRef.current && contentRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const contentRect = contentRef.current.getBoundingClientRect();
if (triggerRect.bottom + contentRect.height > window.innerHeight) {
setPositionAbove(true);
} else {
setPositionAbove(false);
}
}
- }
+ }, 100);
updatePosition();
window.addEventListener('scroll', updatePosition, true);
window.addEventListener('resize', updatePosition);
return () => {
window.removeEventListener('scroll', updatePosition, true);
window.removeEventListener('resize', updatePosition);
+ updatePosition.cancel();
};
}, [isOpen, triggerRef]);
📝 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.
useLayoutEffect(() => { | |
function updatePosition() { | |
if (isOpen && triggerRef.current && contentRef.current) { | |
const triggerRect = triggerRef.current.getBoundingClientRect(); | |
const contentRect = contentRef.current.getBoundingClientRect(); | |
if (triggerRect.bottom + contentRect.height > window.innerHeight) { | |
setPositionAbove(true); | |
} else { | |
setPositionAbove(false); | |
} | |
} | |
} | |
updatePosition(); | |
window.addEventListener('scroll', updatePosition, true); | |
window.addEventListener('resize', updatePosition); | |
return () => { | |
window.removeEventListener('scroll', updatePosition, true); | |
window.removeEventListener('resize', updatePosition); | |
}; | |
}, [isOpen, triggerRef]); | |
+import { debounce } from '@repo/ui/utils'; | |
useLayoutEffect(() => { | |
- function updatePosition() { | |
- if (isOpen && triggerRef.current && contentRef.current) { | |
- const triggerRect = triggerRef.current.getBoundingClientRect(); | |
- const contentRect = contentRef.current.getBoundingClientRect(); | |
- | |
- if (triggerRect.bottom + contentRect.height > window.innerHeight) { | |
- setPositionAbove(true); | |
- } else { | |
- setPositionAbove(false); | |
- } | |
- } | |
- } | |
+ const updatePosition = debounce(() => { | |
+ if (isOpen && triggerRef.current && contentRef.current) { | |
+ const triggerRect = triggerRef.current.getBoundingClientRect(); | |
+ const contentRect = contentRef.current.getBoundingClientRect(); | |
+ | |
+ if (triggerRect.bottom + contentRect.height > window.innerHeight) { | |
+ setPositionAbove(true); | |
+ } else { | |
+ setPositionAbove(false); | |
+ } | |
+ } | |
+ }, 100); | |
updatePosition(); | |
window.addEventListener('scroll', updatePosition, true); | |
window.addEventListener('resize', updatePosition); | |
return () => { | |
window.removeEventListener('scroll', updatePosition, true); | |
window.removeEventListener('resize', updatePosition); | |
+ updatePosition.cancel(); | |
}; | |
}, [isOpen, triggerRef]); |
<Dropdown> | ||
<Dropdown.Trigger> | ||
<IconButton icon="dots" /> | ||
</Dropdown.Trigger> | ||
<Dropdown.Content align="right"> | ||
<Dropdown.Item | ||
value="option1" | ||
style={{ | ||
display: 'flex', | ||
alignItems: 'center', | ||
gap: '1rem', | ||
width: 'fit-content', | ||
}} | ||
> | ||
<Icon name="clock" size="2.4rem" color="grey400" /> | ||
<Text fontSize={18} fontWeight="medium" color="grey1000"> | ||
수정하기 | ||
</Text> | ||
</Dropdown.Item> | ||
<Dropdown.Item | ||
value="option1" | ||
style={{ | ||
display: 'flex', | ||
alignItems: 'center', | ||
gap: '1rem', | ||
width: 'fit-content', | ||
}} | ||
> | ||
<Icon name="trash" size="2.4rem" color="grey400" /> | ||
<Text fontSize={18} fontWeight="medium" color="grey1000"> | ||
삭제하기 | ||
</Text> | ||
</Dropdown.Item> | ||
</Dropdown.Content> | ||
</Dropdown> |
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.
중복된 value 속성 수정 필요
두 Dropdown.Item
컴포넌트에서 동일한 value="option1"을 사용하고 있습니다. 각 아이템은 고유한 값을 가져야 합니다.
- value="option1"
+ value="edit"
- value="option1"
+ value="delete"
📝 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.
<Dropdown> | |
<Dropdown.Trigger> | |
<IconButton icon="dots" /> | |
</Dropdown.Trigger> | |
<Dropdown.Content align="right"> | |
<Dropdown.Item | |
value="option1" | |
style={{ | |
display: 'flex', | |
alignItems: 'center', | |
gap: '1rem', | |
width: 'fit-content', | |
}} | |
> | |
<Icon name="clock" size="2.4rem" color="grey400" /> | |
<Text fontSize={18} fontWeight="medium" color="grey1000"> | |
수정하기 | |
</Text> | |
</Dropdown.Item> | |
<Dropdown.Item | |
value="option1" | |
style={{ | |
display: 'flex', | |
alignItems: 'center', | |
gap: '1rem', | |
width: 'fit-content', | |
}} | |
> | |
<Icon name="trash" size="2.4rem" color="grey400" /> | |
<Text fontSize={18} fontWeight="medium" color="grey1000"> | |
삭제하기 | |
</Text> | |
</Dropdown.Item> | |
</Dropdown.Content> | |
</Dropdown> | |
<Dropdown> | |
<Dropdown.Trigger> | |
<IconButton icon="dots" /> | |
</Dropdown.Trigger> | |
<Dropdown.Content align="right"> | |
<Dropdown.Item | |
value="edit" | |
style={{ | |
display: 'flex', | |
alignItems: 'center', | |
gap: '1rem', | |
width: 'fit-content', | |
}} | |
> | |
<Icon name="clock" size="2.4rem" color="grey400" /> | |
<Text fontSize={18} fontWeight="medium" color="grey1000"> | |
수정하기 | |
</Text> | |
</Dropdown.Item> | |
<Dropdown.Item | |
value="delete" | |
style={{ | |
display: 'flex', | |
alignItems: 'center', | |
gap: '1rem', | |
width: 'fit-content', | |
}} | |
> | |
<Icon name="trash" size="2.4rem" color="grey400" /> | |
<Text fontSize={18} fontWeight="medium" color="grey1000"> | |
삭제하기 | |
</Text> | |
</Dropdown.Item> | |
</Dropdown.Content> | |
</Dropdown> |
관련 이슈
#39
변경 사항
Dropdown 컴포넌트를 구현했습니다!
![image](https://private-user-images.githubusercontent.com/103591752/412101258-04168c20-a389-4f5a-9288-68aded1b834c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzMzMDYsIm5iZiI6MTczOTM3MzAwNiwicGF0aCI6Ii8xMDM1OTE3NTIvNDEyMTAxMjU4LTA0MTY4YzIwLWEzODktNGY1YS05Mjg4LTY4YWRlZDFiODM0Yy5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjEyJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMlQxNTEwMDZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1lMTM1NzcwYWFhYzA0YWE0MjI0Njc2MzJlZmE4MDlmODJjMzJkMDcwNjllZmNmNmJmMjI1MGE4MDQ5NzQ1NWJhJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.-OxqLbR_YI27KrUkx4ncSptT9xDqepZjk0hQxPdkM8w)
현재 디자인 시안에서는 select와 dropdown menu 모두 드롭다운 컴포넌트로 표기하고 있어 두 방식을 모두 지원하도록 구현하려 했고, 아래와 같은 방식으로 사용할 수 있어요!
Summary by CodeRabbit