Skip to content

Commit

Permalink
[#706][3.0] ContextMenu 컴포넌트 개발 (#713)
Browse files Browse the repository at this point in the history
* [#706][3.0] Context Menu 컴포넌트 개발
################
- 컨텍스트메뉴의 템플릿 구조를 body에 teleport 시키는 래핑 컴포넌트와 실제 보여지는 항목 UL, LI로 구성된 메뉴리스트 컴포넌트 분리 개발
- nested 구조인 경우 자식을 recusive하게 호출하여 DOM의 depth가 깊어지는 구조
- 모듈 리펙토링 및 JSDoc 작성
- DOCS context menu 예제 및 설명MD 작성
- DOCS context menu 메뉴 router 추가
- DOCS 메인 화면 구조 변경

* [#706][3.0] Context Menu 컴포넌트 개발
################
- props의 validator를 추가하여 click속성은 function type으로 children속성은 [] type으로 받을 수 있도록 함
  • Loading branch information
kdeun1 authored Nov 13, 2020
1 parent f29a430 commit a6eed9a
Show file tree
Hide file tree
Showing 12 changed files with 673 additions and 14 deletions.
9 changes: 5 additions & 4 deletions docs/components/Content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ export default {
@import '../style/index.scss';
.evui-content {
position: fixed;
width: calc(100% - 320px);
height: calc(100% - 60px);
overflow: auto;
height: 100%;
padding: 30px 40px;
//@include themify() {
// background-color: themed('background-color-base');
//}
}
</style>
1 change: 1 addition & 0 deletions docs/components/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export default {
@include themify() {
border-right: 1px solid themed('border-color-base');
background-color: themed('background-color-base');
}
ul, li {
list-style: none;
Expand Down
4 changes: 3 additions & 1 deletion docs/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import calendarProps from 'docs/views/calendar/props';
import messageProps from 'docs/views/message/props';
import messageBoxProps from 'docs/views/messageBox/props';
import notificationProps from 'docs/views/notification/props';
import contextMenuProps from 'docs/views/contextMenu/props';
import schedulerProps from 'docs/views/scheduler/props';

const routes = [
Expand All @@ -31,7 +32,8 @@ const routes = [
{
path: '/contextMenu',
name: 'ContextMenu',
component: () => import(/* webpackChunkName: "contextMenu" */ '../views/contextMenu'),
component: PageView,
props: contextMenuProps,
},
{
path: '/button',
Expand Down
41 changes: 41 additions & 0 deletions docs/views/contextMenu/api/contextMenu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
### Desc
- 태그는 &lt;ev-context-menu&gt;(이하 <컨텍스트메뉴>)으로 정의

```
<ev-context-menu
ref="REF명"
:items="[
{
text: '텍스트명',
cls: '텍스트 앞에 위치하는 아이콘 태그 클래스명',
disabled: true, // Boolean - 사용여부
click: () => { ... }, // 클릭 시 발생하는 메소드
},
{ ... },
{ ... },
]"
/>
```
- <컨텍스트메뉴>는 특정 영역에 우클릭이벤트(@contextmenu="REF명.show")에서 실행 가능
- 메뉴 값에 바인딩되는 items는 nested 구조
- 텍스트명, 아이콘, 항목 사용여부, 클릭 이벤트 바인딩 가능
- 클릭 시 item.click이 실행되며 <컨텍스트메뉴>전체가 hide
- nested 구조의 항목에서 같은 레벨의 항목에 mouseenter를 하였을 때, 해당 레벨의 자식 컴포넌트가 숨김처리됨


### Binding

| 이름 | 타입 | 디폴트 | 설명 | 종류 |
| --- | ---- | ----- | ---- | --- |
| ref | String | '' | <컨텍스트메뉴>의 REF명을 지정해줘야한다. 필수 | |

### Props

| 이름 | 타입 | 디폴트 | 설명 | 종류 |
| --- | ---- | ----- | ---- | --- |
| items | Array | [] | <컨텍스트메뉴>의 목록. nested 가능 | |
| | {} | | <컨텍스트메뉴>의 하나의 항목 | |
| | | text | <컨텍스트메뉴> 항목 텍스트 | |
| | | cls | <컨텍스트메뉴> 항목 텍스트 앞에 위치하는 <i> 클래스 | |
| | | disabled | <컨텍스트메뉴> 항목 사용 여부 | false, true |
| | | click | <컨텍스트메뉴> 항목 클릭 시 발생하는 메소드 | |
171 changes: 171 additions & 0 deletions docs/views/contextMenu/example/Default.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<template>
<div class="case">
<p class="case-title">Common</p>
<div
class="sample-context"
@contextmenu.prevent="openContextMenu"
>
컨텍스트 메뉴 우클릭 영역
<ev-context-menu
ref="menu"
:items="menuItems"
/>
</div>
<div class="description">
<span class="badge">
CLICK RIGHT
</span>
</div>

</div>
<div class="case">
<p class="case-title">Custom</p>
<div
class="sample-context"
@contextmenu.prevent="menu2.show"
>
<ev-context-menu
ref="menu2"
:items="menuItems2"
/>
컨텍스트 메뉴 우클릭 영역
</div>
<div class="description">
<span
class="badge"
@click="addChild"
>
ADD CHILD
</span>
</div>
</div>
</template>

<script>
import { ref } from 'vue';
export default {
setup() {
const menu = ref(null);
const menuItems = ref([
{
text: 11111,
cls: 'ev-icon-s-panel-out',
click: () => console.log('CLICK text1'),
},
{
text: 'TEXT22222222222222',
cls: 'ev-icon-s-pause',
children: [
{
text: 'TEXT2-111111111111111',
cls: 'ev-icon-server2',
click: () => console.log('CLICK TEXT2-111111111111111'),
},
{
text: 'TEXT2-2',
value: 'value22',
cls: 'ev-icon-server',
},
{
text: 'TEXT2-3',
cls: 'ev-icon-compress',
children: [
{
text: 'TEXT2-3-1',
cls: 'ev-icon-bell-warning',
click: () => console.log('CLICK TEXT2-3-1'),
},
{
text: 'TEXT2-3-2',
cls: 'ev-icon-expand2',
},
],
},
],
},
{
text: 'TEXT3',
click: () => console.log('CLICK TEXT3'),
},
{
text: 'TEXT4',
click: () => console.log('CLICK TEXT4'),
},
{
text: 'TEXT5',
children: [
{
text: 'TEXT5-1',
click: () => console.log('CLICK TEXT5-1'),
},
{
text: 'TEXT5-2',
cls: 'ev-icon-expand',
},
],
},
]);
const openContextMenu = (e) => {
menu.value.show(e);
};
const menu2 = ref(null);
const menuItems2 = ref([
{
text: 'TEXT1',
cls: 'ev-icon-s-panel-out',
disabled: true,
click: () => console.log('CLICK text1'),
},
{
text: 'TEXT2',
disabled: true,
cls: 'ev-icon-s-pause',
children: [
{
text: 'TEXT2-1',
cls: 'ev-icon-server2',
click: () => console.log('CLICK text2-1'),
},
{
text: 'TEXT2-2',
disabled: true,
cls: 'ev-icon-server',
},
],
},
{
text: 'TEXT3',
},
]);
const addChild = () => {
menuItems2.value.push({
text: 'TEXT4',
cls: 'ev-icon-bell-warning',
click: () => { console.log('CLICK TEXT4'); },
});
};
return {
menu,
menuItems,
openContextMenu,
menu2,
menuItems2,
addChild,
};
},
};
</script>

<style>
.sample-context {
width: 400px;
height: 400px;
max-width: 400px;
max-height: 400px;
background: #8D99A7;
}
</style>
9 changes: 0 additions & 9 deletions docs/views/contextMenu/index.vue

This file was deleted.

15 changes: 15 additions & 0 deletions docs/views/contextMenu/props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { parseComponent } from 'vue-template-compiler';
import mdText from '!!raw-loader!./api/contextMenu.md';
import Default from './example/Default';
import DefaultRaw from '!!raw-loader!./example/Default';

export default {
mdText,
components: {
Default: {
description: '영역 내 우클릭하여 컨텍스트 메뉴를 열 수 있습니다.',
component: Default,
parsedData: parseComponent(DefaultRaw),
},
},
};
77 changes: 77 additions & 0 deletions src/components/contextMenu/ContextMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<template v-if="isShow && items.length">
<teleport to="#ev-context-menu">
<menu-list
ref="rootMenuList"
v-model:isShow="isShow"
v-clickoutside="hide"
:items="items"
:style="menuStyle"
:comp="comp"
/>
</teleport>
</template>
</template>

<script>
import { onBeforeMount } from 'vue';
import { clickoutside } from '@/directives/clickoutside';
import MenuList from './MenuList';
import { useModel, usePosition } from './uses';
export default {
name: 'EvContextMenu',
directives: {
clickoutside,
},
components: {
MenuList,
},
props: {
items: {
type: Array,
default: () => [],
validator: (list) => {
if (list.filter(v => v.children).some(v => !Array.isArray(v.children))) {
console.warn('[EVUI][ContextMenu] children attribute must be \'Array\' type.');
return false;
} else if (list.filter(v => v.click).some(v => typeof v.click !== 'function')) {
console.warn('[EVUI][ContextMenu] click attribute must be \'Function\' type.');
return false;
}
return true;
},
},
},
emits: {
},
setup() {
const {
comp,
initWrapperDiv,
} = useModel();
const {
isShow,
rootMenuList,
menuStyle,
show,
hide,
} = usePosition();
onBeforeMount(() => initWrapperDiv());
return {
isShow,
rootMenuList,
menuStyle,
comp,
show,
hide,
};
},
};
</script>

<style lang="scss">
</style>
Loading

0 comments on commit a6eed9a

Please sign in to comment.