update
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
<template>
|
||||
<Transition
|
||||
:name="cn.b()"
|
||||
@before-leave="$emit('close')"
|
||||
@after-leave="$emit('destroy')"
|
||||
>
|
||||
<UiAlert
|
||||
v-show="visible"
|
||||
:id="id"
|
||||
:class="[cn.b(), verticalSide, horizontalSide]"
|
||||
:title="title"
|
||||
:type="type"
|
||||
@mouseenter="clearTimer"
|
||||
@mouseleave="startTimer"
|
||||
>
|
||||
<slot />
|
||||
</UiAlert>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NotificationPlacement, NotificationType } from './types'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
export interface Props {
|
||||
duration?: number
|
||||
id?: string | number
|
||||
offset?: number
|
||||
placement?: NotificationPlacement
|
||||
type?: NotificationType
|
||||
title?: string
|
||||
zIndex?: number
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'UiNotification',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'neutral',
|
||||
duration: 5000,
|
||||
offset: 0,
|
||||
placement: 'top-right',
|
||||
zIndex: 0,
|
||||
})
|
||||
|
||||
defineEmits(['close', 'destroy'])
|
||||
|
||||
const cn = useClassname('ui-notification')
|
||||
|
||||
const timerId = ref()
|
||||
const visible = ref(false)
|
||||
|
||||
const verticalSide = computed(() => props.placement.split('-')[0])
|
||||
const horizontalSide = computed(() => props.placement.split('-')[1])
|
||||
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (props.duration > 0) {
|
||||
timerId.value = setTimeout(() => {
|
||||
if (visible.value)
|
||||
close()
|
||||
}, props.duration)
|
||||
}
|
||||
}
|
||||
|
||||
function clearTimer() {
|
||||
if (timerId.value)
|
||||
clearTimeout(timerId.value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
startTimer()
|
||||
visible.value = true
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
visible,
|
||||
close,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-notification {
|
||||
position: fixed;
|
||||
transition-duration: $transition-duration;
|
||||
transition-property: opacity, transform, left, right, top, bottom;
|
||||
z-index: calc(8000 + v-bind('zIndex'));
|
||||
width: 400px;
|
||||
transform-origin: right top;
|
||||
|
||||
@include mobile {
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
&.top {
|
||||
top: calc(v-bind('`${offset}px`') + 80px);
|
||||
|
||||
@include mobile {
|
||||
top: auto;
|
||||
bottom: v-bind('`${offset}px`');
|
||||
}
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
bottom: v-bind('`${offset}px`');
|
||||
}
|
||||
|
||||
&.left {
|
||||
left: 32px;
|
||||
|
||||
@include mobile {
|
||||
left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: 32px;
|
||||
|
||||
@include mobile {
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,132 +0,0 @@
|
||||
import { createVNode, render } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import NotificationConstructor from './notification.vue'
|
||||
import type {
|
||||
NotificationOptions,
|
||||
NotificationPlacement,
|
||||
NotificationQueue,
|
||||
} from './types'
|
||||
|
||||
const notifications: Record<NotificationPlacement, NotificationQueue> = {
|
||||
'top-left': [],
|
||||
'top-right': [],
|
||||
'bottom-left': [],
|
||||
'bottom-right': [],
|
||||
}
|
||||
|
||||
const GAP_SIZE = 16
|
||||
let SEED = 1
|
||||
|
||||
const DEFAULT_OPTIONS: NotificationOptions = {
|
||||
text: '',
|
||||
placement: 'top-right',
|
||||
duration: 5000,
|
||||
onClose: () => {},
|
||||
}
|
||||
|
||||
const notify = function (options: NotificationOptions, context = null) {
|
||||
// if (process.server) return { close: () => undefined };
|
||||
|
||||
options = defu(options, DEFAULT_OPTIONS)
|
||||
|
||||
const orientedNotifications = notifications[options.placement!]
|
||||
const id = options.id
|
||||
? `${options.placement!}_${options.id}`
|
||||
: `notification_${SEED++}`
|
||||
|
||||
const idx = orientedNotifications.findIndex(
|
||||
({ vm }) => vm.component?.props.id === id,
|
||||
)
|
||||
if (idx > -1)
|
||||
return
|
||||
|
||||
let verticalOffset = options.offset || 0
|
||||
notifications[options.placement!].forEach(({ vm }) => {
|
||||
verticalOffset += (vm.el?.offsetHeight || 0) + GAP_SIZE
|
||||
})
|
||||
verticalOffset += GAP_SIZE
|
||||
|
||||
const userOnClose = options.onClose
|
||||
const props = {
|
||||
...options,
|
||||
offset: verticalOffset,
|
||||
id,
|
||||
onClose: () => {
|
||||
close(id, options.placement!, userOnClose)
|
||||
},
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
|
||||
const vm = createVNode(
|
||||
NotificationConstructor,
|
||||
props,
|
||||
options.text
|
||||
? {
|
||||
default: () => options.text,
|
||||
}
|
||||
: null,
|
||||
)
|
||||
vm.appContext = context ?? notify._context
|
||||
vm.props!.onDestroy = () => {
|
||||
render(null, container)
|
||||
}
|
||||
|
||||
render(vm, container)
|
||||
notifications[options.placement!].push({ vm })
|
||||
document.body.appendChild(container.firstElementChild!)
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
vm.component!.exposed!.close()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function close(
|
||||
id: NotificationOptions['id'],
|
||||
placement: NotificationOptions['placement'],
|
||||
userOnClose: NotificationOptions['onClose'],
|
||||
) {
|
||||
const orientedNotifications = notifications[placement!]
|
||||
const idx = orientedNotifications.findIndex(
|
||||
({ vm }) => vm.component?.props.id === id,
|
||||
)
|
||||
if (idx === -1)
|
||||
return
|
||||
|
||||
const { vm } = orientedNotifications[idx]
|
||||
|
||||
if (!vm)
|
||||
return
|
||||
|
||||
userOnClose?.(vm)
|
||||
|
||||
const removedHeight = vm.el!.offsetHeight
|
||||
const verticalPos = placement!.split('-')[0]
|
||||
orientedNotifications.splice(idx, 1)
|
||||
|
||||
if (orientedNotifications.length < 1)
|
||||
return
|
||||
|
||||
for (let i = idx; i < orientedNotifications.length; i++) {
|
||||
const { el, component } = orientedNotifications[i].vm
|
||||
const styles = getComputedStyle(el as Element)
|
||||
const pos = Number.parseInt(styles.getPropertyValue(verticalPos), 10)
|
||||
|
||||
component!.props.offset = pos - removedHeight - GAP_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
export function closeAll() {
|
||||
for (const orientedNotifications of Object.values(notifications)) {
|
||||
orientedNotifications.forEach(({ vm }) => {
|
||||
vm.component!.exposed!.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
notify.closeAll = closeAll
|
||||
notify._context = null
|
||||
|
||||
export default notify
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
export const NOTIFICATION_TYPES = [
|
||||
'neutral',
|
||||
'positive',
|
||||
'warning',
|
||||
'negative',
|
||||
] as const
|
||||
|
||||
export type NotificationType = (typeof NOTIFICATION_TYPES)[number]
|
||||
|
||||
export type NotificationPlacement =
|
||||
| 'top-right'
|
||||
| 'top-left'
|
||||
| 'bottom-right'
|
||||
| 'bottom-left'
|
||||
|
||||
export interface NotificationOptions {
|
||||
type?: NotificationType
|
||||
text: string
|
||||
title?: string
|
||||
duration?: number
|
||||
placement?: NotificationPlacement
|
||||
id?: string | number
|
||||
offset?: number
|
||||
onClose?: (vm?: VNode) => void
|
||||
}
|
||||
|
||||
export interface NotificationItem {
|
||||
vm: VNode
|
||||
}
|
||||
|
||||
export type NotificationQueue = NotificationItem[]
|
||||
Reference in New Issue
Block a user