-
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.
feat(ui): DropdownMenu 컴포넌트 구현 (#51)
* chore: radix-ui/react-dropdown-menu 의존성 추가 * feat: DropdownMenu 컴포넌트 구현 * docs: DropdownMenu stories 작성 * docs: add changeset
- Loading branch information
Showing
6 changed files
with
395 additions
and
0 deletions.
There are no files selected for viewing
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,5 @@ | ||
--- | ||
'@freemed-kit/ui': patch | ||
--- | ||
|
||
DropdownMenu 컴포넌트 구현 |
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,89 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { useState } from '@storybook/preview-api' | ||
import { | ||
DropdownMenu, | ||
DropdownMenuContent, | ||
DropdownMenuItem, | ||
DropdownMenuCheckboxItem, | ||
DropdownMenuRadioItem, | ||
DropdownMenuLabel, | ||
DropdownMenuSeparator, | ||
DropdownMenuTrigger, | ||
DropdownMenuRadioGroup, | ||
Button, | ||
} from '@freemed-kit/ui' | ||
|
||
const meta: Meta<typeof DropdownMenu> = { | ||
title: 'Components/DropdownMenu', | ||
component: DropdownMenu, | ||
tags: ['autodocs'], | ||
} | ||
|
||
export default meta | ||
type Story = StoryObj<typeof DropdownMenu> | ||
|
||
export const Default: Story = { | ||
render: args => ( | ||
<DropdownMenu> | ||
<DropdownMenuTrigger>Open</DropdownMenuTrigger> | ||
<DropdownMenuContent> | ||
<DropdownMenuLabel>My Account</DropdownMenuLabel> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuItem>Profile</DropdownMenuItem> | ||
<DropdownMenuItem>Billing</DropdownMenuItem> | ||
<DropdownMenuItem>Team</DropdownMenuItem> | ||
<DropdownMenuItem>Subscription</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
), | ||
} | ||
|
||
export const Checkboxes: Story = { | ||
render: function Render(args) { | ||
const [showStatusBar, setShowStatusBar] = useState(true) | ||
const [showActivityBar, setShowActivityBar] = useState(false) | ||
const [showPanel, setShowPanel] = useState(false) | ||
return ( | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<Button variant="outline">Open</Button> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent className="w-56"> | ||
<DropdownMenuLabel>Appearance</DropdownMenuLabel> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuCheckboxItem checked={showStatusBar} onCheckedChange={setShowStatusBar}> | ||
Status Bar | ||
</DropdownMenuCheckboxItem> | ||
<DropdownMenuCheckboxItem checked={showActivityBar} disabled onCheckedChange={setShowActivityBar}> | ||
Activity Bar | ||
</DropdownMenuCheckboxItem> | ||
<DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}> | ||
Panel | ||
</DropdownMenuCheckboxItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
) | ||
}, | ||
} | ||
|
||
export const RadioGroup: Story = { | ||
render: function Render(args) { | ||
const [position, setPosition] = useState('bottom') | ||
return ( | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<Button variant="outline">Open</Button> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent className="w-56"> | ||
<DropdownMenuLabel>Panel Position</DropdownMenuLabel> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuRadioGroup onValueChange={setPosition} value={position}> | ||
<DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem> | ||
<DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem> | ||
<DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem> | ||
</DropdownMenuRadioGroup> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
) | ||
}, | ||
} |
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,183 @@ | ||
import * as React from 'react' | ||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' | ||
import { Check, ChevronRight, Circle } from 'lucide-react' | ||
|
||
import { cn } from '@/lib/utils' | ||
|
||
const DropdownMenu = DropdownMenuPrimitive.Root | ||
|
||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger | ||
|
||
const DropdownMenuGroup = DropdownMenuPrimitive.Group | ||
|
||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal | ||
|
||
const DropdownMenuSub = DropdownMenuPrimitive.Sub | ||
|
||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup | ||
|
||
const DropdownMenuSubTrigger = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { | ||
inset?: boolean | ||
} | ||
>(({ className, inset, children, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.SubTrigger | ||
ref={ref} | ||
className={cn( | ||
'tw-flex tw-cursor-default tw-gap-2 tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent data-[state=open]:tw-bg-accent [&_svg]:tw-pointer-events-none [&_svg]:tw-size-4 [&_svg]:tw-shrink-0', | ||
inset && 'tw-pl-8', | ||
className | ||
)} | ||
{...props} | ||
> | ||
{children} | ||
<ChevronRight className="tw-ml-auto" /> | ||
</DropdownMenuPrimitive.SubTrigger> | ||
)) | ||
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName | ||
|
||
const DropdownMenuSubContent = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> | ||
>(({ className, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.SubContent | ||
ref={ref} | ||
className={cn( | ||
'tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-lg data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)) | ||
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName | ||
|
||
const DropdownMenuContent = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> | ||
>(({ className, sideOffset = 4, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.Portal> | ||
<DropdownMenuPrimitive.Content | ||
ref={ref} | ||
sideOffset={sideOffset} | ||
className={cn( | ||
'tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
</DropdownMenuPrimitive.Portal> | ||
)) | ||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName | ||
|
||
const DropdownMenuItem = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.Item>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { | ||
inset?: boolean | ||
} | ||
>(({ className, inset, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.Item | ||
ref={ref} | ||
className={cn( | ||
'tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-gap-2 tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50 [&_svg]:tw-pointer-events-none [&_svg]:tw-size-4 [&_svg]:tw-shrink-0', | ||
inset && 'tw-pl-8', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)) | ||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName | ||
|
||
const DropdownMenuCheckboxItem = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> | ||
>(({ className, children, checked, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.CheckboxItem | ||
ref={ref} | ||
className={cn( | ||
'tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50', | ||
className | ||
)} | ||
checked={checked} | ||
{...props} | ||
> | ||
<span className="tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center"> | ||
<DropdownMenuPrimitive.ItemIndicator> | ||
<Check className="tw-h-4 tw-w-4" /> | ||
</DropdownMenuPrimitive.ItemIndicator> | ||
</span> | ||
{children} | ||
</DropdownMenuPrimitive.CheckboxItem> | ||
)) | ||
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName | ||
|
||
const DropdownMenuRadioItem = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> | ||
>(({ className, children, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.RadioItem | ||
ref={ref} | ||
className={cn( | ||
'tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none tw-transition-colors focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50', | ||
className | ||
)} | ||
{...props} | ||
> | ||
<span className="tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center"> | ||
<DropdownMenuPrimitive.ItemIndicator> | ||
<Circle className="tw-h-2 tw-w-2 tw-fill-current" /> | ||
</DropdownMenuPrimitive.ItemIndicator> | ||
</span> | ||
{children} | ||
</DropdownMenuPrimitive.RadioItem> | ||
)) | ||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName | ||
|
||
const DropdownMenuLabel = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.Label>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { | ||
inset?: boolean | ||
} | ||
>(({ className, inset, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.Label | ||
ref={ref} | ||
className={cn('tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold', inset && 'tw-pl-8', className)} | ||
{...props} | ||
/> | ||
)) | ||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName | ||
|
||
const DropdownMenuSeparator = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> | ||
>(({ className, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.Separator | ||
ref={ref} | ||
className={cn('tw--mx-1 tw-my-1 tw-h-px tw-bg-muted', className)} | ||
{...props} | ||
/> | ||
)) | ||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName | ||
|
||
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => { | ||
return <span className={cn('tw-ml-auto tw-text-xs tw-tracking-widest tw-opacity-60', className)} {...props} /> | ||
} | ||
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut' | ||
|
||
export { | ||
DropdownMenu, | ||
DropdownMenuTrigger, | ||
DropdownMenuContent, | ||
DropdownMenuItem, | ||
DropdownMenuCheckboxItem, | ||
DropdownMenuRadioItem, | ||
DropdownMenuLabel, | ||
DropdownMenuSeparator, | ||
DropdownMenuShortcut, | ||
DropdownMenuGroup, | ||
DropdownMenuPortal, | ||
DropdownMenuSub, | ||
DropdownMenuSubContent, | ||
DropdownMenuSubTrigger, | ||
DropdownMenuRadioGroup, | ||
} |
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
Oops, something went wrong.