[Feat] #54 - Dropdowns 컴포넌트 구현#60
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds two dropdown components (DropdownMenu, SelectDropdown) with outside-click and Escape handling, Storybook stories, barrel exports, .gitignore additions for local Claude files, and removal of an unused React import in an inputBox story. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant Dropdown as DropdownComponent
participant Parent as ParentComponent
participant Doc as Document
User->>Dropdown: click trigger
Dropdown->>Dropdown: toggle isOpen (render menu)
User->>Dropdown: click option
Dropdown->>Parent: call onSelect/onChange(option)
Dropdown->>Dropdown: close menu
User->>Doc: click outside
Doc->>Dropdown: outside-click handler → close menu
User->>Dropdown: press Escape
Dropdown->>Dropdown: Escape handler → close menu
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
src/shared/ui/index.ts (1)
3-3: Consider re-exporting types for API consistency.The dropdown barrel (
./dropdown/index.ts) exports types likeDropdownMenuProps,DropdownMenuItem,SelectDropdownProps, andSelectOption. For a consistent public API, consider re-exporting these types here as well, allowing consumers to import everything from@/shared/ui.📦 Proposed type re-exports
export { Button } from "./button"; export { Input } from "./input"; export { DropdownMenu, SelectDropdown } from "./dropdown"; +export type { + DropdownMenuProps, + DropdownMenuItem, + SelectDropdownProps, + SelectOption, +} from "./dropdown";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/index.ts` at line 3, Add the missing type re-exports to the UI barrel so consumers can import types from the same entrypoint as components; update the export line that currently exports DropdownMenu and SelectDropdown to also re-export DropdownMenuProps, DropdownMenuItem, SelectDropdownProps, and SelectOption (the exported symbols from ./dropdown) so the barrel exposes both components and associated types for a consistent public API.src/shared/ui/dropdown/dropdown.stories.tsx (1)
81-81: Consider consistent story typing.The DropdownMenu stories use
Story(which isStoryObj<typeof meta>), while the SelectDropdown stories use bareStoryObj. For consistency, consider defining a separateSelectStorytype or using the same pattern throughout.📝 Suggested approach
Since SelectDropdown stories have different props than the meta component (DropdownMenu), using
StoryObjwithout type parameters is acceptable. However, you could add a comment explaining the intentional difference, or define the stories more explicitly:// SelectDropdown stories use different component, so we use untyped StoryObj export const SelectDefault: StoryObj = { render: () => <SelectDefaultComponent /> };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/dropdown/dropdown.stories.tsx` at line 81, The SelectDropdown stories use an untyped StoryObj (export const SelectDefault: StoryObj = { render: () => <SelectDefaultComponent /> };) which is inconsistent with the other stories that use a typed Story (StoryObj<typeof meta> from DropdownMenu); make the typing explicit by either defining a SelectStory type (e.g., type SelectStory = StoryObj<typeof SelectDefaultComponent> or similar) and using it for SelectDefault, or add a brief comment explaining the intentional use of an untyped StoryObj; update references to SelectDefault and SelectDefaultComponent and the StoryObj import to reflect the chosen approach for consistency.src/shared/ui/dropdown/dropdown-menu.tsx (2)
53-65: Consider adding ARIA attributes for better accessibility.The dropdown trigger button is missing accessibility attributes that help screen readers understand the menu's state and relationship.
♿ Proposed accessibility improvements
<button type="button" onClick={() => setIsOpen((prev) => !prev)} + aria-haspopup="menu" + aria-expanded={isOpen} className="inline-flex items-center gap-2 px-4 h-10 rounded-lg border border-(--color-gray-300,`#CCCCCC`) bg-white text-[14px] text-(--color-gray-900,`#1A1A1A`) transition-colors hover:border-(--color-primary-500,`#3385DB`) focus:outline-none focus:shadow-[0_0_0_3px_var(--color-primary-100,`#E0EDFB`)]" >Also consider adding
role="menu"to the dropdown panel (line 68) androle="menuitem"to each item button.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/dropdown/dropdown-menu.tsx` around lines 53 - 65, The dropdown trigger in DropdownMenu is missing ARIA attributes—update the trigger button (where setIsOpen is used and ChevronDownIcon/trigger rendered) to include aria-haspopup="true", aria-expanded set to the isOpen state, and aria-controls that points to the dropdown panel's id; give the panel a stable id and set role="menu" on the panel and role="menuitem" on each item button (or the element rendered for each menu entry) so screen readers can understand the relationship and state; ensure the panel id referenced by aria-controls is unique/stable and that focus management still works with setIsOpen.
67-99: Addroleattributes to the menu container and items.For comprehensive screen reader support, the menu panel should have
role="menu"and items should haverole="menuitem".♿ Proposed ARIA role additions
{isOpen && ( - <div className="absolute z-50 mt-1 min-w-40 rounded-lg border border-(--color-gray-200,`#E5E5E5`) bg-white shadow-[0_4px_16px_rgba(0,0,0,0.10)] py-1"> + <div role="menu" className="absolute z-50 mt-1 min-w-40 rounded-lg border border-(--color-gray-200,`#E5E5E5`) bg-white shadow-[0_4px_16px_rgba(0,0,0,0.10)] py-1"> {items.map((item) => ( <React.Fragment key={item.value}> <button type="button" + role="menuitem" disabled={item.disabled}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/dropdown/dropdown-menu.tsx` around lines 67 - 99, The dropdown panel currently rendered when isOpen should include ARIA roles for screen readers: add role="menu" to the outer menu container div (the one rendering {isOpen && (<div ...>)}), and add role="menuitem" to each item button rendered inside items.map (the button that calls handleSelect); also set aria-disabled={item.disabled} on that button so assistive tech sees disabled state. Ensure these attributes are applied to the same elements identified by isOpen, the container div, the button inside items.map, and the item.disabled check.src/shared/ui/dropdown/select-dropdown.tsx (2)
34-50: Consider extracting shared dropdown behavior to a custom hook.The click-outside and Escape key handling logic is duplicated between
DropdownMenuandSelectDropdown. This could be extracted into a reusable hook.♻️ Example custom hook
// useDropdownClose.ts import { useEffect, RefObject } from "react"; export function useDropdownClose( containerRef: RefObject<HTMLElement>, isOpen: boolean, onClose: () => void ) { useEffect(() => { if (!isOpen) return; const handleClickOutside = (e: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { onClose(); } }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("mousedown", handleClickOutside); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", handleKeyDown); }; }, [isOpen, onClose, containerRef]); }This also adds the optimization of only registering listeners when the dropdown is open.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/dropdown/select-dropdown.tsx` around lines 34 - 50, Extract the duplicated click-outside and Escape-key logic in SelectDropdown and DropdownMenu into a reusable hook (e.g., useDropdownClose) that accepts containerRef, isOpen, and onClose; replace the useEffect in SelectDropdown (the effect that registers handleClickOutside and handleKeyDown and calls setIsOpen(false)) with a call to this hook, and update DropdownMenu to use the same hook so both components share behavior; ensure the hook only registers listeners when isOpen is true and calls onClose (rather than directly mutating state) and include containerRef, isOpen, and onClose in the hook's dependency list.
73-90: Add ARIA attributes for accessibility.Similar to DropdownMenu, the SelectDropdown trigger button should include ARIA attributes for screen reader support.
♿ Proposed accessibility improvements
<button type="button" disabled={disabled} onClick={() => setIsOpen((prev) => !prev)} + aria-haspopup="listbox" + aria-expanded={isOpen} className={triggerClasses} >Also consider adding
role="listbox"to the options panel (line 93) androle="option"witharia-selectedto each option button.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/dropdown/select-dropdown.tsx` around lines 73 - 90, The trigger button for SelectDropdown lacks ARIA attributes; update the button (the onClick that uses setIsOpen and reads isOpen/selectedOption) to include aria-haspopup="listbox", aria-expanded set from isOpen, and aria-controls pointing to the options panel id (generate a stable id), and ensure the visible label uses aria-label or aria-labelledby as appropriate (ties to selectedOption or placeholder). Also add role="listbox" to the options panel element and set each option button to role="option" with aria-selected reflecting whether the option equals selectedOption so screen readers can announce state correctly (adjust any affected components such as the option rendering code and ChevronDownIcon usage accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/shared/ui/dropdown/select-dropdown.tsx`:
- Around line 106-108: Update the inconsistent fallback hex for the primary
color in the SelectDropdown component: replace the fallback "#1A5FBE" with the
actual CSS variable value "#0056b3" wherever it's used (the ternary that sets
class when isSelected and the CheckIcon class usage) so the fallback for
--color-primary-700 matches the definition in src/index.css; locate this in the
SelectDropdown component (select-dropdown.tsx) where isSelected is used and in
the CheckIcon rendering and adjust the fallback hex accordingly.
---
Nitpick comments:
In `@src/shared/ui/dropdown/dropdown-menu.tsx`:
- Around line 53-65: The dropdown trigger in DropdownMenu is missing ARIA
attributes—update the trigger button (where setIsOpen is used and
ChevronDownIcon/trigger rendered) to include aria-haspopup="true", aria-expanded
set to the isOpen state, and aria-controls that points to the dropdown panel's
id; give the panel a stable id and set role="menu" on the panel and
role="menuitem" on each item button (or the element rendered for each menu
entry) so screen readers can understand the relationship and state; ensure the
panel id referenced by aria-controls is unique/stable and that focus management
still works with setIsOpen.
- Around line 67-99: The dropdown panel currently rendered when isOpen should
include ARIA roles for screen readers: add role="menu" to the outer menu
container div (the one rendering {isOpen && (<div ...>)}), and add
role="menuitem" to each item button rendered inside items.map (the button that
calls handleSelect); also set aria-disabled={item.disabled} on that button so
assistive tech sees disabled state. Ensure these attributes are applied to the
same elements identified by isOpen, the container div, the button inside
items.map, and the item.disabled check.
In `@src/shared/ui/dropdown/dropdown.stories.tsx`:
- Line 81: The SelectDropdown stories use an untyped StoryObj (export const
SelectDefault: StoryObj = { render: () => <SelectDefaultComponent /> };) which
is inconsistent with the other stories that use a typed Story (StoryObj<typeof
meta> from DropdownMenu); make the typing explicit by either defining a
SelectStory type (e.g., type SelectStory = StoryObj<typeof
SelectDefaultComponent> or similar) and using it for SelectDefault, or add a
brief comment explaining the intentional use of an untyped StoryObj; update
references to SelectDefault and SelectDefaultComponent and the StoryObj import
to reflect the chosen approach for consistency.
In `@src/shared/ui/dropdown/select-dropdown.tsx`:
- Around line 34-50: Extract the duplicated click-outside and Escape-key logic
in SelectDropdown and DropdownMenu into a reusable hook (e.g., useDropdownClose)
that accepts containerRef, isOpen, and onClose; replace the useEffect in
SelectDropdown (the effect that registers handleClickOutside and handleKeyDown
and calls setIsOpen(false)) with a call to this hook, and update DropdownMenu to
use the same hook so both components share behavior; ensure the hook only
registers listeners when isOpen is true and calls onClose (rather than directly
mutating state) and include containerRef, isOpen, and onClose in the hook's
dependency list.
- Around line 73-90: The trigger button for SelectDropdown lacks ARIA
attributes; update the button (the onClick that uses setIsOpen and reads
isOpen/selectedOption) to include aria-haspopup="listbox", aria-expanded set
from isOpen, and aria-controls pointing to the options panel id (generate a
stable id), and ensure the visible label uses aria-label or aria-labelledby as
appropriate (ties to selectedOption or placeholder). Also add role="listbox" to
the options panel element and set each option button to role="option" with
aria-selected reflecting whether the option equals selectedOption so screen
readers can announce state correctly (adjust any affected components such as the
option rendering code and ChevronDownIcon usage accordingly).
In `@src/shared/ui/index.ts`:
- Line 3: Add the missing type re-exports to the UI barrel so consumers can
import types from the same entrypoint as components; update the export line that
currently exports DropdownMenu and SelectDropdown to also re-export
DropdownMenuProps, DropdownMenuItem, SelectDropdownProps, and SelectOption (the
exported symbols from ./dropdown) so the barrel exposes both components and
associated types for a consistent public API.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0baff93a-eec6-45f0-a118-2121e4938721
📒 Files selected for processing (7)
.gitignoresrc/shared/ui/dropdown/dropdown-menu.tsxsrc/shared/ui/dropdown/dropdown.stories.tsxsrc/shared/ui/dropdown/index.tssrc/shared/ui/dropdown/select-dropdown.tsxsrc/shared/ui/index.tssrc/shared/ui/inputBox/inputBox.stories.tsx
💤 Files with no reviewable changes (1)
- src/shared/ui/inputBox/inputBox.stories.tsx
| {trigger} | ||
| <ChevronDownIcon | ||
| size={16} | ||
| className={`transition-transform duration-200 text-(--color-gray-500,#808080) ${isOpen ? "rotate-180" : ""}`} |
There was a problem hiding this comment.
색상/보더 토큰 표기 방식이 기존 컴포넌트들과 다르게 들어가 있습니다!
border-(--color-gray-300,#CCCCCC), text-(--color-gray-900,#1A1A1A) 형태는 현재 프로젝트에서 사용 중인 border-[color:var(...)], text-[color:var(...)] 패턴과도 다르고 Tailwind 문법상 스타일이 적용되지 않을 수 있어 보여 확인 부탁드립니다~!
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/shared/ui/dropdown/dropdown-menu.tsx (1)
29-45: Bind document listeners only when the menu is open.On Line 39–44, global listeners are attached for the component lifetime. Gate them by
isOpento reduce unnecessary document-level handlers and avoid handling Escape while closed.⚙️ Suggested patch
- useEffect(() => { + useEffect(() => { + if (!isOpen) return; + const handleClickOutside = (e: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setIsOpen(false); } }; @@ return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", handleKeyDown); }; - }, []); + }, [isOpen]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/dropdown/dropdown-menu.tsx` around lines 29 - 45, The document-level handlers (handleClickOutside, handleKeyDown) are currently registered for the component lifetime; update the useEffect surrounding containerRef to only add those listeners when isOpen is true and remove them when isOpen becomes false. Specifically, modify the effect that defines handleClickOutside and handleKeyDown (the effect using containerRef, setIsOpen) to depend on isOpen, only call document.addEventListener when isOpen is true, and always remove the listeners in the cleanup; include isOpen in the dependency array so Escape is not handled while the menu is closed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/shared/ui/dropdown/dropdown-menu.tsx`:
- Around line 55-68: The trigger button and menu container need WAI-ARIA menu
semantics: generate a stable id (e.g., menuId via useId or useRef) and add
aria-haspopup="menu", aria-expanded={isOpen}, and aria-controls={menuId} to the
button (the element that toggles setIsOpen); mark the ChevronDownIcon as
decorative/aria-hidden; add role="menu" and id={menuId} to the div that renders
when isOpen; ensure each menu item element uses role="menuitem" and disabled
items include aria-disabled="true" (update the components that render items
accordingly).
---
Nitpick comments:
In `@src/shared/ui/dropdown/dropdown-menu.tsx`:
- Around line 29-45: The document-level handlers (handleClickOutside,
handleKeyDown) are currently registered for the component lifetime; update the
useEffect surrounding containerRef to only add those listeners when isOpen is
true and remove them when isOpen becomes false. Specifically, modify the effect
that defines handleClickOutside and handleKeyDown (the effect using
containerRef, setIsOpen) to depend on isOpen, only call
document.addEventListener when isOpen is true, and always remove the listeners
in the cleanup; include isOpen in the dependency array so Escape is not handled
while the menu is closed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1ac275dd-7b7f-4475-b83a-8628ceb8ced2
📒 Files selected for processing (1)
src/shared/ui/dropdown/dropdown-menu.tsx
| <button | ||
| type="button" | ||
| onClick={() => setIsOpen((prev) => !prev)} | ||
| className="inline-flex items-center gap-2 px-4 h-10 rounded-lg border border-[color:var(--color-gray-300,#CCCCCC)] bg-white text-[14px] text-[color:var(--color-gray-900,#1A1A1A)] transition-colors hover:border-[color:var(--color-primary-500,#3385DB)] focus:outline-none focus:shadow-[0_0_0_3px_var(--color-primary-100,#E0EDFB)]" | ||
| > | ||
| {trigger} | ||
| <ChevronDownIcon | ||
| size={16} | ||
| className={`transition-transform duration-200 text-[color:var(--color-gray-500,#808080)] ${isOpen ? "rotate-180" : ""}`} | ||
| /> | ||
| </button> | ||
|
|
||
| {isOpen && ( | ||
| <div className="absolute z-50 mt-1 min-w-40 rounded-lg border border-[color:var(--color-gray-200,#E5E5E5)] bg-white shadow-[0_4px_16px_rgba(0,0,0,0.10)] py-1"> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd src/shared/ui/dropdown && cat -n dropdown-menu.tsxRepository: ajou-industry-matching/aim-frontend
Length of output: 4565
🌐 Web query:
WAI-ARIA menu button pattern APG screen reader accessibility
💡 Result:
The WAI-ARIA Authoring Practices Guide (APG) defines the Menu Button pattern as a button (role="button") that opens a menu (role="menu") containing menu items (role="menuitem"). Key ARIA attributes include aria-haspopup="menu" or "true" on the button to indicate it opens a menu, aria-expanded="true/false" to show if the menu is open, and optionally aria-controls referencing the menu ID. The menu uses aria-labelledby pointing to the button for its accessible name. Examples include action menus using element.focus or aria-activedescendant for focus management, and navigation menus with links as menuitems. Keyboard interactions: Space/Enter/Down Arrow (optionally Up Arrow) on button opens menu and focuses first/last item; Esc closes menu; arrow keys navigate items; Enter/Space activates item. Screen reader accessibility: Screen readers recognize aria-haspopup buttons as "menu buttons" via platform APIs (e.g., NVDA/Chrome announces "actions, menu button, collapsed"; JAWS/Firefox "actions, button, menu"). APG examples have tested support with JAWS, NVDA, VoiceOver, showing name, role, expanded state, and menu navigation are conveyed. Partial support noted in some aria-activedescendant tests (95/116 for screen readers), but core pattern is robust. Test before production use, especially mobile. Live examples: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/examples/menu-button-actions/, https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/examples/menu-button-links/. Support data: https://a11ysupport.io/tests/apgmenu-button-actions-active-descendant.
Citations:
- 1: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
- 2: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/examples/menu-button-actions/
- 3: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/examples/menu-button-links/
- 4: https://a11ysupport.io/tests/apg__menu-button-actions-active-descendant
- 5: https://a11ysupport.io/tests/apg__menu-button-actions-active-descendant/html__button_element/convey_name/nvda/chrome
- 6: https://a11ysupport.io/tests/apg__menu-button-actions-active-descendant/html__button_element/convey_name/jaws/firefox
Add ARIA menu semantics for screen-reader accessibility.
The trigger button and menu list lack core WAI-ARIA menu button pattern attributes (aria-haspopup, aria-expanded, aria-controls, role="menu", role="menuitem", aria-disabled). This prevents screen readers (NVDA, JAWS, VoiceOver) from identifying the menu structure and state.
♿ Suggested patch
-import React, { useState, useRef, useEffect } from "react";
+import React, { useState, useRef, useEffect, useId } from "react";
@@
export const DropdownMenu: React.FC<DropdownMenuProps> = ({
@@
}) => {
+ const menuId = useId();
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
@@
<button
type="button"
onClick={() => setIsOpen((prev) => !prev)}
+ aria-haspopup="menu"
+ aria-expanded={isOpen}
+ aria-controls={menuId}
className="inline-flex items-center gap-2 px-4 h-10 rounded-lg border border-[color:var(--color-gray-300,`#CCCCCC`)] bg-white text-[14px] text-[color:var(--color-gray-900,`#1A1A1A`)] transition-colors hover:border-[color:var(--color-primary-500,`#3385DB`)] focus:outline-none focus:shadow-[0_0_0_3px_var(--color-primary-100,`#E0EDFB`)]"
>
@@
{isOpen && (
- <div className="absolute z-50 mt-1 min-w-40 rounded-lg border border-[color:var(--color-gray-200,`#E5E5E5`)] bg-white shadow-[0_4px_16px_rgba(0,0,0,0.10)] py-1">
+ <div
+ id={menuId}
+ role="menu"
+ className="absolute z-50 mt-1 min-w-40 rounded-lg border border-[color:var(--color-gray-200,`#E5E5E5`)] bg-white shadow-[0_4px_16px_rgba(0,0,0,0.10)] py-1"
+ >
{items.map((item) => (
<React.Fragment key={item.value}>
<button
type="button"
+ role="menuitem"
+ aria-disabled={item.disabled}
disabled={item.disabled}
onClick={() => handleSelect(item)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/dropdown/dropdown-menu.tsx` around lines 55 - 68, The trigger
button and menu container need WAI-ARIA menu semantics: generate a stable id
(e.g., menuId via useId or useRef) and add aria-haspopup="menu",
aria-expanded={isOpen}, and aria-controls={menuId} to the button (the element
that toggles setIsOpen); mark the ChevronDownIcon as decorative/aria-hidden; add
role="menu" and id={menuId} to the div that renders when isOpen; ensure each
menu item element uses role="menuitem" and disabled items include
aria-disabled="true" (update the components that render items accordingly).
🔎 What is this PR?
DropdownMenu와SelectDropdown두 가지 공통 드롭다운 컴포넌트 구현📝 Changes
DropdownMenu컴포넌트 구현 (좌측 아이콘, 단축키, Divider, 비활성화 옵션 지원)SelectDropdown컴포넌트 구현 (선택값 표시, placeholder, 전체 너비, 비활성화 지원)dropdown.stories.tsx— 7개 Storybook 스토리 작성 (Default, WithIconAndShortcut, WithDivider, SelectDefault, SelectWithValue, SelectDisabled, SelectWithDisabledOption)shared/ui/index.ts—DropdownMenu,SelectDropdown공개 API 추가✔ Checklist
pnpm build)pnpm lint)Summary by CodeRabbit
New Features
Documentation
Chores