edvoyui-component-library-test-flight 0.0.168 → 0.0.170
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/package.json +10 -3
- package/src/App.vue +0 -16
- package/src/assets/svg/CheckTick.vue +0 -21
- package/src/assets/svg/ChevronBigDown.vue +0 -22
- package/src/assets/svg/ChevronDownSolid.vue +0 -19
- package/src/assets/svg/ChevronDownStroke.vue +0 -22
- package/src/assets/svg/ChevronDownStrokeSolid.vue +0 -19
- package/src/assets/svg/SearchBigZoomIn.vue +0 -21
- package/src/assets/svg/SortArrow.vue +0 -24
- package/src/assets/svg/Student.vue +0 -30
- package/src/assets/svg/partner.vue +0 -33
- package/src/assets/svg/people.vue +0 -25
- package/src/components/HelloWorld.vue +0 -1974
- package/src/components/accordion/EUIAccordion.vue +0 -152
- package/src/components/alerts/EUIAlerts.vue +0 -194
- package/src/components/avatar/EUIAvatar.vue +0 -96
- package/src/components/breadcrumb/EUIBreadcrumb.vue +0 -59
- package/src/components/button/EUIButton.vue +0 -154
- package/src/components/button/EUIButtonGroup.vue +0 -287
- package/src/components/button/buttonAnimateTab.vue +0 -74
- package/src/components/checkbox/EUICheckbox.vue +0 -110
- package/src/components/datepicker/EUIDatepicker.vue +0 -295
- package/src/components/delete.vue +0 -262
- package/src/components/dragModal/EUIDrag.vue +0 -179
- package/src/components/dropdown/EUIMultiDropdown.vue +0 -174
- package/src/components/errorMessage/EUIErrorMessage.vue +0 -25
- package/src/components/input/EUIInput.vue +0 -223
- package/src/components/input/EUINumberInput.vue +0 -250
- package/src/components/loader/EUICircleLoader.vue +0 -31
- package/src/components/loader/EUICubeLoader.vue +0 -237
- package/src/components/loader/EUILoader.vue +0 -17
- package/src/components/loader/EUISquareLoader.vue +0 -47
- package/src/components/modal/EUIModal.vue +0 -224
- package/src/components/pillSelect/EUIPillSelect.vue +0 -149
- package/src/components/popover/EUIPopover.vue +0 -297
- package/src/components/radio/EUIRadio.vue +0 -75
- package/src/components/searchInput/EUISearch.vue +0 -223
- package/src/components/searchTagSelect/EUISearchTagSelect.vue +0 -642
- package/src/components/searchTagSelect/SearchInput.vue +0 -167
- package/src/components/searchexpand/EUISearchExpand.vue +0 -148
- package/src/components/searchexpand/EUISearchToggle.vue +0 -86
- package/src/components/select/EUISelect.vue +0 -1092
- package/src/components/selectSearch/EUISelectSearch.vue +0 -23
- package/src/components/slideover/EUISlideover.vue +0 -207
- package/src/components/stepperTimeline/EUIStepperHorizontal.vue +0 -242
- package/src/components/stepperTimeline/EUIStepperTimeline.vue +0 -16
- package/src/components/stepperTimeline/EUIStepperVertical.vue +0 -112
- package/src/components/table/ColumnResizeTable.vue +0 -740
- package/src/components/table/EUIDashboardTable.vue +0 -514
- package/src/components/table/EUIPageLimit.vue +0 -66
- package/src/components/table/EUIPagination.vue +0 -175
- package/src/components/table/EUIStudentPagination.vue +0 -172
- package/src/components/table/EUITable.vue +0 -559
- package/src/components/table/EUITableCheckbox.vue +0 -98
- package/src/components/table/GrowthTable.vue +0 -575
- package/src/components/table/GrowthTableView.vue +0 -108
- package/src/components/table/ResizeTableview.vue +0 -198
- package/src/components/table/UCheckbox.vue +0 -169
- package/src/components/table/UTable.vue +0 -611
- package/src/components/table/UTableview.vue +0 -189
- package/src/components/tabs/EUITabOutline.vue +0 -263
- package/src/components/tabs/EUITabs.vue +0 -226
- package/src/components/tag/EUITag.vue +0 -88
- package/src/components/telephone/EUITelephone.vue +0 -299
- package/src/components/textArea/EUITextArea.vue +0 -155
- package/src/components/timeLine/EUITimeLine.vue +0 -148
- package/src/components/toggle/EUIToggle.vue +0 -101
- package/src/components/tooltip/EUITooltip.vue +0 -111
- package/src/components/uidemo/select-com.vue +0 -120
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Teleport defer to="body">
|
|
3
|
-
<Transition name="modal" appear>
|
|
4
|
-
<div
|
|
5
|
-
v-if="isVisible"
|
|
6
|
-
class="fixed z-50 flex flex-col items-center justify-end inset-2 modal-wrapper"
|
|
7
|
-
:class="[
|
|
8
|
-
placement === 'top'
|
|
9
|
-
? 'sm:justify-start'
|
|
10
|
-
: placement === 'bottom'
|
|
11
|
-
? 'sm:justify-end'
|
|
12
|
-
: 'sm:justify-center',
|
|
13
|
-
]"
|
|
14
|
-
@click.self="closeModal"
|
|
15
|
-
>
|
|
16
|
-
<div
|
|
17
|
-
class="backdrop fixed inset-0 z-[-1] w-screen h-screen bg-black/50 pointer-events-none overflow-hidden"
|
|
18
|
-
></div>
|
|
19
|
-
<div
|
|
20
|
-
:class="[
|
|
21
|
-
'bg-white shadow-lg w-full overflow-hidden relative flex flex-col justify-between modal-container',
|
|
22
|
-
slideClass,
|
|
23
|
-
roundedClass !== '' ? roundedClass : 'rounded-t-3xl md:rounded-2xl',
|
|
24
|
-
size === 'full'
|
|
25
|
-
? 'h-full max-h-svh'
|
|
26
|
-
: 'max-h-[calc(100svh-3rem)] md:h-auto',
|
|
27
|
-
]"
|
|
28
|
-
>
|
|
29
|
-
<template v-if="$slots.header">
|
|
30
|
-
<slot name="header"></slot>
|
|
31
|
-
</template>
|
|
32
|
-
<div
|
|
33
|
-
v-else
|
|
34
|
-
class="flex flex-row items-center justify-between font-medium text-gray-700"
|
|
35
|
-
:class="slimHeader ? 'p-4 text-base' : 'p-6 text-lg'"
|
|
36
|
-
>
|
|
37
|
-
<h3 class="text-xl font-semibold">
|
|
38
|
-
<slot name="title">{{ title || "Modal Title" }}</slot>
|
|
39
|
-
</h3>
|
|
40
|
-
<div v-if="visibleClose" class="flex-initial">
|
|
41
|
-
<button
|
|
42
|
-
type="button"
|
|
43
|
-
class="flex items-center justify-center text-gray-400 bg-white hover:bg-gray-50 rounded-3xl hover:text-gray-600 size-8"
|
|
44
|
-
@click="closeModal"
|
|
45
|
-
>
|
|
46
|
-
<span class="sr-only">Close</span>
|
|
47
|
-
<svg
|
|
48
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
49
|
-
fill="none"
|
|
50
|
-
viewBox="0 0 24 24"
|
|
51
|
-
stroke-width="2"
|
|
52
|
-
stroke="currentColor"
|
|
53
|
-
aria-hidden="true"
|
|
54
|
-
class="w-5 h-5"
|
|
55
|
-
>
|
|
56
|
-
<path
|
|
57
|
-
stroke-linecap="round"
|
|
58
|
-
stroke-linejoin="round"
|
|
59
|
-
d="M6 18L18 6M6 6l12 12"
|
|
60
|
-
></path>
|
|
61
|
-
</svg>
|
|
62
|
-
</button>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<template v-if="$slots.content">
|
|
67
|
-
<slot name="content"></slot>
|
|
68
|
-
</template>
|
|
69
|
-
<div
|
|
70
|
-
v-else
|
|
71
|
-
class="p-4 border-t border-b border-gray-200 max-h-[calc(100svh-3rem)] flex-1"
|
|
72
|
-
>
|
|
73
|
-
<slot></slot>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<template v-if="$slots.footer">
|
|
77
|
-
<slot name="footer"></slot>
|
|
78
|
-
</template>
|
|
79
|
-
<div
|
|
80
|
-
v-else
|
|
81
|
-
class="sticky bottom-0 flex items-center justify-end float-none p-4 space-x-2"
|
|
82
|
-
>
|
|
83
|
-
<button
|
|
84
|
-
@click="closeModal"
|
|
85
|
-
class="px-4 py-2 text-base font-semibold tracking-wide text-gray-600 transition-colors duration-75 bg-white rounded-md hover:bg-gray-100"
|
|
86
|
-
>
|
|
87
|
-
Cancel
|
|
88
|
-
</button>
|
|
89
|
-
<button
|
|
90
|
-
@click="$emit('confirm')"
|
|
91
|
-
class="px-4 py-2 text-base font-semibold tracking-wide text-white transition-colors duration-75 bg-purple-600 rounded-md hover:bg-purple-700"
|
|
92
|
-
>
|
|
93
|
-
Confirm
|
|
94
|
-
</button>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</Transition>
|
|
99
|
-
</Teleport>
|
|
100
|
-
</template>
|
|
101
|
-
|
|
102
|
-
<script lang="ts">
|
|
103
|
-
import {
|
|
104
|
-
defineComponent,
|
|
105
|
-
onMounted,
|
|
106
|
-
onUnmounted,
|
|
107
|
-
watch,
|
|
108
|
-
computed,
|
|
109
|
-
type PropType,
|
|
110
|
-
} from "vue";
|
|
111
|
-
|
|
112
|
-
export default defineComponent({
|
|
113
|
-
name: "Modal",
|
|
114
|
-
props: {
|
|
115
|
-
isVisible: {
|
|
116
|
-
type: Boolean,
|
|
117
|
-
default: false,
|
|
118
|
-
},
|
|
119
|
-
slimHeader: {
|
|
120
|
-
type: Boolean,
|
|
121
|
-
default: false,
|
|
122
|
-
},
|
|
123
|
-
title: {
|
|
124
|
-
type: String,
|
|
125
|
-
default: "",
|
|
126
|
-
},
|
|
127
|
-
roundedClass: {
|
|
128
|
-
type: String,
|
|
129
|
-
default: "",
|
|
130
|
-
},
|
|
131
|
-
visibleClose: {
|
|
132
|
-
type: Boolean,
|
|
133
|
-
default: true,
|
|
134
|
-
},
|
|
135
|
-
persistent: {
|
|
136
|
-
type: Boolean,
|
|
137
|
-
default: false,
|
|
138
|
-
},
|
|
139
|
-
size: {
|
|
140
|
-
type: String as PropType<"xs" | "sm" | "md" | "lg" | "xl" | "full">,
|
|
141
|
-
default: "sm",
|
|
142
|
-
},
|
|
143
|
-
placement: {
|
|
144
|
-
type: String as PropType<"center" | "bottom" | "top">,
|
|
145
|
-
default: "center",
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
emits: ["update:isVisible", "confirm"],
|
|
149
|
-
setup(props, { emit }) {
|
|
150
|
-
const slideClass = computed(() => {
|
|
151
|
-
const sizeClass = {
|
|
152
|
-
full: "max-w-screen",
|
|
153
|
-
xl: "max-w-screen-lg min-w-[100svw] sm:min-w-[1024px]",
|
|
154
|
-
lg: "max-w-2xl min-w-[100svw] sm:min-w-[42rem]",
|
|
155
|
-
md: "max-w-xl min-w-[100svw] sm:min-w-[36rem]",
|
|
156
|
-
sm: "max-w-lg min-w-[100svw] sm:min-w-[32rem]",
|
|
157
|
-
xs: "max-w-md min-w-[100svw] sm:min-w-[28rem]",
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const slideWidth = sizeClass[props.size];
|
|
161
|
-
return slideWidth;
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// Methods
|
|
165
|
-
const closeModal = () => {
|
|
166
|
-
if (!props.persistent) {
|
|
167
|
-
emit("update:isVisible", false);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
const handleKeydown = (event: KeyboardEvent) => {
|
|
171
|
-
if (event.key === "Escape" && props.isVisible && !props.persistent) {
|
|
172
|
-
closeModal();
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
// Watcher for body scroll lock
|
|
176
|
-
watch(
|
|
177
|
-
() => props.isVisible,
|
|
178
|
-
(newVal) => {
|
|
179
|
-
document.body.style.overflow = newVal ? "hidden" : "";
|
|
180
|
-
}
|
|
181
|
-
);
|
|
182
|
-
// Lifecycle hooks
|
|
183
|
-
onMounted(() => {
|
|
184
|
-
window.addEventListener("keydown", handleKeydown);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
onUnmounted(() => {
|
|
188
|
-
document.body.style.overflow = ""; // Reset scroll when modal unmounts
|
|
189
|
-
window.removeEventListener("keydown", handleKeydown);
|
|
190
|
-
});
|
|
191
|
-
return {
|
|
192
|
-
closeModal,
|
|
193
|
-
slideClass,
|
|
194
|
-
};
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
</script>
|
|
198
|
-
|
|
199
|
-
<style lang="scss" scoped>
|
|
200
|
-
.modal-enter-active,
|
|
201
|
-
.modal-leave-active {
|
|
202
|
-
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
203
|
-
}
|
|
204
|
-
.modal-enter-from,
|
|
205
|
-
.modal-leave-to {
|
|
206
|
-
@apply opacity-0;
|
|
207
|
-
}
|
|
208
|
-
.modal-enter-to,
|
|
209
|
-
.modal-leave-from {
|
|
210
|
-
@apply opacity-100;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.backdrop {
|
|
214
|
-
transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
215
|
-
}
|
|
216
|
-
.modal-enter-from .backdrop,
|
|
217
|
-
.modal-leave-to .backdrop {
|
|
218
|
-
@apply opacity-0;
|
|
219
|
-
}
|
|
220
|
-
.modal-enter-to .backdrop,
|
|
221
|
-
.modal-leave-from .backdrop {
|
|
222
|
-
@apply opacity-100;
|
|
223
|
-
}
|
|
224
|
-
</style>
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<p v-if="label" class="mb-3 text-sm font-medium text-gray-500 font-inter">
|
|
4
|
-
{{ label }}<span v-if="required" class="text-red-500">*</span>
|
|
5
|
-
</p>
|
|
6
|
-
<div class="flex flex-row flex-wrap gap-3">
|
|
7
|
-
<EUIButton
|
|
8
|
-
v-for="(item, idx) in items"
|
|
9
|
-
:key="`${name}-${idx}`"
|
|
10
|
-
type="button"
|
|
11
|
-
size="md"
|
|
12
|
-
color="white"
|
|
13
|
-
:class="[
|
|
14
|
-
isSelected(item)
|
|
15
|
-
? '!bg-purple-100 !text-purple-600 !font-medium shadow-sm'
|
|
16
|
-
: '!bg-gray-100 !text-gray-600 !font-medium hover:text-gray-800',
|
|
17
|
-
'px-4 py-2 text-sm leading-6 focus:outline-none rounded-3xl transition-colors duration-100 ease-in-out',
|
|
18
|
-
]"
|
|
19
|
-
rounded
|
|
20
|
-
@click="handleSelect(item)"
|
|
21
|
-
:disabled="disabled"
|
|
22
|
-
>
|
|
23
|
-
{{
|
|
24
|
-
ignoreStartCase
|
|
25
|
-
? item[labelKey] || ""
|
|
26
|
-
: startCase(item[labelKey] || "")
|
|
27
|
-
}}
|
|
28
|
-
</EUIButton>
|
|
29
|
-
</div>
|
|
30
|
-
<template v-if="errors && Object.keys(errors).length">
|
|
31
|
-
<EUIErrorMessage :errors="errors" :name="name" />
|
|
32
|
-
</template>
|
|
33
|
-
</div>
|
|
34
|
-
</template>
|
|
35
|
-
|
|
36
|
-
<script setup lang="ts">
|
|
37
|
-
import { startCase } from "lodash";
|
|
38
|
-
import EUIButton from "../button/EUIButton.vue";
|
|
39
|
-
import EUIErrorMessage from "../errorMessage/EUIErrorMessage.vue";
|
|
40
|
-
import { PropType, computed, toRefs, watch } from "vue";
|
|
41
|
-
import { ErrorObject, ValidationErrors } from "../../utils/types";
|
|
42
|
-
const props = defineProps({
|
|
43
|
-
label: {
|
|
44
|
-
type: String,
|
|
45
|
-
default: "",
|
|
46
|
-
},
|
|
47
|
-
modelValue: {
|
|
48
|
-
type: Object as PropType<any | null>,
|
|
49
|
-
default: null,
|
|
50
|
-
},
|
|
51
|
-
labelKey: {
|
|
52
|
-
type: String,
|
|
53
|
-
default: "label",
|
|
54
|
-
},
|
|
55
|
-
valueKey: {
|
|
56
|
-
type: String,
|
|
57
|
-
default: "value",
|
|
58
|
-
},
|
|
59
|
-
items: {
|
|
60
|
-
type: Array<any>,
|
|
61
|
-
default: [],
|
|
62
|
-
},
|
|
63
|
-
multiple: {
|
|
64
|
-
type: Boolean,
|
|
65
|
-
default: false,
|
|
66
|
-
},
|
|
67
|
-
name: {
|
|
68
|
-
type: String,
|
|
69
|
-
default: "",
|
|
70
|
-
},
|
|
71
|
-
disabled: {
|
|
72
|
-
type: Boolean,
|
|
73
|
-
deault: false,
|
|
74
|
-
},
|
|
75
|
-
required: {
|
|
76
|
-
type: Boolean,
|
|
77
|
-
deault: false,
|
|
78
|
-
},
|
|
79
|
-
errors: {
|
|
80
|
-
type: Object as PropType<ValidationErrors | ErrorObject[]>,
|
|
81
|
-
default: [],
|
|
82
|
-
},
|
|
83
|
-
ignoreStartCase: {
|
|
84
|
-
type: Boolean,
|
|
85
|
-
deault: false,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
const { multiple, valueKey, labelKey, errors, ignoreStartCase } = toRefs(props);
|
|
89
|
-
const emit = defineEmits(["update:model-value","on-change"]);
|
|
90
|
-
const activeItem = computed({
|
|
91
|
-
set: (value) => {
|
|
92
|
-
emit("update:model-value", value);
|
|
93
|
-
},
|
|
94
|
-
get: () => props.modelValue,
|
|
95
|
-
});
|
|
96
|
-
const handleMultiSelect = (item: any) => {
|
|
97
|
-
if (Array.isArray(activeItem.value)) {
|
|
98
|
-
const index = activeItem.value?.findIndex(
|
|
99
|
-
(x) => x?.[valueKey.value] === item?.[valueKey.value]
|
|
100
|
-
);
|
|
101
|
-
if (index != -1) {
|
|
102
|
-
activeItem.value.splice(index, 1);
|
|
103
|
-
} else {
|
|
104
|
-
activeItem.value.push(item);
|
|
105
|
-
}
|
|
106
|
-
emit('on-change',activeItem.value)
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const handleSingleSelect = (item: any) => {
|
|
110
|
-
if (!Array.isArray(activeItem.value)) {
|
|
111
|
-
if (activeItem.value?.[valueKey.value] === item?.[valueKey.value]) {
|
|
112
|
-
activeItem.value = null;
|
|
113
|
-
} else {
|
|
114
|
-
activeItem.value = item;
|
|
115
|
-
}
|
|
116
|
-
emit('on-change',activeItem.value)
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
const handleSelect = (item: any) => {
|
|
120
|
-
if (multiple.value) {
|
|
121
|
-
handleMultiSelect(item);
|
|
122
|
-
} else {
|
|
123
|
-
handleSingleSelect(item);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const isSelected = (item: any) => {
|
|
128
|
-
if (Array.isArray(activeItem.value)) {
|
|
129
|
-
const values = activeItem.value
|
|
130
|
-
.map((x) => x[valueKey.value])
|
|
131
|
-
.filter((x) => x);
|
|
132
|
-
return values.includes(item?.[valueKey.value]);
|
|
133
|
-
}
|
|
134
|
-
return activeItem.value?.[valueKey.value] === item?.[valueKey.value];
|
|
135
|
-
};
|
|
136
|
-
watch(
|
|
137
|
-
multiple,
|
|
138
|
-
() => {
|
|
139
|
-
if (multiple.value) {
|
|
140
|
-
if (!Array.isArray(activeItem.value)) {
|
|
141
|
-
activeItem.value = [activeItem.value];
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
activeItem.value = activeItem.value ? activeItem.value : null;
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
{ immediate: true }
|
|
148
|
-
);
|
|
149
|
-
</script>
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div ref="popperWrapper" class="w-max">
|
|
3
|
-
<div
|
|
4
|
-
ref="popperButton"
|
|
5
|
-
:id="triggerId"
|
|
6
|
-
:aria-describedby="tooltipId"
|
|
7
|
-
:aria-expanded="isOpen"
|
|
8
|
-
aria-haspopup="true"
|
|
9
|
-
:class="['inline-flex items-center text-sm font-semibold gap-x-2 cursor-pointer', customButton]"
|
|
10
|
-
@click="handleClick"
|
|
11
|
-
@mouseenter="handleMouseEnter"
|
|
12
|
-
@mouseleave="handleMouseLeave"
|
|
13
|
-
@focus="handleFocus"
|
|
14
|
-
@blur="handleBlur"
|
|
15
|
-
>
|
|
16
|
-
<slot name="referenceButton" :open="isOpen"/>
|
|
17
|
-
</div>
|
|
18
|
-
<div
|
|
19
|
-
ref="tooltip"
|
|
20
|
-
:id="tooltipId"
|
|
21
|
-
role="tooltip"
|
|
22
|
-
:class="['poppertooltip h-auto text-sm font-normal select-none z-50', className]"
|
|
23
|
-
:data-show="isOpen ? '' : null"
|
|
24
|
-
@click.stop
|
|
25
|
-
@mouseenter="handleTooltipMouseEnter"
|
|
26
|
-
@mouseleave="handleTooltipMouseLeave"
|
|
27
|
-
>
|
|
28
|
-
<div ref="arrowElement" class="arrow" data-popper-arrow />
|
|
29
|
-
<div
|
|
30
|
-
v-if="title"
|
|
31
|
-
class="text-base font-semibold text-current leading-[normal] px-4 py-2"
|
|
32
|
-
v-text="title"
|
|
33
|
-
/>
|
|
34
|
-
<slot />
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</template>
|
|
38
|
-
|
|
39
|
-
<script setup lang="ts">
|
|
40
|
-
import { computed, nextTick, onBeforeUnmount, PropType, watch } from "vue";
|
|
41
|
-
import { createPopper, Instance } from "@popperjs/core";
|
|
42
|
-
import { onClickOutside } from "@vueuse/core";
|
|
43
|
-
import { onMounted, ref } from "vue";
|
|
44
|
-
const props = defineProps({
|
|
45
|
-
defaultOpen: {
|
|
46
|
-
type: Boolean,
|
|
47
|
-
default: false,
|
|
48
|
-
},
|
|
49
|
-
title: {
|
|
50
|
-
type: String,
|
|
51
|
-
default: "",
|
|
52
|
-
},
|
|
53
|
-
trigger: {
|
|
54
|
-
type: String as PropType<"click" | "hover" | "manual">,
|
|
55
|
-
default: "click",
|
|
56
|
-
validator: (value: string) => ["click", "hover", "manual"].includes(value),
|
|
57
|
-
},
|
|
58
|
-
className: {
|
|
59
|
-
type: String,
|
|
60
|
-
required: false,
|
|
61
|
-
default: "bg-white text-gray-900",
|
|
62
|
-
},
|
|
63
|
-
placement: {
|
|
64
|
-
type: String as PropType<"top" | "left" | "bottom" | "right" | "bottom-start" | "bottom-end">,
|
|
65
|
-
default: "bottom",
|
|
66
|
-
},
|
|
67
|
-
customButton: {
|
|
68
|
-
type: String,
|
|
69
|
-
required: false,
|
|
70
|
-
default: ''
|
|
71
|
-
},
|
|
72
|
-
visible: {
|
|
73
|
-
type: Boolean,
|
|
74
|
-
default: undefined,
|
|
75
|
-
},
|
|
76
|
-
hoverHideDelay: {
|
|
77
|
-
type: Number,
|
|
78
|
-
default: 100,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const popperWrapper = ref<HTMLElement | null>(null);
|
|
83
|
-
const popperInstance = ref<Instance | null>(null);
|
|
84
|
-
const popperButton = ref<HTMLElement | null>(null);
|
|
85
|
-
const tooltip = ref<HTMLElement | null>(null);
|
|
86
|
-
const arrowElement = ref<HTMLElement | null>(null);
|
|
87
|
-
|
|
88
|
-
const isOpen = ref(props.visible === undefined ? props.defaultOpen : props.visible);
|
|
89
|
-
const hideTimer = ref<ReturnType<typeof setTimeout> | null>(null);
|
|
90
|
-
|
|
91
|
-
const uniqueId = `popover-${Math.random().toString(36).substring(2, 9)}`;
|
|
92
|
-
const triggerId = computed(() => `${uniqueId}-trigger`);
|
|
93
|
-
const tooltipId = computed(() => `${uniqueId}-tooltip`);
|
|
94
|
-
|
|
95
|
-
const emit = defineEmits<{
|
|
96
|
-
(e: 'update:visible', value: boolean): void;
|
|
97
|
-
(e: 'show'): void;
|
|
98
|
-
(e: 'hide'): void;
|
|
99
|
-
}>();
|
|
100
|
-
|
|
101
|
-
watch(() => props.visible, (newValue) => {
|
|
102
|
-
if (newValue !== undefined && newValue !== isOpen.value) {
|
|
103
|
-
if (newValue) {
|
|
104
|
-
show();
|
|
105
|
-
} else {
|
|
106
|
-
hide();
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
watch(isOpen, (newValue) => {
|
|
112
|
-
if (props.visible === undefined || newValue !== props.visible) {
|
|
113
|
-
emit('update:visible', newValue);
|
|
114
|
-
}
|
|
115
|
-
nextTick(() => {
|
|
116
|
-
popperInstance.value?.update();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const initializePopper = () => {
|
|
122
|
-
if (popperButton.value && tooltip.value && arrowElement.value) {
|
|
123
|
-
popperInstance.value = createPopper(popperButton.value, tooltip.value, {
|
|
124
|
-
placement: props.placement,
|
|
125
|
-
modifiers: [
|
|
126
|
-
{ name: 'offset', options: { offset: [0, 10] } },
|
|
127
|
-
{ name: 'arrow', options: { element: arrowElement.value } },
|
|
128
|
-
{
|
|
129
|
-
name: 'preventOverflow',
|
|
130
|
-
options: {
|
|
131
|
-
padding: 8,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const destroyPopper = () => {
|
|
140
|
-
if (popperInstance.value) {
|
|
141
|
-
popperInstance.value.destroy();
|
|
142
|
-
popperInstance.value = null;
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const show = () => {
|
|
147
|
-
if (isOpen.value) return;
|
|
148
|
-
clearTimeout(hideTimer.value!);
|
|
149
|
-
isOpen.value = true;
|
|
150
|
-
emit('show');
|
|
151
|
-
nextTick(() => {
|
|
152
|
-
if (!popperInstance.value) {
|
|
153
|
-
initializePopper();
|
|
154
|
-
}
|
|
155
|
-
popperInstance.value?.update();
|
|
156
|
-
popperInstance.value?.forceUpdate();
|
|
157
|
-
});
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Hide the popover
|
|
161
|
-
const hide = (immediate = false) => {
|
|
162
|
-
if (!isOpen.value) return; // Already closed
|
|
163
|
-
|
|
164
|
-
if (props.trigger === 'hover' && !immediate) {
|
|
165
|
-
clearTimeout(hideTimer.value!);
|
|
166
|
-
hideTimer.value = setTimeout(() => {
|
|
167
|
-
isOpen.value = false;
|
|
168
|
-
emit('hide');
|
|
169
|
-
}, props.hoverHideDelay);
|
|
170
|
-
} else {
|
|
171
|
-
clearTimeout(hideTimer.value!);
|
|
172
|
-
isOpen.value = false;
|
|
173
|
-
emit('hide');
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const handleClick = () => {
|
|
178
|
-
if (props.trigger === 'click') {
|
|
179
|
-
if (isOpen.value) {
|
|
180
|
-
hide(true);
|
|
181
|
-
} else {
|
|
182
|
-
show();
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const handleMouseEnter = () => {
|
|
188
|
-
if (props.trigger === 'hover') {
|
|
189
|
-
show();
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const handleMouseLeave = () => {
|
|
194
|
-
if (props.trigger === 'hover') {
|
|
195
|
-
hide(false);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const handleFocus = () => {
|
|
200
|
-
if (props.trigger === 'hover') {
|
|
201
|
-
show();
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const handleBlur = () => {
|
|
206
|
-
if (props.trigger === 'hover') {
|
|
207
|
-
hide(false);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const handleTooltipMouseEnter = () => {
|
|
212
|
-
if (props.trigger === 'hover') {
|
|
213
|
-
clearTimeout(hideTimer.value!);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const handleTooltipMouseLeave = () => {
|
|
218
|
-
if (props.trigger === 'hover') {
|
|
219
|
-
hide(false);
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
onMounted(() => {
|
|
224
|
-
if (isOpen.value) {
|
|
225
|
-
nextTick(() => {
|
|
226
|
-
initializePopper();
|
|
227
|
-
setTimeout(() => popperInstance.value?.forceUpdate(), 50);
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (props.trigger === 'click') {
|
|
232
|
-
onClickOutside(popperWrapper.value, () => {
|
|
233
|
-
if (isOpen.value) {
|
|
234
|
-
hide(true);
|
|
235
|
-
}
|
|
236
|
-
}, { ignore: [popperButton] });
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
onBeforeUnmount(() => {
|
|
241
|
-
destroyPopper();
|
|
242
|
-
clearTimeout(hideTimer.value!); // Clear any running timers
|
|
243
|
-
});
|
|
244
|
-
</script>
|
|
245
|
-
|
|
246
|
-
<style lang="scss" scoped>
|
|
247
|
-
.poppertooltip {
|
|
248
|
-
@apply hidden;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.poppertooltip[data-show] {
|
|
252
|
-
z-index: calc(infinity);
|
|
253
|
-
@apply block rounded-lg;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.arrow,
|
|
257
|
-
.arrow::before {
|
|
258
|
-
background: inherit;
|
|
259
|
-
@apply size-3 absolute border-[0.4rem] border-gray-200 rounded-tl-sm -z-[1];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.arrow {
|
|
263
|
-
@apply invisible;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.arrow::before {
|
|
267
|
-
content: "";
|
|
268
|
-
@apply visible transform rotate-45 mx-1;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.poppertooltip{
|
|
272
|
-
&[data-popper-placement^="top"] > .arrow {
|
|
273
|
-
@apply bottom-0 -ml-2.5;
|
|
274
|
-
&::before {
|
|
275
|
-
@apply border-t-transparent border-l-transparent;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
&[data-popper-placement^="bottom"] > .arrow {
|
|
279
|
-
@apply -top-3 -ml-2.5;
|
|
280
|
-
&::before {
|
|
281
|
-
@apply border-b-transparent border-r-transparent;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
&[data-popper-placement^="left"] > .arrow {
|
|
285
|
-
@apply right-1 -mt-1.5;
|
|
286
|
-
&::before {
|
|
287
|
-
@apply border-l-transparent border-b-transparent;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
&[data-popper-placement^="right"] > .arrow {
|
|
291
|
-
@apply -left-4 -mt-1.5;
|
|
292
|
-
&::before {
|
|
293
|
-
@apply border-r-transparent border-t-transparent;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
</style>
|