Skip to content

Commit

Permalink
feat(contextmenu): support contextmenu
Browse files Browse the repository at this point in the history
  • Loading branch information
qianmoQ committed Nov 12, 2024
1 parent 22e4d0a commit 2f1620e
Show file tree
Hide file tree
Showing 14 changed files with 334 additions and 23 deletions.
13 changes: 11 additions & 2 deletions configure/publish/publish-deploy-npm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@ yarn run build
time=$(date "+%Y-%m-%d %H:%M:%S")

echo "Select the release type:"
echo "0. version"
echo "1. alpha"
echo "2. beta"
echo "3. prod"

read -p "Enter your choice (1-3): " choice
read -p "Enter your choice (0-3): " choice

current_version=$(node -p "require('./package.json').version")

if [ "$choice" = "1" ]; then
if [ "$choice" = "0" ]; then
major=$(echo "$current_version" | cut -d'.' -f1)
minor=$(echo "$current_version" | cut -d'.' -f2)
patch=$(echo "$current_version" | cut -d'.' -f3 | cut -d'-' -f1)
# shellcheck disable=SC2004
new_version="${major}.${minor}.$(( $patch))-alpha.$(date +%s)"
echo "Only publishing version $new_version on $time"
yarn version --new-version "$new_version" --no-git-tag-version
elif [ "$choice" = "1" ]; then
major=$(echo "$current_version" | cut -d'.' -f1)
minor=$(echo "$current_version" | cut -d'.' -f2)
patch=$(echo "$current_version" | cut -d'.' -f3 | cut -d'-' -f1)
Expand Down
2 changes: 1 addition & 1 deletion configure/publish/publish-deploy-page.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ yarn run docs:build

cd docs/.vitepress/dist

echo 'shadcn.vue.devlive.org' > CNAME
echo 'view-shadcn-ui.devlive.org' > CNAME

time=$(date "+%Y-%m-%d %H:%M:%S")

Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export default {
{text: 'Slider <span class="VPBadge tip">2024.2.0</span>', link: 'slider'},
{text: 'Link <span class="VPBadge tip">2024.2.0</span>', link: 'link'},
{text: 'Dropdown <span class="VPBadge tip">2024.2.0</span>', link: 'dropdown'},
{text: 'Contextmenu <span class="VPBadge tip">2024.4.0</span>', link: 'contextmenu'},
]
return {
text: `Navigation [ ${items.length} ]`,
Expand Down
7 changes: 5 additions & 2 deletions docs/.vitepress/theme/css/override.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@
background-color: #bfdbfe;
}

.bg-blue-300
{
.bg-blue-300 {
background-color: #93c5fd;
}

.bg-gray-100 {
background-color: #f3f4f6;
}
113 changes: 113 additions & 0 deletions docs/components/contextmenu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: Shadcn Context Menu
---

# Introduction

This document is mainly used to describe some features and usage of the ShadcnContextMenu component.

- ShadcnContextMenu
- ShadcnContextMenuItem

## Usage

::: raw

<CodeRunner title="Usage">
<ShadcnContextMenu v-model="showMenu">
<template #trigger>
<div class="w-full h-32 bg-gray-100 rounded-lg flex items-center justify-center">
Right click in this area to show menu
</div>
</template>
<ShadcnContextMenuItem @click="onItemClick('edit')">Edit</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('delete')">Delete</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('share')">Share</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('move')">Move</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('copy')">Copy</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('print')">Print</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('duplicate')">Duplicate</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('duplicate-with-images-and-text')">Duplicate with images and text</ShadcnContextMenuItem>
</ShadcnContextMenu>
</CodeRunner>

:::

::: details Show code

```vue
<template>
<ShadcnContextMenu v-model="showMenu">
<template #trigger>
<div class="w-full h-64 bg-gray-100 rounded-lg flex items-center justify-center">
Right click in this area to show menu
</div>
</template>
<ShadcnContextMenuItem @click="onItemClick('edit')">Edit</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('delete')">Delete</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('share')">Share</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('move')">Move</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('copy')">Copy</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('print')">Print</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('duplicate')">Duplicate</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('duplicate-with-images-and-text')">Duplicate with images and text</ShadcnContextMenuItem>
</ShadcnContextMenu>
</template>
<script setup>
import { ref } from 'vue'
const showMenu = ref(false)
const onItemClick = (action) => console.log(`Clicked: ${action}`)
</script>
```

:::

## Context Menu Props

<ApiTable title="Context Menu Props"
:headers="['Attribute', 'Description', 'Type', 'Default Value', 'List']"
:columns="[
['modelValue', 'The model value of the context menu', 'boolean', 'false', '-'],
]">
</ApiTable>

