Skip to content

Commit

Permalink
Merge pull request #937 from tszhong0411/pack-11-add-context-menu
Browse files Browse the repository at this point in the history
Add context menu
  • Loading branch information
tszhong0411 authored Jan 9, 2025
2 parents 84dab1c + 338ab05 commit 622fd0b
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-turkeys-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tszhong0411/ui': patch
---

Standardize the styles of dropdown menu
5 changes: 5 additions & 0 deletions .changeset/tall-knives-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tszhong0411/ui': patch
---

Add context menu component
2 changes: 2 additions & 0 deletions .cspell/names.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ calcom
cleanmyma
cleanmymac
cleanshot
colm
delba
duarte
ghostty
Expand Down Expand Up @@ -38,6 +39,7 @@ samuelkraft
tableplus
tabtab
theodorusclarence
Tuite
umami
visualstudiocode
vocs
Expand Down
29 changes: 29 additions & 0 deletions apps/docs/src/app/ui/components/context-menu.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Context Menu
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
---

<ComponentPreview name='context-menu/context-menu' />

## Usage

```tsx
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger
} from '@tszhong0411/ui'
```

```tsx
<ContextMenu>
<ContextMenuTrigger>Right click</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Profile</ContextMenuItem>
<ContextMenuItem>Billing</ContextMenuItem>
<ContextMenuItem>Team</ContextMenuItem>
<ContextMenuItem>Subscription</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
```
67 changes: 67 additions & 0 deletions apps/docs/src/components/demos/context-menu/context-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger
} from '@tszhong0411/ui'

const ContextMenuDemo = () => {
return (
<ContextMenu>
<ContextMenuTrigger className='flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm'>
Right click here
</ContextMenuTrigger>
<ContextMenuContent className='w-64'>
<ContextMenuItem inset>
Back
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset disabled>
Forward
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem inset>
Reload
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent className='w-48'>
<ContextMenuItem>
Save Page As...
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
<ContextMenuItem>Name Window...</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuCheckboxItem checked>
Show Bookmarks Bar
<ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
<ContextMenuSeparator />
<ContextMenuRadioGroup value='pedro'>
<ContextMenuLabel inset>People</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuRadioItem value='pedro'>Pedro Duarte</ContextMenuRadioItem>
<ContextMenuRadioItem value='colm'>Colm Tuite</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
)
}

export default ContextMenuDemo
4 changes: 4 additions & 0 deletions apps/docs/src/config/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export const SIDEBAR_LINKS: SidebarLinks = [
href: '/ui/components/command',
text: 'Command'
},
{
href: '/ui/components/context-menu',
text: 'Context Menu'
},
{
href: '/ui/components/data-table',
text: 'Data Table'
Expand Down
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",
Expand Down
199 changes: 199 additions & 0 deletions packages/ui/src/context-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
'use client'

import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
import { cn } from '@tszhong0411/utils'
import { CheckIcon, ChevronRightIcon, DotIcon } from 'lucide-react'

export const ContextMenu = ContextMenuPrimitive.Root
export const ContextMenuTrigger = ContextMenuPrimitive.Trigger
export const ContextMenuGroup = ContextMenuPrimitive.Group
export const ContextMenuPortal = ContextMenuPrimitive.Portal
export const ContextMenuSub = ContextMenuPrimitive.Sub
export const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup

type ContextMenuSubTriggerProps = {
inset?: boolean
} & React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger>

export const ContextMenuSubTrigger = (props: ContextMenuSubTriggerProps) => {
const { className, inset, children, ...rest } = props

return (
<ContextMenuPrimitive.SubTrigger
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'focus:bg-accent focus:text-accent-foreground',
'data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className
)}
{...rest}
>
{children}
<ChevronRightIcon className='ml-auto size-4' />
</ContextMenuPrimitive.SubTrigger>
)
}

type ContextMenuSubContentProps = React.ComponentProps<typeof ContextMenuPrimitive.SubContent>

export const ContextMenuSubContent = (props: ContextMenuSubContentProps) => {
const { className, ...rest } = props

return (
<ContextMenuPrimitive.SubContent
className={cn(
'bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg',
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
'data-[side=top]:slide-in-from-bottom-2',
'data-[side=right]:slide-in-from-left-2',
'data-[side=bottom]:slide-in-from-top-2',
'data-[side=left]:slide-in-from-right-2',
className
)}
{...rest}
/>
)
}

