@v1nt1248/3nclient-lib 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/components/index.d.ts +2 -1
- package/dist/components/ui3n-menu/types.d.ts +20 -0
- package/dist/components/{ui3n-menu.vue.d.ts → ui3n-menu/ui3n-menu.vue.d.ts} +4 -18
- package/dist/components/ui3n-notification.vue.d.ts +1 -1
- package/dist/components/ui3n-tooltip/types.d.ts +25 -0
- package/dist/index.d.ts +1 -1
- package/dist/style.css +1 -1
- package/dist/ui-3n-lib.js +10507 -9494
- package/package.json +2 -1
- package/src/components/index.ts +2 -2
- package/src/components/ui3n-menu/types.ts +23 -0
- package/src/components/ui3n-menu/ui3n-menu.vue +117 -0
- package/src/components/ui3n-table/types.ts +2 -2
- package/src/components/ui3n-table/ui3n-table.vue +1 -1
- package/src/components/ui3n-tooltip/types.ts +41 -0
- package/src/components/ui3n-tooltip/ui3n-tooltip.vue +199 -0
- package/src/components/ui3n-menu.vue +0 -136
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@v1nt1248/3nclient-lib",
|
|
3
3
|
"license": "AGPL-3.0-or-later",
|
|
4
4
|
"author": "v1nt1248",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.3",
|
|
6
6
|
"description": "Library for 3NWeb clients",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"files": [
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"docs:preview": "vitepress preview docs"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@floating-ui/vue": "^1.1.4",
|
|
37
38
|
"@iconify-icons/ic": "1.2.13",
|
|
38
39
|
"@iconify/vue": "4.1.2",
|
|
39
40
|
"dayjs": "1.11.12",
|
package/src/components/index.ts
CHANGED
|
@@ -40,8 +40,8 @@ import type {
|
|
|
40
40
|
Ui3nDialogProps,
|
|
41
41
|
Ui3nDialogEvent,
|
|
42
42
|
} from './ui3n-dialog.vue';
|
|
43
|
-
import Ui3nMenu from './ui3n-menu.vue';
|
|
44
|
-
import type { Ui3nMenuProps, Ui3nMenuEmits, Ui3nMenuSlots } from './ui3n-menu
|
|
43
|
+
import Ui3nMenu from './ui3n-menu/ui3n-menu.vue';
|
|
44
|
+
import type { Ui3nMenuProps, Ui3nMenuEmits, Ui3nMenuSlots } from './ui3n-menu/types';
|
|
45
45
|
import Ui3nList from './ui3n-list.vue';
|
|
46
46
|
import type { Ui3nListProps, Ui3nListEmits, Ui3nListSlots } from './ui3n-list.vue';
|
|
47
47
|
import Ui3nVirtualScroll from './ui3n-virtual-scroll.vue';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { VNode } from 'vue';
|
|
2
|
+
|
|
3
|
+
export interface Ui3nMenuProps {
|
|
4
|
+
positionStrategy?: 'absolute' | 'fixed';
|
|
5
|
+
offsetX?: number;
|
|
6
|
+
offsetY?: number;
|
|
7
|
+
closeOnClick?: boolean;
|
|
8
|
+
closeOnClickOutside?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Ui3nMenuEmits {
|
|
13
|
+
(ev: 'open'): void;
|
|
14
|
+
(ev: 'opened'): void;
|
|
15
|
+
(ev: 'close'): void;
|
|
16
|
+
(ev: 'closed'): void;
|
|
17
|
+
(ev: 'click-outside'): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Ui3nMenuSlots {
|
|
21
|
+
default: () => VNode;
|
|
22
|
+
menu?: () => VNode;
|
|
23
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, watch } from 'vue';
|
|
3
|
+
import { autoUpdate, flip, useFloating, offset, shift } from '@floating-ui/vue';
|
|
4
|
+
import { default as vClickOutside } from '../../directives/ui3n-click-outside';
|
|
5
|
+
import type { Ui3nMenuEmits, Ui3nMenuProps, Ui3nMenuSlots } from './types';
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<Ui3nMenuProps>(), {
|
|
8
|
+
positionStrategy: 'absolute',
|
|
9
|
+
offsetX: 0,
|
|
10
|
+
offsetY: 0,
|
|
11
|
+
closeOnClick: true,
|
|
12
|
+
closeOnClickOutside: true,
|
|
13
|
+
disabled: false,
|
|
14
|
+
});
|
|
15
|
+
const emits = defineEmits<Ui3nMenuEmits>();
|
|
16
|
+
defineSlots<Ui3nMenuSlots>();
|
|
17
|
+
|
|
18
|
+
const menuElement = ref<HTMLDivElement | null>(null);
|
|
19
|
+
const menuTriggerElement = ref<HTMLDivElement | null>(null);
|
|
20
|
+
const menuContentElement = ref<HTMLDivElement | null>(null);
|
|
21
|
+
const isShow = ref(false);
|
|
22
|
+
|
|
23
|
+
const { floatingStyles, isPositioned } = useFloating(
|
|
24
|
+
menuTriggerElement,
|
|
25
|
+
menuContentElement,
|
|
26
|
+
{
|
|
27
|
+
placement: 'bottom-start',
|
|
28
|
+
strategy: props.positionStrategy,
|
|
29
|
+
middleware: [
|
|
30
|
+
offset({
|
|
31
|
+
mainAxis: props.offsetY,
|
|
32
|
+
crossAxis: props.offsetX + 2,
|
|
33
|
+
}),
|
|
34
|
+
flip({
|
|
35
|
+
fallbackAxisSideDirection: 'end',
|
|
36
|
+
flipAlignment: false,
|
|
37
|
+
fallbackPlacements: ['bottom-end'],
|
|
38
|
+
}),
|
|
39
|
+
shift(),
|
|
40
|
+
],
|
|
41
|
+
whileElementsMounted: props.positionStrategy === 'fixed' ? autoUpdate : undefined,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function toggleMenu() {
|
|
46
|
+
isShow.value = !isShow.value;
|
|
47
|
+
isShow.value ? emits('open') : emits('close');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function onContentClick() {
|
|
51
|
+
isShow.value = false;
|
|
52
|
+
emits('close');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function onClickOutside() {
|
|
56
|
+
emits('click-outside');
|
|
57
|
+
if (props.closeOnClickOutside) {
|
|
58
|
+
isShow.value = false;
|
|
59
|
+
emits('close');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
watch(
|
|
64
|
+
isPositioned,
|
|
65
|
+
(val) => {
|
|
66
|
+
val ? emits('opened') : emits('closed');
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<div ref="menuElement" :class="$style.menu">
|
|
73
|
+
<div
|
|
74
|
+
ref="menuTriggerElement"
|
|
75
|
+
:class="$style.trigger"
|
|
76
|
+
@click.stop="toggleMenu"
|
|
77
|
+
>
|
|
78
|
+
<slot />
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div
|
|
82
|
+
v-if="isShow"
|
|
83
|
+
ref="menuContentElement"
|
|
84
|
+
v-click-outside="onClickOutside"
|
|
85
|
+
:style="floatingStyles"
|
|
86
|
+
:class="$style.content"
|
|
87
|
+
v-on="props.closeOnClick ? { click: onContentClick } : {}"
|
|
88
|
+
>
|
|
89
|
+
<slot name="menu" />
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<style lang="scss" module>
|
|
95
|
+
@import "../../assets/styles/mixins";
|
|
96
|
+
|
|
97
|
+
.menu {
|
|
98
|
+
--ui3n-menu-content-bg: var(--color-bg-control-secondary-default);
|
|
99
|
+
|
|
100
|
+
position: relative;
|
|
101
|
+
max-width: max-content;
|
|
102
|
+
overflow: visible;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.trigger {
|
|
106
|
+
position: relative;
|
|
107
|
+
max-width: max-content;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.content {
|
|
111
|
+
position: absolute;
|
|
112
|
+
border-radius: 4px;
|
|
113
|
+
background-color: var(--ui3n-menu-content-bg);
|
|
114
|
+
z-index: 1000;
|
|
115
|
+
@include elevation(3);
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -13,8 +13,8 @@ export type Ui3nTableConfig<T extends Ui3nTableBodyBaseItem, K extends keyof T =
|
|
|
13
13
|
tableName?: string;
|
|
14
14
|
sortOrder?: Ui3nTableSort<T>;
|
|
15
15
|
selectable?: 'single' | 'multiple';
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// draggableRows?: boolean; // ToDo this will be done in the next version
|
|
17
|
+
// draggableColumns?: boolean; // ToDo this will be done in the next version
|
|
18
18
|
columnStyle?: { [P in Omit<K, 'id'> as string | number]: Record<string, string> };
|
|
19
19
|
fieldAsRowKey?: keyof T;
|
|
20
20
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { VNode } from 'vue';
|
|
2
|
+
|
|
3
|
+
export type Ui3nTooltipPlacement =
|
|
4
|
+
| 'top'
|
|
5
|
+
| 'top-start'
|
|
6
|
+
| 'top-end'
|
|
7
|
+
| 'bottom'
|
|
8
|
+
| 'bottom-start'
|
|
9
|
+
| 'bottom-end'
|
|
10
|
+
| 'right'
|
|
11
|
+
| 'right-start'
|
|
12
|
+
| 'right-end'
|
|
13
|
+
| 'left'
|
|
14
|
+
| 'left-start'
|
|
15
|
+
| 'left-end';
|
|
16
|
+
|
|
17
|
+
export interface Ui3nTooltipProps {
|
|
18
|
+
modelValue?: boolean;
|
|
19
|
+
content?: string;
|
|
20
|
+
color?: string;
|
|
21
|
+
textColor?: string;
|
|
22
|
+
placement?: Ui3nTooltipPlacement;
|
|
23
|
+
positionStrategy?: 'absolute' | 'fixed';
|
|
24
|
+
offsetX?: string | number;
|
|
25
|
+
offsetY?: string | number;
|
|
26
|
+
trigger?: 'click' | 'hover' | 'manual';
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Ui3nTooltipEmits {
|
|
31
|
+
(ev: 'open'): void;
|
|
32
|
+
(ev: 'opened'): void;
|
|
33
|
+
(ev: 'close'): void;
|
|
34
|
+
(ev: 'closed'): void;
|
|
35
|
+
(ev: 'update:modelValue', value: boolean): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Ui3nTooltipSlots {
|
|
39
|
+
default: () => VNode;
|
|
40
|
+
content?: () => VNode;
|
|
41
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed, ref, watch } from 'vue';
|
|
3
|
+
import { useFloating, offset, autoUpdate, arrow } from '@floating-ui/vue';
|
|
4
|
+
import type { Ui3nTooltipEmits, Ui3nTooltipProps, Ui3nTooltipSlots } from './types';
|
|
5
|
+
|
|
6
|
+
const baseOffset = 5;
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<Ui3nTooltipProps>(),
|
|
10
|
+
{
|
|
11
|
+
color: 'var(--color-bg-control-secondary-default)',
|
|
12
|
+
textColor: 'var(--color-text-control-primary-default)',
|
|
13
|
+
placement: 'top',
|
|
14
|
+
positionStrategy: 'absolute',
|
|
15
|
+
offsetX: 0,
|
|
16
|
+
offsetY: 0,
|
|
17
|
+
trigger: 'hover',
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const emits = defineEmits<Ui3nTooltipEmits>();
|
|
22
|
+
defineSlots<Ui3nTooltipSlots>();
|
|
23
|
+
|
|
24
|
+
const referenceElEventHandler = {
|
|
25
|
+
...(props.trigger === 'hover' && !props.disabled && {
|
|
26
|
+
mouseenter: () => handleMouseEvents(true),
|
|
27
|
+
mouseleave: () => handleMouseEvents(false),
|
|
28
|
+
}),
|
|
29
|
+
...(props.trigger === 'click' && !props.disabled && {
|
|
30
|
+
click: () => handleMouseEvents(!showTooltip.value),
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const showTooltip = ref(false);
|
|
35
|
+
const referenceEl = ref(null);
|
|
36
|
+
const floatingEl = ref(null);
|
|
37
|
+
const floatingArrowEl = ref(null);
|
|
38
|
+
|
|
39
|
+
const baseOffsetCssValue = computed(() => `${-baseOffset}px`);
|
|
40
|
+
const arrowSizeCssValue = computed(() => `${baseOffset}px`);
|
|
41
|
+
const mainPlacement = computed(() => {
|
|
42
|
+
const [part1, _] = props.placement.split('-');
|
|
43
|
+
return part1;
|
|
44
|
+
});
|
|
45
|
+
const offsetOptions = computed(() => {
|
|
46
|
+
const options = {
|
|
47
|
+
mainAxis: 0,
|
|
48
|
+
crossAxis: 0,
|
|
49
|
+
};
|
|
50
|
+
switch (mainPlacement.value) {
|
|
51
|
+
case 'top':
|
|
52
|
+
options.mainAxis = -1 * Number(props.offsetY) + baseOffset;
|
|
53
|
+
options.crossAxis = Number(props.offsetX);
|
|
54
|
+
break;
|
|
55
|
+
case 'bottom':
|
|
56
|
+
options.mainAxis = Number(props.offsetY) + baseOffset;
|
|
57
|
+
options.crossAxis = Number(props.offsetX);
|
|
58
|
+
break;
|
|
59
|
+
case 'left':
|
|
60
|
+
options.mainAxis = -1 * Number(props.offsetX) + baseOffset;
|
|
61
|
+
options.crossAxis = Number(props.offsetY);
|
|
62
|
+
break;
|
|
63
|
+
case 'right':
|
|
64
|
+
options.mainAxis = Number(props.offsetX) + baseOffset;
|
|
65
|
+
options.crossAxis = Number(props.offsetY);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
return options;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const { floatingStyles, isPositioned, middlewareData } = useFloating(referenceEl, floatingEl, {
|
|
72
|
+
open: showTooltip,
|
|
73
|
+
placement: props.placement,
|
|
74
|
+
strategy: props.positionStrategy,
|
|
75
|
+
middleware: [
|
|
76
|
+
offset(offsetOptions.value),
|
|
77
|
+
arrow({ element: floatingArrowEl, padding: 8 }),
|
|
78
|
+
],
|
|
79
|
+
whileElementsMounted: props.positionStrategy === 'fixed' ? autoUpdate : undefined,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function handleMouseEvents(val: boolean) {
|
|
83
|
+
showTooltip.value = val;
|
|
84
|
+
val ? emits('open') : emits('close');
|
|
85
|
+
emits('update:modelValue', val);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
watch(
|
|
89
|
+
() => props.modelValue,
|
|
90
|
+
(val) => props.trigger === 'manual' && (showTooltip.value = val),
|
|
91
|
+
{ immediate: true },
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
watch(
|
|
95
|
+
isPositioned,
|
|
96
|
+
(val) => {
|
|
97
|
+
val ? emits('opened') : emits('closed');
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<template>
|
|
103
|
+
<div :class="$style.tooltip">
|
|
104
|
+
<div
|
|
105
|
+
ref="referenceEl"
|
|
106
|
+
:class="$style.reference"
|
|
107
|
+
v-on="referenceElEventHandler"
|
|
108
|
+
>
|
|
109
|
+
<slot name="default" />
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div ref="floatingEl" :class="$style.floating" :style="floatingStyles" v-if="showTooltip">
|
|
113
|
+
<slot name="content">
|
|
114
|
+
<div :class="$style.content">
|
|
115
|
+
{{ props.content }}
|
|
116
|
+
<div
|
|
117
|
+
ref="floatingArrowEl"
|
|
118
|
+
:class="[$style.arrow, $style[`arrow-${mainPlacement}`]]"
|
|
119
|
+
:style="{
|
|
120
|
+
left: middlewareData.arrow?.x != null ? `${middlewareData.arrow?.x}px` : '',
|
|
121
|
+
top: middlewareData.arrow?.y != null ? `${middlewareData.arrow?.y}px` : '',
|
|
122
|
+
}"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
</slot>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<style lang="scss" module>
|
|
131
|
+
.tooltip {
|
|
132
|
+
--ui3n-tooltip-bg-color: v-bind(color);
|
|
133
|
+
--ui3n-tooltip-text-color: v-bind(textColor);
|
|
134
|
+
|
|
135
|
+
position: relative;
|
|
136
|
+
width: max-content;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.reference {
|
|
140
|
+
position: relative;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.floating {
|
|
144
|
+
width: max-content;
|
|
145
|
+
z-index: 5;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.arrow {
|
|
149
|
+
position: absolute;
|
|
150
|
+
width: 0;
|
|
151
|
+
height: 0;
|
|
152
|
+
|
|
153
|
+
&-top,
|
|
154
|
+
&-bottom {
|
|
155
|
+
border-style: solid;
|
|
156
|
+
border-width: 0 v-bind(arrowSizeCssValue) v-bind(arrowSizeCssValue) v-bind(arrowSizeCssValue);
|
|
157
|
+
border-color: transparent transparent var(--ui3n-tooltip-bg-color) transparent;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&-top {
|
|
161
|
+
bottom: v-bind(baseOffsetCssValue);
|
|
162
|
+
transform: rotate(180deg);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
&-bottom {
|
|
166
|
+
top: v-bind(baseOffsetCssValue);
|
|
167
|
+
transform: rotate(0deg);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&-left,
|
|
171
|
+
&-right {
|
|
172
|
+
border-style: solid;
|
|
173
|
+
border-width: v-bind(arrowSizeCssValue) 0 v-bind(arrowSizeCssValue) v-bind(arrowSizeCssValue);
|
|
174
|
+
border-color: transparent transparent transparent var(--ui3n-tooltip-bg-color);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
&-left {
|
|
178
|
+
right: v-bind(baseOffsetCssValue);
|
|
179
|
+
transform: rotate(0deg);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
&-right {
|
|
183
|
+
left: v-bind(baseOffsetCssValue);
|
|
184
|
+
transform: rotate(180deg);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.content {
|
|
189
|
+
position: relative;
|
|
190
|
+
max-width: 400px;
|
|
191
|
+
padding: var(--spacing-xs) var(--spacing-s);
|
|
192
|
+
border-radius: var(--spacing-xs);
|
|
193
|
+
font-size: var(--font-11);
|
|
194
|
+
line-height: var(--font-12);
|
|
195
|
+
font-weight: 400;
|
|
196
|
+
background-color: var(--ui3n-tooltip-bg-color);
|
|
197
|
+
color: var(--ui3n-tooltip-text-color);
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
import { computed, onMounted, ref } from 'vue';
|
|
4
|
-
import { default as vClickOutside } from '../directives/ui3n-click-outside';
|
|
5
|
-
|
|
6
|
-
export interface Ui3nMenuProps {
|
|
7
|
-
offsetX?: number;
|
|
8
|
-
offsetY?: number;
|
|
9
|
-
closeOnClick?: boolean;
|
|
10
|
-
closeOnClickOutside?: boolean;
|
|
11
|
-
disabled?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface Ui3nMenuEmits {
|
|
15
|
-
(ev: 'open'): void;
|
|
16
|
-
(ev: 'opened'): void;
|
|
17
|
-
(ev: 'close'): void;
|
|
18
|
-
(ev: 'closed'): void;
|
|
19
|
-
(ev: 'click-outside'): void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface Ui3nMenuSlots {
|
|
23
|
-
default: () => any;
|
|
24
|
-
menu?: () => any;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const props = withDefaults(defineProps<Ui3nMenuProps>(), {
|
|
28
|
-
offsetX: 0,
|
|
29
|
-
offsetY: 0,
|
|
30
|
-
closeOnClick: true,
|
|
31
|
-
closeOnClickOutside: true,
|
|
32
|
-
disabled: false,
|
|
33
|
-
});
|
|
34
|
-
const emits = defineEmits<Ui3nMenuEmits>();
|
|
35
|
-
defineSlots<Ui3nMenuSlots>();
|
|
36
|
-
|
|
37
|
-
const menuElement = ref<HTMLDivElement | null>(null);
|
|
38
|
-
const menuTriggerElement = ref<HTMLDivElement | null>(null);
|
|
39
|
-
const isShow = ref(false);
|
|
40
|
-
const defaultAnimationDuration = 400;
|
|
41
|
-
const menuContentStyle = computed(() => {
|
|
42
|
-
if (!props.offsetX && !props.offsetY) {
|
|
43
|
-
return {};
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
...(props.offsetX && { left: `${props.offsetX}px` }),
|
|
47
|
-
...(props.offsetY && { top: `${menuTriggerElement.value!.clientHeight + props.offsetY}px` }),
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
onMounted(() => {
|
|
52
|
-
menuElement.value!.style.setProperty('--ui3n-menu-animation-duration', `${defaultAnimationDuration}ms`);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
function toggleMenu() {
|
|
56
|
-
isShow.value = !isShow.value;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function onContentClick() {
|
|
60
|
-
isShow.value = false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function onClickOutside() {
|
|
64
|
-
emits('click-outside');
|
|
65
|
-
if (props.closeOnClickOutside) {
|
|
66
|
-
isShow.value = false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
</script>
|
|
70
|
-
|
|
71
|
-
<template>
|
|
72
|
-
<div ref="menuElement" :class="$style.menu">
|
|
73
|
-
<div
|
|
74
|
-
ref="menuTriggerElement"
|
|
75
|
-
:class="$style.trigger"
|
|
76
|
-
@click.stop="toggleMenu"
|
|
77
|
-
>
|
|
78
|
-
<slot />
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<transition
|
|
82
|
-
name="menu"
|
|
83
|
-
:duration="defaultAnimationDuration"
|
|
84
|
-
@before-enter="emits('open')"
|
|
85
|
-
@after-enter="emits('opened')"
|
|
86
|
-
@before-leave="emits('close')"
|
|
87
|
-
@after-leave="emits('closed')"
|
|
88
|
-
>
|
|
89
|
-
<div
|
|
90
|
-
v-if="isShow"
|
|
91
|
-
v-click-outside="onClickOutside"
|
|
92
|
-
:style="menuContentStyle"
|
|
93
|
-
:class="$style.content"
|
|
94
|
-
v-on="props.closeOnClick ? { click: onContentClick } : {}"
|
|
95
|
-
>
|
|
96
|
-
<slot name="menu" />
|
|
97
|
-
</div>
|
|
98
|
-
</transition>
|
|
99
|
-
</div>
|
|
100
|
-
</template>
|
|
101
|
-
|
|
102
|
-
<style lang="scss" module>
|
|
103
|
-
@import "../assets/styles/mixins";
|
|
104
|
-
|
|
105
|
-
.menu {
|
|
106
|
-
--ui3n-menu-animation-duration: 400ms;
|
|
107
|
-
--ui3n-menu-content-bg: var(--color-bg-control-secondary-default);
|
|
108
|
-
|
|
109
|
-
position: relative;
|
|
110
|
-
overflow: visible;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.trigger {
|
|
114
|
-
position: relative;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.content {
|
|
118
|
-
position: absolute;
|
|
119
|
-
border-radius: 4px;
|
|
120
|
-
background-color: var(--ui3n-menu-content-bg);
|
|
121
|
-
z-index: 1000;
|
|
122
|
-
@include elevation(3);
|
|
123
|
-
}
|
|
124
|
-
</style>
|
|
125
|
-
|
|
126
|
-
<style lang="scss" scoped>
|
|
127
|
-
.menu-enter-active,
|
|
128
|
-
.menu-leave-active {
|
|
129
|
-
transition: opacity var(--ui3n-menu-animation-duration);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.menu-enter-from,
|
|
133
|
-
.menu-leave-to {
|
|
134
|
-
opacity: 0;
|
|
135
|
-
}
|
|
136
|
-
</style>
|