## Context Menu Events

<ApiTable title="Context Menu Events"
:headers="['Event', 'Description', 'Parameters']"
:columns="[
['update:modelValue', 'Update the model value of the context menu', 'boolean'],
['on-open', 'Emit when the context menu is opened', '-'],
['on-close', 'Emit when the context menu is closed', '-'],
]">
</ApiTable>

<br />

<ApiTable title="Context Menu Item Events"
:headers="['Event', 'Description', 'Parameters']"
:columns="[
['on-click', 'Emit when the context menu item is clicked', '-'],
]">
</ApiTable>

## Context Menu Slots

<ApiTable title="Context Menu Slots"
:headers="['Slot', 'Description']"
:columns="[
['trigger', 'The trigger slot of the context menu'],
]">
</ApiTable>

<script setup>
import { ref } from 'vue'

const showMenu = ref(false)

const onItemClick = (action) => console.log(`Clicked: ${action}`)
</script>
4 changes: 4 additions & 0 deletions docs/components/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,8 @@ features:
- icon: <img style="width:100%; height:100%; object-fit:contain;" src="/components/gradient-text.svg" />
title: <a href='gradient-text.html'>Shadcn Gradient Text <span class="VPBadge tip">2024.3.0</span></a>
details: Gradient Text component

- icon: <img style="width:100%; height:100%; object-fit:contain;" src="/components/contextmenu.svg" />
title: <a href='contextmenu.html'>Shadcn Context Menu <span class="VPBadge tip">2024.4.0</span></a>
details: Context Menu component
---
17 changes: 17 additions & 0 deletions docs/public/components/contextmenu.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"author": "devlive-community",
"homepage": "https://github.com/devlive-community/view-shadcn-ui",
"private": false,
"version": "2024.3.0",
"version": "2024.4.0-alpha.1731396516",
"license": "MIT",
"main": "./dist/view-shadcn.umd.ts",
"module": "./dist/view-shadcn.es.ts",
Expand Down
6 changes: 5 additions & 1 deletion packages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import ShadcnNumber from '@/ui/number'
import ShadcnTree from '@/ui/tree'
import ShadcnText from '@/ui/text'
import ShadcnGradientText from '@/ui/text/gradient'
import { ShadcnContextMenu, ShadcnContextMenuItem } from '@/ui/contextmenu'

