diff --git a/package-lock.json b/package-lock.json index ef663737..5008accf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "postcss": "^8.4.39", "postcss-preset-mantine": "^1.15.0", "postcss-simple-vars": "^7.0.1", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "storybook": "^8.2.2", "storybook-dark-mode": "^4.0.2", "ts-jest": "^29.2.2", @@ -15763,9 +15763,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/package.json b/package.json index e8b78b5e..3a4bdf37 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "postcss": "^8.4.39", "postcss-preset-mantine": "^1.15.0", "postcss-simple-vars": "^7.0.1", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "storybook": "^8.2.2", "storybook-dark-mode": "^4.0.2", "ts-jest": "^29.2.2", diff --git a/src/components/common/Dropdown/Dropdown.module.css b/src/components/common/Dropdown/Dropdown.module.css new file mode 100644 index 00000000..40b5486f --- /dev/null +++ b/src/components/common/Dropdown/Dropdown.module.css @@ -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; +} diff --git a/src/components/common/Dropdown/Dropdown.story.tsx b/src/components/common/Dropdown/Dropdown.story.tsx new file mode 100644 index 00000000..a333e2fe --- /dev/null +++ b/src/components/common/Dropdown/Dropdown.story.tsx @@ -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; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + placeholder: "선택해주세요", + options: ["연도", "작성자", "제목"], + }, +}; diff --git a/src/components/common/Dropdown/Dropdown.test.tsx b/src/components/common/Dropdown/Dropdown.test.tsx new file mode 100644 index 00000000..963fb03a --- /dev/null +++ b/src/components/common/Dropdown/Dropdown.test.tsx @@ -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(); + expect(screen.getByRole("button", { name: "선택해주세요" })).toBeInTheDocument(); + }); + + it("toggles the dropdown menu when clicked", () => { + render(); + 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(); + 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(); + }); +}); diff --git a/src/components/common/Dropdown/Dropdown.tsx b/src/components/common/Dropdown/Dropdown.tsx new file mode 100644 index 00000000..ef7cef7b --- /dev/null +++ b/src/components/common/Dropdown/Dropdown.tsx @@ -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(null); + const [opened, setOpened] = useState(false); + + const handleOptionClick = (option: string) => { + setSelectedOption(option); + setOpened(false); + }; + + return ( + + + + + + {options.map((option) => ( + handleOptionClick(option)} + > + {option} + + ))} + + + ); +}