feat: context menu
This commit is contained in:
parent
b34e1dbc62
commit
9d43d0b6e7
66
apps/www/src/content/docs/components/context-menu.md
Normal file
66
apps/www/src/content/docs/components/context-menu.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
title: Context Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
source: https://github.com/radix-vue/shadcn-vue/tree/main/apps/www/src/lib/registry/default/ui/context-menu
|
||||
primitive: https://www.radix-vue.com/components/context-menu.html
|
||||
---
|
||||
|
||||
|
||||
<ComponentPreview name="ContextMenuDemo" >
|
||||
|
||||
<<< ../../../lib/registry/default/examples/ContextMenuDemo.vue
|
||||
|
||||
</ComponentPreview>
|
||||
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add context-menu
|
||||
```
|
||||
|
||||
<ManualInstall>
|
||||
|
||||
1. Install `radix-vue`:
|
||||
|
||||
```bash
|
||||
npm install radix-vue
|
||||
```
|
||||
|
||||
2. Copy and paste the component source files linked at the top of this page into your project.
|
||||
</ManualInstall>
|
||||
|
||||
## Usage
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuRadioGroup,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
} from '@/lib/registry/default/ui/context-menu'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>Right click</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem>Profile</ContextMenuItem>
|
||||
<ContextMenuItem>Billing</ContextMenuItem>
|
||||
<ContextMenuItem>Team</ContextMenuItem>
|
||||
<ContextMenuItem>Subscription</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
```
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuRadioGroup,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
} from '@/lib/registry/default/ui/context-menu'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent class="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 class="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>
|
||||
</template>
|
||||
|
|
@ -5,12 +5,10 @@ import { useEmitAsProps } from '@/lib/utils'
|
|||
|
||||
const props = defineProps<CollapsibleRootProps>()
|
||||
const emits = defineEmits<CollapsibleRootEmits>()
|
||||
|
||||
const emitsAsProps = useEmitAsProps(emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CollapsibleRoot v-slot="{ open }" v-bind="{ ...props, ...emitsAsProps }">
|
||||
<CollapsibleRoot v-slot="{ open }" v-bind="{ ...props, ...useEmitAsProps(emits) }">
|
||||
<slot :open="open" />
|
||||
</CollapsibleRoot>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import { ContextMenuRoot, type ContextMenuRootProps } from 'radix-vue'
|
||||
import { ContextMenuRoot } from 'radix-vue'
|
||||
import type { ContextMenuRootEmits, ContextMenuRootProps } from 'radix-vue'
|
||||
import { useEmitAsProps } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuRootProps>()
|
||||
const emits = defineEmits<ContextMenuRootEmits>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRoot v-bind="props">
|
||||
<ContextMenuRoot v-bind="{ ...props, ...useEmitAsProps(emits) }">
|
||||
<slot />
|
||||
</ContextMenuRoot>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,30 +5,30 @@ import {
|
|||
type ContextMenuCheckboxItemProps,
|
||||
ContextMenuItemIndicator,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import RadixIconsCheck from '~icons/radix-icons/check'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { cn, useEmitAsProps } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuCheckboxItemProps & { class?: string }>()
|
||||
|
||||
const emits = defineEmits<ContextMenuCheckboxItemEmits>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuCheckboxItem
|
||||
v-bind="props"
|
||||
v-bind="{ ...props, ...useEmitAsProps(emits) }"
|
||||
:class="[
|
||||
cn(
|
||||
'flex relative items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-outline-hover pl-7 py-1.5 text-sm outline-none select-none cursor-default',
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
),
|
||||
]"
|
||||
@update:checked="emits('update:checked', $event)"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuItemIndicator
|
||||
class="absolute left-1.5 inline-flex w-4 h-4 items-center justify-center"
|
||||
>
|
||||
<RadixIconsCheck />
|
||||
<Check class="h-4 h-w" />
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</ContextMenuCheckboxItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,25 +5,23 @@ import {
|
|||
type ContextMenuContentProps,
|
||||
ContextMenuPortal,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { cn, useEmitAsProps } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuContentProps & { class?: string }>()
|
||||
|
||||
const emits = defineEmits<ContextMenuContentEmits>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal force-mount>
|
||||
<ContextMenuPortal>
|
||||
<ContextMenuContent
|
||||
:loop="props.loop"
|
||||
:align-offset="props.alignOffset"
|
||||
:as-child="props.asChild"
|
||||
:class="[
|
||||
cn(
|
||||
'bg-background focus:outline-none outline-none border border-border p-1 z-50 min-w-[8rem] rounded-md shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
props.class,
|
||||
),
|
||||
]"
|
||||
v-bind="{ ...props, ...useEmitAsProps(emits) }"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuContent>
|
||||
|
|
|
|||
|
|
@ -4,23 +4,22 @@ import {
|
|||
type ContextMenuItemEmits,
|
||||
type ContextMenuItemProps,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuItemProps & { class?: string }>()
|
||||
import { cn, useEmitAsProps } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuItemProps & { class?: string; inset?: boolean }>()
|
||||
const emits = defineEmits<ContextMenuItemEmits>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuItem
|
||||
v-bind="props"
|
||||
v-bind="{ ...props, ...useEmitAsProps(emits) }"
|
||||
:class="[
|
||||
cn(
|
||||
'flex items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none focus:bg-outline-hover px-2 py-1.5 text-sm outline-none select-none cursor-default',
|
||||
'relative 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-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
),
|
||||
]"
|
||||
@select="emits('select', $event)"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuItem>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { ContextMenuLabel, type ContextMenuLabelProps } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuLabelProps>()
|
||||
const props = defineProps<ContextMenuLabelProps & { class?: string; inset?: boolean }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-2 py-1.5 text-sm font-semibold">
|
||||
<ContextMenuLabel v-bind="props">
|
||||
<ContextMenuLabel
|
||||
v-bind="props"
|
||||
:class="
|
||||
cn('px-2 py-1.5 text-sm font-semibold text-foreground',
|
||||
inset && 'pl-8', props.class ?? '',
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuLabel>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { ContextMenuPortal, type ContextMenuPortalProps } from 'radix-vue'
|
||||
|
||||
const props = defineProps<ContextMenuPortalProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal v-bind="props">
|
||||
<slot />
|
||||
</ContextMenuPortal>
|
||||
</template>
|
||||
|
|
@ -5,30 +5,28 @@ import {
|
|||
type ContextMenuRadioItemEmits,
|
||||
type ContextMenuRadioItemProps,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import RiCheckboxBlankCircleFill from '~icons/ri/checkbox-blank-circle-fill'
|
||||
import { Circle } from 'lucide-vue-next'
|
||||
import { cn, useEmitAsProps } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuRadioItemProps & { class?: string }>()
|
||||
|
||||
const emits = defineEmits<ContextMenuRadioItemEmits>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRadioItem
|
||||
v-bind="props"
|
||||
v-bind="{ ...props, ...useEmitAsProps(emits) }"
|
||||
:class="[
|
||||
cn(
|
||||
'flex relative items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-outline-hover pl-7 py-1.5 text-sm outline-none select-none cursor-default',
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class,
|
||||
),
|
||||
]"
|
||||
@select="emits('select', $event)"
|
||||
>
|
||||
<ContextMenuItemIndicator
|
||||
class="absolute left-2 inline-flex w-2 h-2 items-center justify-center"
|
||||
>
|
||||
<RiCheckboxBlankCircleFill class="text-foreground" />
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuItemIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</ContextMenuRadioItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import {
|
|||
ContextMenuSeparator,
|
||||
type ContextMenuSeparatorProps,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuSeparatorProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSeparator v-bind="props" class="-mx-1 my-1 h-px bg-secondary" />
|
||||
<ContextMenuSeparator v-bind="props" :class="cn('-mx-1 my-1 h-px bg-border', $attrs.class ?? '')" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-xxs ml-auto tracking-widest opacity-50">
|
||||
<span :class="cn('ml-auto text-xs tracking-widest text-muted-foreground', $attrs.class ?? '')">
|
||||
<slot />
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
ContextMenuPortal,
|
||||
ContextMenuSubContent,
|
||||
type DropdownMenuSubContentEmits,
|
||||
type DropdownMenuSubContentProps,
|
||||
} from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { cn, useEmitAsProps } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: string }>()
|
||||
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal force-mount>
|
||||
<ContextMenuSubContent
|
||||
:loop="props.loop"
|
||||
:align-offset="props.alignOffset"
|
||||
:side="props.side"
|
||||
:side-offset="props.sideOffset"
|
||||
:as-child="props.asChild"
|
||||
v-bind="{ ...props, ...useEmitAsProps(emits) }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background focus:outline-none outline-none text-foreground border border-border p-1 z-50 min-w-[10rem] rounded-md shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuPortal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ import {
|
|||
ContextMenuSubTrigger,
|
||||
type ContextMenuSubTriggerProps,
|
||||
} from 'radix-vue'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<ContextMenuSubTriggerProps & { class?: string }>()
|
||||
const props = defineProps<ContextMenuSubTriggerProps & { class?: string; inset?: boolean }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -13,11 +14,13 @@ const props = defineProps<ContextMenuSubTriggerProps & { class?: string }>()
|
|||
v-bind="props"
|
||||
:class="[
|
||||
cn(
|
||||
'flex items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-outline-hover px-2 py-1.5 text-sm outline-none select-none cursor-default',
|
||||
'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',
|
||||
props.class,
|
||||
),
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
</ContextMenuSubTrigger>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user