let components = [
ShadcnButton,
Expand Down Expand Up @@ -146,7 +147,9 @@ let components = [
ShadcnNumber,
ShadcnTree,
ShadcnText,
ShadcnGradientText
ShadcnGradientText,
ShadcnContextMenu,
ShadcnContextMenuItem
]

const install = (Vue: App) => {
Expand Down Expand Up @@ -243,6 +246,7 @@ export { default as ShadcnNumber } from '@/ui/number'
export { default as ShadcnTree } from '@/ui/tree'
export { default as ShadcnText } from '@/ui/text'
export { default as ShadcnGradientText } from '@/ui/text/gradient'
export { ShadcnContextMenu, ShadcnContextMenuItem } from '@/ui/contextmenu'

// Support global import
export default install
38 changes: 22 additions & 16 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
<template>
<div class="p-32 space-y-2">
<div>Horizontal</div>
<ShadcnDivider type="horizontal"/>
<div class="flex space-x-2">
<div>Vertical</div>
<ShadcnDivider type="vertical"/>
<a href="#">Link</a>
<ShadcnDivider type="vertical"/>
<div>Vertical</div>
</div>
<div class="min-h-screen p-4">
<ShadcnContextMenu v-model="showMenu">
<template #trigger>
<div class="w-full h-64 bg-gray-100 rounded-lg flex items-center justify-center">
Right click in this area to show menu
</div>
</template>

<ShadcnDivider orientation="left">Left</ShadcnDivider>
<ShadcnDivider orientation="center">Center</ShadcnDivider>
<ShadcnDivider orientation="right">Right</ShadcnDivider>

<ShadcnDivider dashed />
<ShadcnContextMenuItem @click="onItemClick('edit')">Edit</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('delete')">Delete</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('share')">Share</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('move')">Move</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('copy')">Copy</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('print')">Print</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('duplicate')">Duplicate</ShadcnContextMenuItem>
<ShadcnContextMenuItem @click="onItemClick('duplicate-with-images-and-text')">Duplicate with images and text</ShadcnContextMenuItem>
</ShadcnContextMenu>
</div>
</template>

<script setup lang="ts">
<script setup>
import { ref } from 'vue'
const showMenu = ref(false)
const onItemClick = (action) => console.log(`Clicked: ${action}`)
</script>
118 changes: 118 additions & 0 deletions src/ui/contextmenu/ShadcnContextMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// ShadcnContextMenu.vue
<template>
<div ref="triggerRef" @contextmenu.prevent="onContextMenu">
<slot name="trigger"/>
</div>

<Teleport to="body">
<div v-if="modelValue"
ref="menuRef"
class="fixed min-w-[8rem] z-50 bg-white rounded-md shadow-lg border border-gray-200 p-1"
:style="menuStyle">
<slot/>
</div>
</Teleport>
</template>

<script setup lang="ts">
import { computed, defineEmits, onMounted, onUnmounted, provide, ref } from 'vue'
import { ContextMenuEmits, ContextMenuProps } from './types'
import { calcSize } from '@/utils/common.ts'
const emit = defineEmits<ContextMenuEmits>()
const props = withDefaults(defineProps<ContextMenuProps>(), {
modelValue: false
})
const menuPosition = ref({ x: 0, y: 0 })
const menuRef = ref<HTMLElement | null>(null)
const triggerRef = ref<HTMLElement | null>(null)
// Calculate the position of the menu, handle boundary cases
const menuStyle = computed(() => {
if (!menuRef.value) {
return {
left: calcSize(menuPosition.value.x),
top: calcSize(menuPosition.value.y)
}
}
const menuRect = menuRef.value.getBoundingClientRect()
const menuWidth = menuRect.width
const menuHeight = menuRect.height
// Get the window size
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
// Calculate the final position
let x = menuPosition.value.x
let y = menuPosition.value.y
// Handle the right boundary
if (x + menuWidth > viewportWidth) {
x = viewportWidth - menuWidth - 10
}
if (x < 0) {
x = 8
}
if (y + menuHeight > viewportHeight) {
y = viewportHeight - menuHeight - 8
}
if (y < 0) {
y = 8
}
return {
left: calcSize(x),
top: calcSize(y)
}
})
provide('menuPosition', menuPosition)
const closeMenu = () => {
emit('update:modelValue', false)
emit('on-close', false)
}
provide('closeMenu', closeMenu)
// Handle right-click events
const onContextMenu = (event) => {
event.preventDefault()
menuPosition.value = {
x: event.clientX,
y: event.clientY
}
emit('update:modelValue', true)
emit('on-open', true)
}
// Handle clicking on the external close menu
const onClickOutside = (event) => {
if (menuRef.value && !menuRef.value.contains(event.target)) {
emit('update:modelValue', false)
emit('on-close', false)
}
}
const handleResize = () => {
if (props.modelValue) {
closeMenu()
}
}
onMounted(() => {
document.addEventListener('click', onClickOutside)
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
document.removeEventListener('click', onClickOutside)
window.removeEventListener('resize', handleResize)
})
</script>
Loading

0 comments on commit 2f1620e

Please sign in to comment.