Skip to content

Commit

Permalink
feat(noti): progress and pauseHover (#11)
Browse files Browse the repository at this point in the history
* feat(noti): progress bar

* feat(noti): hoverPause

* feat: playground

* feat: progress props
  • Loading branch information
Rock070 authored Sep 14, 2023
1 parent 8e274ad commit 23b33e8
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 60 deletions.
46 changes: 46 additions & 0 deletions packages/core/src/components/AtomicProgress.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
interface AtomicProgressProps {
/**
* progress value
*/
value?: number
/**
* max value
*/
max?: number
}
withDefaults(defineProps<AtomicProgressProps>(), {
value: 0,
max: 100,
})
</script>

<template>
<progress
:value="value"
:max="max"
class="vue3-noti__progress"
>
{{ Math.floor(value / max) }}%
</progress>
</template>

<style>
.vue3-noti__progress {
width: 100%;
border-radius: 10px;
height: 4px;
overflow: hidden;
}
.vue3-noti__progress::-webkit-progress-value {
background-color: #0235E6
}
.vue3-noti__progress::-webkit-progress-bar {
background: gray;
}
</style>
114 changes: 97 additions & 17 deletions packages/core/src/components/Noti.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,100 @@
import { ref } from 'vue'
import { useCounter } from '@vueuse/core'
import { useNoti } from '../composables/useNoti'
import useEventBus from '../composables/useEventBus'
import { useNotiContext } from '../composables/useNotiContext'
import type { NotiOptions } from '../types'
import AtomicProgress from './AtomicProgress.vue'
export type NotificationList = (NotiOptions & { id: number | string | symbol })[]
export interface NotiTimer {
/**
* Whether the notification timer is active
*/
isActive: boolean
export interface NotiProps {
test: string
/**
* Time left (in milliseconds)
*/
lastTime: number
/**
* end count down timestamp
*/
endCountDownTimestamp: number
}
export interface Notification extends NotiOptions {
id: number | string | symbol
timer: NotiTimer
}
defineProps<NotiProps>()
export type NotificationList = Notification[]
const { options: initialOptions } = useNotiContext()
const { inc: countIncrease } = useCounter(1)
const noti = useNoti()
const queue = ref<NotificationList>([])
/**
* Time per frame rendered (in milliseconds)
*/
const MS_PER_FRAME = 1000 / 60
if (!noti)
console.warn('[@vue3-noti/core warning] useNoti is undefined')
function triggerCountDown(target: Notification) {
const interval = setInterval(() => {
if (!target.timer.isActive)
clearInterval(interval)
const queue = ref<NotificationList>([])
const lastTime = target.timer.endCountDownTimestamp - Date.now()
if (lastTime > 0) {
target.timer.lastTime = lastTime
}
else {
target.timer.lastTime = 0
queue.value = queue.value.filter(item => item.id !== target.id)
clearInterval(interval)
}
}, MS_PER_FRAME)
}
function publishEvent(options: NotiOptions) {
const item = { ...initialOptions, ...options, id: countIncrease() }
const item: Notification = {
...initialOptions,
...options,
id: countIncrease(),
timer: {
isActive: true,
lastTime: 0,
endCountDownTimestamp: 0,
},
}
item.timer.lastTime = item.duration!
item.timer.endCountDownTimestamp = item.duration! + Date.now()
queue.value.push(item)
const { id, duration } = item
setTimeout(() => {
queue.value = queue.value.filter(item => item.id !== id)
}, duration)
const target = queue.value.find(i => i.id === item.id)
if (!target)
return
triggerCountDown(target)
}
function onMouseEnter(val: Notification) {
if (!val.hoverPause)
return
val.timer.isActive = false
}
function onMouseLeave(val: Notification) {
if (!val.hoverPause)
return
val.timer.isActive = true
val.timer.endCountDownTimestamp = val.timer.lastTime + Date.now()
triggerCountDown(val)
}
const bus = useEventBus()
Expand All @@ -44,8 +105,19 @@ bus.on(publishEvent)
<template>
<div class="vue3-noti">
<div class="vue3-noti__container">
<div v-for="item in queue" :key="item.id" class="vue3-noti-group">
{{ item.message }} {{ item.id }}
<div
v-for="item in queue"
:key="item.id"
class="vue3-noti-group"
@mouseenter="onMouseEnter(item)"
@mouseleave="onMouseLeave(item)"
>
{{ item.message }}
<AtomicProgress
v-if="item.showProgressBar"
:value="item.timer.lastTime"
:max="item.duration"
/>
</div>
</div>
</div>
Expand All @@ -63,10 +135,18 @@ bus.on(publishEvent)
z-index: 9999;
}
.vue3-noti .vue3-noti__container .vue3-noti-group {
position: relative;
font-size: 32px;
background-color: chartreuse;
color: black;
border-radius: 12px;
padding: 10px 20px
padding: 10px 20px;
overflow: hidden;
}
.vue3-noti .vue3-noti__progress {
position: absolute;
bottom: 0;
left: 0;
}
</style>
29 changes: 29 additions & 0 deletions packages/core/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,40 @@ export enum NOTI_POSITION {
export type NotiPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'middle-top' | 'middle-left' | 'middle-right'

export interface NotiOptions {
/**
* The title of the notification.
*/
message: string

/**
* The type of the notification.
*/
type?: NotificationType

/**
* The position of the notification.
*/
position?: NotiPosition

/**
* The duration of the notification.
*/
duration?: number

/**
* Whether to show the progress bar.
*/
showProgressBar?: boolean

/**
* Whether to close when clicking on the notification.
*/
closeOnClick?: boolean

/**
* Whether to pause on hover.
*/
hoverPause?: boolean
}

export interface NotiContext {
Expand Down
101 changes: 58 additions & 43 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Noti, useNoti } from '@vue3-noti/core'
import type { NotiOptions } from '@vue3-noti/core'
import '@vue3-noti/core/style.css'
const string = ref('')
const noti = useNoti()
function callNoti() {
noti({
message: 'Hello World',
type: 'success',
})
}
const options = ref<NotiOptions>({
message: 'Hello Noti',
duration: 3e3,
hoverPause: true,
showProgressBar: true,
function callNoti2() {
noti({
duration: 5000,
message: 'Hello World Longer',
type: 'success',
})
}
closeOnClick: true,
position: 'top-right',
type: 'info',
})
</script>

<template>
<div class="playground">
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo">
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo">
</a>
<h1> Vue3-Noti </h1>
<Noti />
<br>
<button
type="button"
class="noti-button"
@click="noti(options)"
>
notify
</button>
<div class="control-panel">
<!-- message -->
<div class="field-group">
<label for="message">message: </label>
<input id="message" v-model="options.message" type="text">
</div>

<br>
<Noti v-model:test="string" />
<div class="button-group">
<button type="button" @click="callNoti">
call default notify last 1 second
</button>
<button type="button" @click="callNoti2">
call custom notify last 5 second
</button>
<div class="field-group">
<!-- duration -->
<label for="duration">duration(ms): </label>
<input id="duration" v-model="options.duration" type="number" min="0">
</div>

<div class="field-group">
<!-- showProgressBar -->
<label for="showProgressBar">showProgressBar </label>
<input id="showProgressBar" v-model="options.showProgressBar" type="checkbox">
</div>

<div class="field-group">
<!-- hoverPause -->
<label for="hoverPause">hoverPause </label>
<input id="hoverPause" v-model="options.hoverPause" type="checkbox">
</div>
</div>
</div>
</template>
Expand All @@ -52,21 +63,25 @@ function callNoti2() {
height: 100vh;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
.noti-button {
margin-bottom: 2em;
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
.control-panel {
display: grid;
gap: 1em;
grid-template-columns: repeat(2, 1fr);
}
.button-group {
.field-group {
display: flex;
gap: 1em;
margin-top: 1em;
justify-content: center;
align-items: center;
gap: 0 1em;
margin: 1em 0;
}
label {
cursor: pointer;
}
</style>
2 changes: 2 additions & 0 deletions playground/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const notiOptions: NotiOptions = {
type: 'success',
duration: 1000,
position: 'top-right',
hoverPause: true,
showProgressBar: true,
}

createApp(App).use(NotiPlugin, notiOptions).mount('#app')

0 comments on commit 23b33e8

Please sign in to comment.