type ContextMenuContentProps = React.ComponentProps<typeof ContextMenuPrimitive.Content>

export const ContextMenuContent = (props: ContextMenuContentProps) => {
const { className, ...rest } = props

return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
className={cn(
'bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md',
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
'data-[side=top]:slide-in-from-bottom-2',
'data-[side=right]:slide-in-from-left-2',
'data-[side=bottom]:slide-in-from-top-2',
'data-[side=left]:slide-in-from-right-2',
className
)}
{...rest}
/>
</ContextMenuPrimitive.Portal>
)
}

type ContextMenuItemProps = {
inset?: boolean
} & React.ComponentProps<typeof ContextMenuPrimitive.Item>

export const ContextMenuItem = (props: ContextMenuItemProps) => {
const { className, inset, ...rest } = props

return (
<ContextMenuPrimitive.Item
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
'focus:bg-accent focus:text-accent-foreground',
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
{...rest}
/>
)
}

type ContextMenuCheckboxItemProps = React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>

export const ContextMenuCheckboxItem = (props: ContextMenuCheckboxItemProps) => {
const { className, children, checked, ...rest } = props

return (
<ContextMenuPrimitive.CheckboxItem
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors',
'focus:bg-accent focus:text-accent-foreground',
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
{...rest}
>
<span className='absolute left-2 flex size-3.5 items-center justify-center'>
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className='size-4' />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
)
}

type ContextMenuRadioItemProps = React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>

export const ContextMenuRadioItem = (props: ContextMenuRadioItemProps) => {
const { className, children, ...rest } = props

return (
<ContextMenuPrimitive.RadioItem
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors',
'focus:bg-accent focus:text-accent-foreground',
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...rest}
>
<span className='absolute left-2 flex size-3.5 items-center justify-center'>
<ContextMenuPrimitive.ItemIndicator>
<DotIcon className='size-9' />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
)
}

type ContextMenuLabelProps = {
inset?: boolean
} & React.ComponentProps<typeof ContextMenuPrimitive.Label>

export const ContextMenuLabel = (props: ContextMenuLabelProps) => {
const { className, inset, ...rest } = props

return (
<ContextMenuPrimitive.Label
className={cn(
'text-foreground px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className
)}
{...rest}
/>
)
}

type ContextMenuSeparatorProps = React.ComponentProps<typeof ContextMenuPrimitive.Separator>

export const ContextMenuSeparator = (props: ContextMenuSeparatorProps) => {
const { className, ...rest } = props

return (
<ContextMenuPrimitive.Separator
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...rest}
/>
)
}

type ContextMenuShortcutProps = React.ComponentProps<'span'>

export const ContextMenuShortcut = (props: ContextMenuShortcutProps) => {
const { className, ...rest } = props

return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...rest}
/>
)
}
19 changes: 14 additions & 5 deletions packages/ui/src/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export const DropdownMenuSubTrigger = (
<DropdownMenuPrimitive.SubTrigger
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'focus:bg-accent',
'data-[state=open]:bg-accent',
'focus:bg-accent focus:text-accent-foreground',
'data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className
)}
Expand Down Expand Up @@ -163,7 +163,11 @@ export const DropdownMenuLabel = (

return (
<DropdownMenuPrimitive.Label
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn(
'text-foreground px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className
)}
{...rest}
/>
)
Expand All @@ -176,7 +180,7 @@ export const DropdownMenuSeparator = (

return (
<DropdownMenuPrimitive.Separator
className={cn('bg-muted -mx-1 my-1 h-px', className)}
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...rest}
/>
)
Expand All @@ -185,7 +189,12 @@ export const DropdownMenuSeparator = (
export const DropdownMenuShortcut = (props: React.ComponentProps<'span'>) => {
const { className, ...rest } = props

return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...rest} />
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...rest}
/>
)
}

DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './checkbox'
export * from './code-block'
export * from './collapsible'
export * from './command'
export * from './context-menu'
export * from './data-table'
export * from './dialog'
export * from './dropdown-menu'
Expand Down
Loading

0 comments on commit 622fd0b

Please sign in to comment.