Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(context-menu): new component #422

Closed
wants to merge 18 commits into from
Closed
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Enter the component you want most in the components, leave the emojis and follow
| [Avatar](https://oku-ui.com/primitives/components/avatar) | <span><a href="https://www.npmjs.com/package/@oku-ui/avatar "><img src="https://img.shields.io/npm/v/@oku-ui/avatar?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/avatar"><img src="https://img.shields.io/npm/dm/@oku-ui/avatar?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"></a></span> | <span> <a href="https://oku-ui.com/primitives/components/avatar"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> |
| [Checkbox](https://oku-ui.com/primitives/components/checkbox) | <span><a href="https://www.npmjs.com/package/@oku-ui/checkbox "><img src="https://img.shields.io/npm/v/@oku-ui/checkbox?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/checkbox"> <img src="https://img.shields.io/npm/dm/@oku-ui/checkbox?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/checkbox"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> |
| [Collapsible](https://oku-ui.com/primitives/components/collapsible) | <span><a href="https://www.npmjs.com/package/@oku-ui/collapsible "><img src="https://img.shields.io/npm/v/@oku-ui/collapsible?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/collapsible"> <img src="https://img.shields.io/npm/dm/@oku-ui/collapsible?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/collapsible"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> |
| [Context Menu](https://github.com/oku-ui/primitives/issues/8) | A menu that appears when a user interacts with an element's trigger | Not Started | - |
| [Context Menu](https://oku-ui.com/primitives/components/context-menu) | <span><a href="https://www.npmjs.com/package/@oku-ui/context-menu "><img src="https://img.shields.io/npm/v/@oku-ui/context-menu?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/context-menu"> <img src="https://img.shields.io/npm/dm/@oku-ui/context-menu?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/context-menu"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> |
| [Dialog](https://oku-ui.com/primitives/components/dialog) | <span><a href="https://www.npmjs.com/package/@oku-ui/dialog "><img src="https://img.shields.io/npm/v/@oku-ui/dialog?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/dialog"> <img src="https://img.shields.io/npm/dm/@oku-ui/dialog?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/dialog"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> |
| [Dropdown Menu](https://github.com/oku-ui/primitives/issues/10) | A menu that appears when a user interacts with an element's trigger | Not Started | - |
| [Form](https://github.com/oku-ui/primitives/issues/11) | A group of form controls | Not Started | - |
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@oku-ui/checkbox": "workspace:^",
"@oku-ui/collapsible": "workspace:^",
"@oku-ui/collection": "workspace:^",
"@oku-ui/context-menu": "workspace:^",
"@oku-ui/dialog": "workspace:^",
"@oku-ui/direction": "workspace:^",
"@oku-ui/dismissable-layer": "workspace:^",
Expand Down Expand Up @@ -134,6 +135,7 @@
"@oku-ui/checkbox": "workspace:^",
"@oku-ui/collapsible": "workspace:^",
"@oku-ui/collection": "workspace:^",
"@oku-ui/context-menu": "workspace:^",
"@oku-ui/dialog": "workspace:^",
"@oku-ui/direction": "workspace:^",
"@oku-ui/dismissable-layer": "workspace:^",
Expand Down
14 changes: 14 additions & 0 deletions packages/components/context-menu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Scroll Area
Displays a menu located at the pointer, triggered by a right-click or a long-press.

![@oku-ui/context-menu](./../../../.github/assets/og/oku-context-menu.jpg)

<span><a href="https://www.npmjs.com/package/@oku-ui/context-menu "><img src="https://img.shields.io/npm/v/@oku-ui/context-menu?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/context-menu"> <img src="https://img.shields.io/npm/dm/@oku-ui/context-menu?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/context-menu"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> </span>

## Installation

```sh
$ pnpm add @oku-ui/context-menu
```

[Documentation](https://oku-ui.com/primitives/components/context-menu)
7 changes: 7 additions & 0 deletions packages/components/context-menu/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineBuildConfig } from 'unbuild'

const isClean = (process.env.CLEAN || 'false') === 'true'
export default defineBuildConfig({
declaration: true,
clean: isClean,
})
54 changes: 54 additions & 0 deletions packages/components/context-menu/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@oku-ui/context-menu",
"type": "module",
"version": "0.5.0",
"license": "MIT",
"source": "src/index.ts",
"funding": "https://github.com/sponsors/productdevbook",
"homepage": "https://oku-ui.com/primitives",
"repository": {
"type": "git",
"url": "git+https://github.com/oku-ui/primitives.git",
"directory": "packages/components/context-menu"
},
"bugs": {
"url": "https://github.com/oku-ui/primitives/issues"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
}
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=18"
},
"scripts": {
"build": "unbuild",
"dev": "unbuild --stub",
"clean": "rimraf ./dist && rimraf ./node_modules"
},
"peerDependencies": {
"vue": "^3.3.0"
},
"dependencies": {
"@floating-ui/vue": "^1.0.2",
"@oku-ui/direction": "latest",
"@oku-ui/menu": "latest",
"@oku-ui/primitive": "latest",
"@oku-ui/provide": "latest",
"@oku-ui/use-composable": "latest",
"@oku-ui/utils": "latest"
},
"devDependencies": {
"tsconfig": "workspace:^"
},
"publishConfig": {
"access": "public"
}
}
41 changes: 41 additions & 0 deletions packages/components/context-menu/src/context-menu-arrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue'
import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable'
import { OkuMenuArrow } from '@oku-ui/menu'
import { CONTEXT_MENU_ARROW_NAME, contextMenuArrowProps, scopedContextMenuProps, useMenuScope } from './props'
import type { ContextMenuArrowNativeElement } from './props'

const contextMenuArrow = defineComponent({
name: CONTEXT_MENU_ARROW_NAME,
components: {
OkuMenuArrow,
},
inheritAttrs: false,
props: {
...contextMenuArrowProps.props,
...scopedContextMenuProps,
},
emits: contextMenuArrowProps.emits,
setup(props, { attrs, slots }) {
const {
scopeOkuContextMenu,
...arrowProps
} = toRefs(props)

const _other = reactive(arrowProps)
const otherProps = reactiveOmit(_other, (key, _value) => key === undefined)

const forwardedRef = useForwardRef()

const menuScope = useMenuScope(scopeOkuContextMenu.value)

return () => h(OkuMenuArrow, {
...menuScope,
...mergeProps(attrs, otherProps),
ref: forwardedRef,
}, slots)
},
})

// TODO: https://github.com/vuejs/core/pull/7444 after delete
export const OkuContextMenuArrow = contextMenuArrow as typeof contextMenuArrow &
(new () => { $props: ContextMenuArrowNativeElement })
42 changes: 42 additions & 0 deletions packages/components/context-menu/src/context-menu-checkbox-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue'
import { reactiveOmit, useForwardRef, useListeners } from '@oku-ui/use-composable'
import { OkuMenuCheckboxItem } from '@oku-ui/menu'
import { CONTEXT_MENU_CHECKBOX_ITEM_NAME, contextMenuCheckboxItemProps, scopedContextMenuProps, useMenuScope } from './props'
import type { ContextMenuCheckboxItemNativeElement } from './props'

const contextMenuCheckboxItem = defineComponent({
name: CONTEXT_MENU_CHECKBOX_ITEM_NAME,
components: {
OkuMenuCheckboxItem,
},
inheritAttrs: false,
props: {
...contextMenuCheckboxItemProps.props,
...scopedContextMenuProps,
},
emits: contextMenuCheckboxItemProps.emits,
setup(props, { attrs, slots }) {
const {
scopeOkuContextMenu,
...checkboxItemProps
} = toRefs(props)

const _other = reactive(checkboxItemProps)
const otherProps = reactiveOmit(_other, (key, _value) => key === undefined)

const forwardedRef = useForwardRef()
const emits = useListeners()

const menuScope = useMenuScope(scopeOkuContextMenu.value)

return () => h(OkuMenuCheckboxItem, {
...menuScope,
...mergeProps(attrs, otherProps, emits),
ref: forwardedRef,
}, slots)
},
})

// TODO: https://github.com/vuejs/core/pull/7444 after delete
export const OkuContextMenuCheckboxItem = contextMenuCheckboxItem as typeof contextMenuCheckboxItem &
(new () => { $props: ContextMenuCheckboxItemNativeElement })
69 changes: 69 additions & 0 deletions packages/components/context-menu/src/context-menu-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { defineComponent, h, mergeProps, reactive, ref, toRefs } from 'vue'
import { reactiveOmit, useForwardRef, useListeners } from '@oku-ui/use-composable'
import { OkuMenuContent } from '@oku-ui/menu'
import { CONTEXT_MENU_CONTENT_NAME, contextMenuContentProps, scopedContextMenuProps, useContextMenuInject, useMenuScope } from './props'

const contextMenuContent = defineComponent({
name: CONTEXT_MENU_CONTENT_NAME,
components: {
OkuMenuContent,
},
inheritAttrs: false,
props: {
...contextMenuContentProps.props,
...scopedContextMenuProps,
},
emits: contextMenuContentProps.emits,
setup(props, { attrs, emit, slots }) {
const {
scopeOkuContextMenu,
...contentProps
} = toRefs(props)

const _other = reactive(contentProps)
const otherProps = reactiveOmit(_other, (key, _value) => key === undefined)

const forwardedRef = useForwardRef()
const emits = useListeners()

const inject = useContextMenuInject(CONTEXT_MENU_CONTENT_NAME, scopeOkuContextMenu.value)
const menuScope = useMenuScope(scopeOkuContextMenu.value)
const hasInteractedOutsideRef = ref(false)

return () => h(OkuMenuContent, {
...menuScope,
...mergeProps(attrs, otherProps, emits),
ref: forwardedRef,
side: 'right',
sideOffset: 2,
align: 'start',
onCloseAutoFocus: (event) => {
emit('closeAutoFocus', event)

if (!event.defaultPrevented && hasInteractedOutsideRef.value)
event.preventDefault()

hasInteractedOutsideRef.value = false
},
onInteractOutside: (event) => {
emit('interactOutside', event)

if (!event.defaultPrevented && !inject.modal.value)
hasInteractedOutsideRef.value = true
},
style: {
...attrs.style as any,
// re-namespace exposed content custom properties
...{
'--oku-context-menu-content-transform-origin': 'var(--oku-popper-transform-origin)',
'--oku-context-menu-content-available-width': 'var(--oku-popper-available-width)',
'--oku-context-menu-content-available-height': 'var(--oku-popper-available-height)',
'--oku-context-menu-trigger-width': 'var(--oku-popper-anchor-width)',
'--oku-context-menu-trigger-height': 'var(--oku-popper-anchor-height)',
},
},
}, slots)
},
})

export const OkuContextMenuContent = contextMenuContent
41 changes: 41 additions & 0 deletions packages/components/context-menu/src/context-menu-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue'
import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable'
import { OkuMenuGroup } from '@oku-ui/menu'
import { CONTEXT_MENU_GROUP_NAME, contextMenuGroupProps, scopedContextMenuProps, useMenuScope } from './props'
import type { ContextMenuGroupNativeElement } from './props'

const contextMenuGroup = defineComponent({
name: CONTEXT_MENU_GROUP_NAME,
components: {
OkuMenuGroup,
},
inheritAttrs: false,
props: {
...contextMenuGroupProps.props,
...scopedContextMenuProps,
},
emits: contextMenuGroupProps.emits,
setup(props, { attrs, slots }) {
const {
scopeOkuContextMenu,
...groupProps
} = toRefs(props)

const _other = reactive(groupProps)
const otherProps = reactiveOmit(_other, (key, _value) => key === undefined)

const forwardedRef = useForwardRef()

const menuScope = useMenuScope(scopeOkuContextMenu.value)

return () => h(OkuMenuGroup, {
...menuScope,
...mergeProps(attrs, otherProps),
ref: forwardedRef,
}, slots)
},
})

// TODO: https://github.com/vuejs/core/pull/7444 after delete
export const OkuContextMenuGroup = contextMenuGroup as typeof contextMenuGroup &
(new () => { $props: ContextMenuGroupNativeElement })
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue'
import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable'
import { OkuMenuItemIndicator } from '@oku-ui/menu'
import { CONTEXT_MENU_ITEM_INDICATOR_NAME, contextMenuItemIndicatorProps, scopedContextMenuProps, useMenuScope } from './props'
import type { ContextMenuItemIndicatorNativeElement } from './props'

const contextMenuItemIndicator = defineComponent({
name: CONTEXT_MENU_ITEM_INDICATOR_NAME,
components: {
OkuMenuItemIndicator,
},
inheritAttrs: false,
props: {
...contextMenuItemIndicatorProps.props,
...scopedContextMenuProps,
},
emits: contextMenuItemIndicatorProps.emits,
setup(props, { attrs, slots }) {
const {
scopeOkuContextMenu,
...itemIndicatorProps
} = toRefs(props)

const _other = reactive(itemIndicatorProps)
const otherProps = reactiveOmit(_other, (key, _value) => key === undefined)

const forwardedRef = useForwardRef()

const menuScope = useMenuScope(scopeOkuContextMenu.value)

return () => h(OkuMenuItemIndicator, {
...menuScope,
...mergeProps(attrs, otherProps),
ref: forwardedRef,
}, slots)
},
})

// TODO: https://github.com/vuejs/core/pull/7444 after delete
export const OkuContextMenuItemIndicator = contextMenuItemIndicator as typeof contextMenuItemIndicator &
(new () => { $props: ContextMenuItemIndicatorNativeElement })
42 changes: 42 additions & 0 deletions packages/components/context-menu/src/context-menu-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { defineComponent, h, mergeProps, reactive, toRefs } from 'vue'
import { reactiveOmit, useForwardRef, useListeners } from '@oku-ui/use-composable'
import { OkuMenuItem } from '@oku-ui/menu'
import { CONTEXT_MENU_ITEM_NAME, contextMenuItemProps, scopedContextMenuProps, useMenuScope } from './props'
import type { ContextMenuItemNativeElement } from './props'

const contextMenuItem = defineComponent({
name: CONTEXT_MENU_ITEM_NAME,
components: {
OkuMenuItem,
},
inheritAttrs: false,
props: {
...contextMenuItemProps.props,
...scopedContextMenuProps,
},
emits: contextMenuItemProps.emits,
setup(props, { attrs, slots }) {
const {
scopeOkuContextMenu,
...itemProps
} = toRefs(props)

const _other = reactive(itemProps)
const otherProps = reactiveOmit(_other, (key, _value) => key === undefined)

const forwardedRef = useForwardRef()
const emits = useListeners()

const menuScope = useMenuScope(scopeOkuContextMenu.value)

return () => h(OkuMenuItem, {
...menuScope,
...mergeProps(attrs, otherProps, emits),
ref: forwardedRef,
}, slots)
},
})

// TODO: https://github.com/vuejs/core/pull/7444 after delete
export const OkuContextMenuItem = contextMenuItem as typeof contextMenuItem &
(new () => { $props: ContextMenuItemNativeElement })
Loading