-
Notifications
You must be signed in to change notification settings - Fork 1
Feat(design-system): Dropdown 구현 #61
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
Changes from all commits
c63c34a
9b68f19
37c8e7f
b5ccfca
dfd731b
e7f34d8
913bf09
54e1850
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
| import { useState } from 'react'; | ||
| import Dropdown from './Dropdown'; | ||
|
|
||
| const meta: Meta<typeof Dropdown> = { | ||
| title: 'Components/Dropdown', | ||
| component: Dropdown, | ||
| tags: ['autodocs'], | ||
| parameters: { | ||
| layout: 'centered', | ||
| docs: { | ||
| description: { | ||
| component: | ||
| 'dropdown 컴포넌트는 `select`와 `option` 컴포넌트를 포함하여 여러 항목 중 하나를 선택할 수 있는 UI 요소입니다. <br/>' + | ||
| '`onAddItem`과 `limit` props를 통해 사용자가 항목을 추가할 수 있는 기능도 제공합니다.', | ||
| }, | ||
| }, | ||
| }, | ||
| argTypes: { | ||
| options: { | ||
| control: 'object', | ||
| description: '드롭다운 항목 문자열 배열입니다.', | ||
| }, | ||
| selectedValue: { | ||
| control: 'text', | ||
| description: '현재 선택된 값(제어 컴포넌트에서 사용).', | ||
| }, | ||
| placeholder: { | ||
| control: 'text', | ||
| description: '선택 전 표시되는 안내 문구.', | ||
| }, | ||
| addItemLabel: { | ||
| control: 'text', | ||
| description: '추가 버튼 라벨 (`onAddItem`과 함께 사용).', | ||
| }, | ||
| onChange: { | ||
| action: 'changed', | ||
| description: '선택이 변경될 때 호출됩니다.', | ||
| }, | ||
| limit: { | ||
| control: 'number', | ||
| description: '옵션 개수 상한. 도달 시 “추가” 버튼 숨김.', | ||
| }, | ||
| onAddItem: { | ||
| action: 'add item clicked', | ||
| description: '“추가” 버튼 클릭 시 호출됩니다.', | ||
| }, | ||
| className: { table: { disable: true } }, | ||
| }, | ||
| args: { | ||
| options: ['사과', '바나나', '체리', '포도'], | ||
| selectedValue: null, | ||
| placeholder: '항목을 선택하세요', | ||
| addItemLabel: '항목 추가', | ||
| limit: 5, | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| type Story = StoryObj<typeof Dropdown>; | ||
|
|
||
| function ControlledRender(args: any) { | ||
| const [value, setValue] = useState<string | null>(args.selectedValue ?? null); | ||
|
|
||
| return ( | ||
| <div className="h-[25rem]"> | ||
| <Dropdown | ||
| {...args} | ||
| selectedValue={value} | ||
| onChange={(next: string | null) => { | ||
| setValue(next); | ||
| args.onChange?.(next); | ||
| }} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export const Default: Story = { | ||
| name: '기본', | ||
| render: ControlledRender, | ||
| }; | ||
|
|
||
| export const WithAddItemAlert: Story = { | ||
| name: '추가 버튼 클릭 시 (test Alert)', | ||
| args: { | ||
| options: ['사과', '바나나'], | ||
| limit: 5, | ||
| addItemLabel: '새 항목 추가', | ||
| onAddItem: () => { | ||
| alert('추가 버튼이 클릭되었습니다.'); | ||
| }, | ||
| }, | ||
| render: ControlledRender, | ||
| }; | ||
|
|
||
| export const LimitReached: Story = { | ||
| name: '추가 제한 도달 (limit)', | ||
| args: { | ||
| options: ['사과', '바나나', '체리'], | ||
| limit: 3, | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: | ||
| '`options.length`가 `limit`에 도달하여 “추가” 버튼이 표시되지 않습니다.', | ||
| }, | ||
| }, | ||
| }, | ||
| render: ControlledRender, | ||
| }; | ||
|
|
||
| export const ManyOptions: Story = { | ||
| name: '옵션이 많은 경우', | ||
| args: { | ||
| options: Array.from({ length: 20 }, (_, i) => `옵션 ${i + 1}`), | ||
| }, | ||
| render: ControlledRender, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { Icon } from '@pinback/design-system/icons'; | ||
| import { useState } from 'react'; | ||
|
|
||
| interface DropdownProps { | ||
| options: string[]; | ||
| selectedValue: string | null; | ||
| onChange: (selected: string | null) => void; | ||
| placeholder: string; | ||
| onAddItem?: () => void; | ||
| addItemLabel?: string; | ||
| limit?: number; | ||
| className?: string; | ||
| } | ||
|
|
||
| const Dropdown = ({ | ||
| options, | ||
| selectedValue, | ||
| onChange, | ||
| placeholder, | ||
| onAddItem, | ||
| addItemLabel, | ||
| limit, | ||
| className = '', | ||
| }: DropdownProps) => { | ||
|
Comment on lines
+15
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 빈 라벨/플레이스홀더 노출 위험 수정: 기본 placeholder·addItemLabel 기본값을 부여하세요. - placeholder,
+ placeholder = '선택하세요',
onAddItem,
- addItemLabel,
+ addItemLabel = '추가',
@@
- <span className={selectedValue ? 'text-black' : 'text-font-gray-3'}>
- {selectedValue || placeholder}
+ <span className={selectedValue !== null ? 'text-black' : 'text-font-gray-3'}>
+ {selectedValue ?? placeholder}
</span>
@@
- {addItemLabel}
+ {addItemLabel}Also applies to: 42-44, 69-81 🤖 Prompt for AI Agents |
||
| const [isOpen, setIsOpen] = useState(false); | ||
|
|
||
| const handleSelect = (option: string) => { | ||
| onChange(option); | ||
| setIsOpen(false); | ||
| }; | ||
|
|
||
| const showAddItemButton = | ||
| onAddItem && (limit === undefined || options.length < limit); | ||
|
|
||
| return ( | ||
| <div className={`relative w-[24.8rem] ${className}`}> | ||
| <button | ||
| type="button" | ||
| onClick={() => setIsOpen(!isOpen)} | ||
| className={`body4-r flex h-[4.4rem] w-full items-center justify-between rounded-[4px] border px-[0.8rem] py-[1.2rem] transition-colors duration-200 ${isOpen ? 'border-main500' : 'border-gray200'}`} | ||
| > | ||
| <span className={selectedValue ? 'text-black' : 'text-font-gray-3'}> | ||
| {selectedValue || placeholder} | ||
| </span> | ||
| <Icon | ||
| name="ic_arrow_down_disable" | ||
| width={16} | ||
| height={16} | ||
| rotate={isOpen ? 180 : undefined} | ||
| hasRotateAnimation={true} | ||
| /> | ||
| </button> | ||
|
|
||
| {isOpen && ( | ||
| <div className="common-shadow ds-scrollbar absolute z-10 mt-[1.5rem] h-[20.4rem] w-full overflow-y-auto rounded-[0.4rem] bg-white p-[0.8rem]"> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 만들어서 스타일 토큰해뒀습니당 |
||
| <ul className="flex flex-col gap-[0.2rem]"> | ||
| {options.map((option) => ( | ||
| <li | ||
| key={option} | ||
| onClick={() => handleSelect(option)} | ||
| className={`body4-r h-[3.6rem] cursor-pointer p-[0.8rem] ${selectedValue === option ? 'text-main600' : 'text-font-gray-3'}`} | ||
| > | ||
| {option} | ||
| </li> | ||
| ))} | ||
|
|
||
| {showAddItemButton && ( | ||
| <button | ||
| type="button" | ||
| onClick={() => { | ||
| onAddItem?.(); | ||
| setIsOpen(false); | ||
| }} | ||
| className="text-main500 body4-r flex w-full cursor-pointer items-center gap-[0.8rem] p-[0.8rem]" | ||
| > | ||
| <Icon name="ic_plus" width={16} height={16} /> | ||
| {addItemLabel} | ||
| </button> | ||
| )} | ||
| </ul> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default 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.
리밋 속성을 통해서, 카테고리 추가 노출여부를 제어할 수 있어서! 확장성 측면에서 좋은 것 같네요!
++ 대신 디자인적으로 궁금한 점이, 카테고리 리스트개수가 적은 경우에도 그 박스의 height가 고정값일까요?
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.
네네 일단 그렇게 구현이 되어있어요. 리스트가 많아지는 경우에 스크롤을 보여주려면 box에
height값을 줄 수 밖에 없어서..!개수에 따라
height를 다르게 분기처리 할 수 있기는 하지만 현재도 괜찮다고 일단 생각이 드는데 어떻게 생각하시나요??애매하다면 QA때 디자인 분들께 질문 드려도 좋을 것 같아요 👍
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.
아하, 그러네요 스크롤 적용하려면 Height 지정을 해두긴 하네요,, 그렇다고 개수에 따라 height 분기하는 건 오히려 너무 불필요한 거 같아서! 저는 좋아요! QA때 디자이너 분들 피드백에 따라 추후에 수정해보는 걸로 해용!