-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from SystemConsultantGroup/feat/7-dropdown
[FEAT] 드롭다운 컴포넌트
- Loading branch information
Showing
6 changed files
with
209 additions
and
5 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
.dropdownToggle { | ||
padding: 10px 12px; | ||
width: 300px; | ||
background-color: var(--color-surfaceContainerLowest); | ||
border: 1px solid var(--color-onSurface); | ||
border-radius: 8px; | ||
font-weight: 500; | ||
font-size: 14px; | ||
line-height: 20px; | ||
letter-spacing: 0.4px; | ||
color: var(--color-onSurface); | ||
} | ||
|
||
.dropdownToggle:hover { | ||
background-color: var(--color-surfaceBright); | ||
color: var(--color-onSurfaceVariant); | ||
} | ||
|
||
.dropdownToggle.opened { | ||
border-radius: 8px 8px 0 0; | ||
border-bottom: none; | ||
} | ||
|
||
.toggleIcon { | ||
width: 24px; | ||
height: 24px; | ||
left: 90%; | ||
} | ||
|
||
.dropdownList { | ||
border-radius: 0 0 8px 8px; | ||
padding: 0; | ||
margin: 0; | ||
border: 1px solid var(--color-onSurface); | ||
} | ||
|
||
.dropdownItem { | ||
padding: 8px 12px; | ||
cursor: pointer; | ||
background-color: var(--color-surfaceContainerLowest); | ||
font-weight: 500; | ||
font-size: 14px; | ||
line-height: 20px; | ||
letter-spacing: 0.4px; | ||
width: 298px; | ||
} | ||
|
||
.dropdownItem:hover { | ||
background-color: var(--color-surfaceBright); | ||
color: var(--color-onSurfaceVariant); | ||
} | ||
|
||
.dropdownList .dropdownItem:last-child { | ||
border-bottom-left-radius: 8px; | ||
border-bottom-right-radius: 8px; | ||
border-top-left-radius: 0; | ||
border-top-right-radius: 0; | ||
} | ||
|
||
.dropdownList .dropdownItem:not(:last-child) { | ||
border-radius: 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { Dropdown } from "./Dropdown"; | ||
|
||
const meta = { | ||
title: "Components/Dropdown", | ||
component: Dropdown, | ||
parameters: { | ||
layout: "centered", | ||
}, | ||
tags: ["autodocs"], | ||
argTypes: {}, | ||
args: {}, | ||
} satisfies Meta<typeof Dropdown>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
placeholder: "선택해주세요", | ||
options: ["연도", "작성자", "제목"], | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import React from "react"; | ||
import { render, screen, fireEvent } from "@/utils/test-utils"; | ||
import { Dropdown } from "./Dropdown"; | ||
import "@testing-library/jest-dom"; | ||
|
||
describe("Dropdown component", () => { | ||
const options = ["연도", "작성자", "제목"]; | ||
|
||
it("renders correctly with the given placeholder", () => { | ||
render(<Dropdown placeholder="선택해주세요" options={options} />); | ||
expect(screen.getByRole("button", { name: "선택해주세요" })).toBeInTheDocument(); | ||
}); | ||
|
||
it("toggles the dropdown menu when clicked", () => { | ||
render(<Dropdown placeholder="선택해주세요" options={options} />); | ||
const toggleButton = screen.getByRole("button", { name: "선택해주세요" }); | ||
|
||
// Initially closed | ||
expect(screen.queryByText("연도")).not.toBeInTheDocument(); | ||
|
||
// Open dropdown | ||
fireEvent.click(toggleButton); | ||
expect(screen.getByText("연도")).toBeInTheDocument(); | ||
|
||
// Close dropdown | ||
fireEvent.click(toggleButton); | ||
expect(screen.queryByText("연도")).not.toBeInTheDocument(); | ||
}); | ||
|
||
it("selects an option and closes the dropdown", () => { | ||
render(<Dropdown placeholder="선택해주세요" options={options} />); | ||
const toggleButton = screen.getByRole("button", { name: "선택해주세요" }); | ||
|
||
// Open dropdown | ||
fireEvent.click(toggleButton); | ||
const option = screen.getByText("연도"); | ||
fireEvent.click(option); | ||
|
||
// Check selected option | ||
expect(screen.getByRole("button", { name: "연도" })).toBeInTheDocument(); | ||
// Check dropdown closed | ||
expect(screen.queryByText("작성자")).not.toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import React, { useState } from "react"; | ||
import { Button, Menu, MenuTarget, MenuDropdown, MenuItem } from "@mantine/core"; | ||
import classes from "./Dropdown.module.css"; | ||
|
||
interface DropdownProps { | ||
options: string[]; | ||
placeholder: string; | ||
} | ||
|
||
export function Dropdown({ options, placeholder }: DropdownProps) { | ||
const [selectedOption, setSelectedOption] = useState<string | null>(null); | ||
const [opened, setOpened] = useState<boolean>(false); | ||
|
||
const handleOptionClick = (option: string) => { | ||
setSelectedOption(option); | ||
setOpened(false); | ||
}; | ||
|
||
return ( | ||
<Menu offset={0} opened={opened} onChange={setOpened}> | ||
<MenuTarget> | ||
<Button | ||
justify="space-between" | ||
className={`${classes.dropdownToggle} ${opened ? classes.opened : ""}`} | ||
onClick={() => setOpened(!opened)} | ||
leftSection={<span className={classes.buttonLabel}>{selectedOption || placeholder}</span>} | ||
rightSection={ | ||
<span className={classes.toggleIcon}> | ||
{opened ? ( | ||
/* opened dropdown = Lets Icons v1.0 Single Arrow Arrow_drop_up */ | ||
<svg | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M12.1921 9.23047L15.9065 13.6879C16.3408 14.2089 15.9702 15 15.292 15L8.70803 15C8.02976 15 7.65924 14.2089 8.09346 13.6879L11.8079 9.23047C11.9079 9.11053 12.0921 9.11053 12.1921 9.23047Z" | ||
fill="#222222" | ||
/> | ||
</svg> | ||
) : ( | ||
/* closed dropdown = Lets Icons v1.0 Single Arrow Arrow_drop_down */ | ||
<svg | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M11.8079 14.7695L8.09346 10.3121C7.65924 9.79109 8.02976 9 8.70803 9L15.292 9C15.9702 9 16.3408 9.79108 15.9065 10.3121L12.1921 14.7695C12.0921 14.8895 11.9079 14.8895 11.8079 14.7695Z" | ||
fill="#222222" | ||
/> | ||
</svg> | ||
)} | ||
</span> | ||
} | ||
></Button> | ||
</MenuTarget> | ||
<MenuDropdown className={classes.dropdownList}> | ||
{options.map((option) => ( | ||
<MenuItem | ||
key={option} | ||
className={classes.dropdownItem} | ||
onClick={() => handleOptionClick(option)} | ||
> | ||
{option} | ||
</MenuItem> | ||
))} | ||
</MenuDropdown> | ||
</Menu> | ||
); | ||
} |