diff --git a/packages/tailwind-config/theme.css b/packages/tailwind-config/theme.css index 5c2c7ba..bdbbc4f 100644 --- a/packages/tailwind-config/theme.css +++ b/packages/tailwind-config/theme.css @@ -1,4 +1,8 @@ @import "./tokens/colors.css"; @import "./tokens/typography.css"; @import "./tokens/radius.css"; -@import "./scrollbar.css"; \ No newline at end of file +@import "./scrollbar.css"; + +@theme { + --shadow-timo: 0px 0px 3px 0px rgba(0, 0, 0, 0.12); +} \ No newline at end of file diff --git a/packages/timo-design-system/src/components/dropdown/Dropdown.stories.tsx b/packages/timo-design-system/src/components/dropdown/Dropdown.stories.tsx new file mode 100644 index 0000000..6d75508 --- /dev/null +++ b/packages/timo-design-system/src/components/dropdown/Dropdown.stories.tsx @@ -0,0 +1,24 @@ +import { useState } from "react"; + +import { Dropdown } from "./Dropdown"; + +import type { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Components/Dropdown", + component: Dropdown, + parameters: { layout: "centered" }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const DefaultStory = () => { + const [value, setValue] = useState(""); + return ; +}; + +export const Default: Story = { + args: { items: [], value: "", onChange: () => {} }, + render: () => , +}; diff --git a/packages/timo-design-system/src/components/dropdown/Dropdown.tsx b/packages/timo-design-system/src/components/dropdown/Dropdown.tsx new file mode 100644 index 0000000..cc51879 --- /dev/null +++ b/packages/timo-design-system/src/components/dropdown/Dropdown.tsx @@ -0,0 +1,122 @@ +import { cn } from "@lib"; +import { useEffect, useRef, useState } from "react"; + +export interface DropdownProps { + items: string[]; + value: string; + onChange: (value: string) => void; + placeholder?: string; + className?: string; +} + +export const Dropdown = ({ + items, + value, + onChange, + placeholder = "기본", + className, +}: DropdownProps) => { + const [isOpen, setIsOpen] = useState(false); + const containerRef = useRef(null); + const triggerRef = useRef(null); + + const handleSelect = (item: string) => { + onChange(item); + setIsOpen(false); + }; + + const closeWithFocus = () => { + setIsOpen(false); + triggerRef.current?.focus(); + }; + + useEffect(() => { + if (!isOpen) return; + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") closeWithFocus(); + }; + const handleClickOutside = (e: MouseEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(e.target as Node) + ) { + closeWithFocus(); + } + }; + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [isOpen]); + + return ( +
+ + + {isOpen && ( +
    + {items.map((item, i) => ( +
  • + handleSelect(item)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") handleSelect(item); + }} + className="typo-body-m-12 text-timo-gray-900 cursor-pointer" + > + {item} + + {i < items.length - 1 && ( +
    + )} +
  • + ))} +
+ )} +
+ ); +}; diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index 7917669..17c5644 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -1,4 +1,5 @@ export { Checkbox } from "@components/checkbox/Checkbox"; +export { Dropdown } from "@components/dropdown/Dropdown"; export { Color } from "@components/color/Color"; export { Typography } from "@components/typography/Typography"; export { Tag } from "@components/tag/Tag";