@v-c/notification 0.0.2
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/LICENSE +21 -0
- package/bump.config.ts +6 -0
- package/dist/Notice.cjs +230 -0
- package/dist/Notice.d.ts +15 -0
- package/dist/Notice.js +225 -0
- package/dist/NoticeList.cjs +157 -0
- package/dist/NoticeList.d.ts +13 -0
- package/dist/NoticeList.js +154 -0
- package/dist/NotificationProvider.cjs +13 -0
- package/dist/NotificationProvider.d.ts +10 -0
- package/dist/NotificationProvider.js +10 -0
- package/dist/Notifications.cjs +146 -0
- package/dist/Notifications.d.ts +24 -0
- package/dist/Notifications.js +143 -0
- package/dist/_virtual/rolldown_runtime.cjs +21 -0
- package/dist/hooks/useNotification.cjs +80 -0
- package/dist/hooks/useNotification.d.ts +36 -0
- package/dist/hooks/useNotification.js +78 -0
- package/dist/hooks/useStack.cjs +24 -0
- package/dist/hooks/useStack.d.ts +6 -0
- package/dist/hooks/useStack.js +22 -0
- package/dist/index.cjs +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/interface.cjs +0 -0
- package/dist/interface.d.ts +55 -0
- package/dist/interface.js +0 -0
- package/docs/context.vue +34 -0
- package/docs/hooks.vue +89 -0
- package/docs/index.less +265 -0
- package/docs/maxCount.vue +24 -0
- package/docs/motion.ts +33 -0
- package/docs/notification.stories.vue +31 -0
- package/docs/showProgress.vue +34 -0
- package/docs/stack.vue +39 -0
- package/package.json +30 -0
- package/src/Notice.tsx +212 -0
- package/src/NoticeList.tsx +203 -0
- package/src/NotificationProvider.tsx +19 -0
- package/src/Notifications.tsx +164 -0
- package/src/hooks/useNotification.tsx +163 -0
- package/src/hooks/useStack.ts +32 -0
- package/src/index.ts +9 -0
- package/src/interface.ts +61 -0
- package/tsconfig.json +7 -0
- package/vite.config.ts +18 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { VueNode } from '@v-c/util/dist/type'
|
|
2
|
+
import type { CSSProperties, TransitionGroupProps } from 'vue'
|
|
3
|
+
import type { Key, OpenConfig, Placement, StackConfig } from '../interface'
|
|
4
|
+
import type { NotificationsProps, NotificationsRef } from '../Notifications'
|
|
5
|
+
import { onMounted, shallowRef, watch } from 'vue'
|
|
6
|
+
import Notifications from '../Notifications'
|
|
7
|
+
|
|
8
|
+
const defaultGetContainer = () => document.body
|
|
9
|
+
|
|
10
|
+
type OptionalConfig = Partial<OpenConfig>
|
|
11
|
+
|
|
12
|
+
export interface NotificationConfig {
|
|
13
|
+
prefixCls?: string
|
|
14
|
+
/** Customize container. It will repeat call which means you should return same container element. */
|
|
15
|
+
getContainer?: () => HTMLElement | ShadowRoot
|
|
16
|
+
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
17
|
+
closeIcon?: VueNode
|
|
18
|
+
closable?:
|
|
19
|
+
| boolean
|
|
20
|
+
| ({ closeIcon?: VueNode, onClose?: VoidFunction } & Record<string, any>)
|
|
21
|
+
maxCount?: number
|
|
22
|
+
duration?: number | false | null
|
|
23
|
+
showProgress?: boolean
|
|
24
|
+
pauseOnHover?: boolean
|
|
25
|
+
/** @private. Config for notification holder style. Safe to remove if refactor */
|
|
26
|
+
className?: (placement: Placement) => string
|
|
27
|
+
/** @private. Config for notification holder style. Safe to remove if refactor */
|
|
28
|
+
style?: (placement: Placement) => CSSProperties
|
|
29
|
+
/** @private Trigger when all the notification closed. */
|
|
30
|
+
onAllRemoved?: VoidFunction
|
|
31
|
+
stack?: StackConfig
|
|
32
|
+
/** @private Slot for style in Notifications */
|
|
33
|
+
renderNotifications?: NotificationsProps['renderNotifications']
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NotificationAPI {
|
|
37
|
+
open: (config: OptionalConfig) => void
|
|
38
|
+
close: (key: Key) => void
|
|
39
|
+
destroy: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface OpenTask {
|
|
43
|
+
type: 'open'
|
|
44
|
+
config: OpenConfig
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface CloseTask {
|
|
48
|
+
type: 'close'
|
|
49
|
+
key: Key
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface DestroyTask {
|
|
53
|
+
type: 'destroy'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type Task = OpenTask | CloseTask | DestroyTask
|
|
57
|
+
|
|
58
|
+
let uniqueKey = 0
|
|
59
|
+
|
|
60
|
+
function mergeConfig<T>(...objList: Partial<T>[]): T {
|
|
61
|
+
const clone: any = {}
|
|
62
|
+
|
|
63
|
+
objList.forEach((obj: any) => {
|
|
64
|
+
if (obj) {
|
|
65
|
+
Object.keys(obj).forEach((key) => {
|
|
66
|
+
const val = obj[key]
|
|
67
|
+
|
|
68
|
+
if (val !== undefined) {
|
|
69
|
+
clone[key] = val
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return clone
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default function useNotification(rootConfig: NotificationConfig = {}) {
|
|
79
|
+
const {
|
|
80
|
+
getContainer = defaultGetContainer,
|
|
81
|
+
motion,
|
|
82
|
+
prefixCls,
|
|
83
|
+
maxCount,
|
|
84
|
+
className,
|
|
85
|
+
style,
|
|
86
|
+
onAllRemoved,
|
|
87
|
+
stack,
|
|
88
|
+
renderNotifications,
|
|
89
|
+
...shareConfig
|
|
90
|
+
} = rootConfig
|
|
91
|
+
const container = shallowRef<HTMLElement | ShadowRoot>()
|
|
92
|
+
|
|
93
|
+
const notificationRef = shallowRef<NotificationsRef>()
|
|
94
|
+
|
|
95
|
+
const contextHolder = () => (
|
|
96
|
+
<Notifications
|
|
97
|
+
container={container.value}
|
|
98
|
+
ref={notificationRef}
|
|
99
|
+
prefixCls={prefixCls}
|
|
100
|
+
motion={motion}
|
|
101
|
+
maxCount={maxCount}
|
|
102
|
+
className={className}
|
|
103
|
+
style={style}
|
|
104
|
+
onAllRemoved={onAllRemoved}
|
|
105
|
+
stack={stack}
|
|
106
|
+
renderNotifications={renderNotifications}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
const taskQueue = shallowRef<Task[]>([])
|
|
111
|
+
|
|
112
|
+
// ========================= Refs =========================
|
|
113
|
+
|
|
114
|
+
const api: NotificationAPI = {
|
|
115
|
+
open(config) {
|
|
116
|
+
const mergedConfig = mergeConfig(shareConfig, config)
|
|
117
|
+
if (mergedConfig.key === null || mergedConfig.key === undefined) {
|
|
118
|
+
mergedConfig.key = `vc-notification-${uniqueKey}`
|
|
119
|
+
uniqueKey += 1
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
taskQueue.value = [...taskQueue.value, { type: 'open', config: mergedConfig }]
|
|
123
|
+
},
|
|
124
|
+
close(key) {
|
|
125
|
+
taskQueue.value = [...taskQueue.value, { type: 'close', key }]
|
|
126
|
+
},
|
|
127
|
+
destroy() {
|
|
128
|
+
taskQueue.value = [...taskQueue.value, { type: 'destroy' }]
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ======================= Container ======================
|
|
133
|
+
// React 18 should all in effect that we will check container in each render
|
|
134
|
+
// Which means getContainer should be stable.
|
|
135
|
+
onMounted(
|
|
136
|
+
() => {
|
|
137
|
+
container.value = getContainer()
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
watch(taskQueue, () => {
|
|
141
|
+
if (notificationRef.value && taskQueue.value.length) {
|
|
142
|
+
taskQueue.value.forEach((task) => {
|
|
143
|
+
switch (task.type) {
|
|
144
|
+
case 'open':
|
|
145
|
+
notificationRef.value?.open(task.config)
|
|
146
|
+
break
|
|
147
|
+
case 'close':
|
|
148
|
+
notificationRef.value?.close(task.key)
|
|
149
|
+
break
|
|
150
|
+
case 'destroy':
|
|
151
|
+
notificationRef.value?.destroy()
|
|
152
|
+
break
|
|
153
|
+
default:
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
taskQueue.value = taskQueue.value.filter(task => !taskQueue.value.includes(task))
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// ======================== Return ========================
|
|
162
|
+
return [api, contextHolder] as [NotificationAPI, () => VueNode]
|
|
163
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComputedRef, MaybeRef, ToRefs } from 'vue'
|
|
2
|
+
import type { StackConfig } from '../interface'
|
|
3
|
+
import { computed, reactive, toRefs, unref, watchEffect } from 'vue'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_OFFSET = 8
|
|
6
|
+
const DEFAULT_THRESHOLD = 3
|
|
7
|
+
const DEFAULT_GAP = 16
|
|
8
|
+
|
|
9
|
+
type StackParams = Exclude<StackConfig, boolean>
|
|
10
|
+
|
|
11
|
+
type UseStack = (config?: MaybeRef<StackConfig | undefined>) => [ComputedRef<boolean>, ToRefs<StackParams>]
|
|
12
|
+
|
|
13
|
+
const useStack: UseStack = (config) => {
|
|
14
|
+
const result: StackParams = reactive({
|
|
15
|
+
offset: DEFAULT_OFFSET,
|
|
16
|
+
threshold: DEFAULT_THRESHOLD,
|
|
17
|
+
gap: DEFAULT_GAP,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
watchEffect(() => {
|
|
21
|
+
const _config = unref(config)
|
|
22
|
+
if (_config && typeof _config === 'object') {
|
|
23
|
+
result.offset = _config.offset ?? DEFAULT_OFFSET
|
|
24
|
+
result.threshold = _config.threshold ?? DEFAULT_THRESHOLD
|
|
25
|
+
result.gap = _config.gap ?? DEFAULT_GAP
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return [computed(() => !!unref(config)), toRefs(result)]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default useStack
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NotificationAPI, NotificationConfig } from './hooks/useNotification'
|
|
2
|
+
import useNotification from './hooks/useNotification'
|
|
3
|
+
import Notice from './Notice'
|
|
4
|
+
import { useNotificationProvider } from './NotificationProvider'
|
|
5
|
+
|
|
6
|
+
export { Notice, useNotification, useNotificationProvider }
|
|
7
|
+
export type { NotificationAPI, NotificationConfig }
|
|
8
|
+
|
|
9
|
+
export type { NoticeProps } from './Notice'
|
package/src/interface.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { VueNode } from '@v-c/util/dist/type'
|
|
2
|
+
import type { CSSProperties } from 'vue'
|
|
3
|
+
|
|
4
|
+
export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'
|
|
5
|
+
|
|
6
|
+
type NoticeSemanticProps = 'wrapper'
|
|
7
|
+
|
|
8
|
+
export type Key = string | number
|
|
9
|
+
export interface NoticeConfig {
|
|
10
|
+
content?: VueNode
|
|
11
|
+
duration?: number | false | null
|
|
12
|
+
showProgress?: boolean
|
|
13
|
+
pauseOnHover?: boolean
|
|
14
|
+
closeIcon?: VueNode
|
|
15
|
+
closable?:
|
|
16
|
+
| boolean
|
|
17
|
+
| ({ closeIcon?: VueNode; onClose?: VoidFunction } & Record<string, any>)
|
|
18
|
+
className?: string
|
|
19
|
+
style?: CSSProperties
|
|
20
|
+
classNames?: {
|
|
21
|
+
[key in NoticeSemanticProps]?: string;
|
|
22
|
+
}
|
|
23
|
+
styles?: {
|
|
24
|
+
[key in NoticeSemanticProps]?: CSSProperties;
|
|
25
|
+
}
|
|
26
|
+
/** @private Internal usage. Do not override in your code */
|
|
27
|
+
props?: Record<string, any>
|
|
28
|
+
|
|
29
|
+
onClose?: VoidFunction
|
|
30
|
+
onClick?: (event: Event) => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface OpenConfig extends NoticeConfig {
|
|
34
|
+
key: Key
|
|
35
|
+
placement?: Placement
|
|
36
|
+
content?: VueNode
|
|
37
|
+
duration?: number | false | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type InnerOpenConfig = OpenConfig & { times?: number }
|
|
41
|
+
|
|
42
|
+
export type Placements = Partial<Record<Placement, OpenConfig[]>>
|
|
43
|
+
|
|
44
|
+
export type StackConfig
|
|
45
|
+
= | boolean
|
|
46
|
+
| {
|
|
47
|
+
/**
|
|
48
|
+
* When number is greater than threshold, notifications will be stacked together.
|
|
49
|
+
* @default 3
|
|
50
|
+
*/
|
|
51
|
+
threshold?: number
|
|
52
|
+
/**
|
|
53
|
+
* Offset when notifications are stacked together.
|
|
54
|
+
* @default 8
|
|
55
|
+
*/
|
|
56
|
+
offset?: number
|
|
57
|
+
/**
|
|
58
|
+
* Spacing between each notification when expanded.
|
|
59
|
+
*/
|
|
60
|
+
gap?: number
|
|
61
|
+
}
|
package/tsconfig.json
ADDED
package/vite.config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { UserConfig } from 'vite'
|
|
2
|
+
import fg from 'fast-glob'
|
|
3
|
+
import { defineConfig, mergeConfig } from 'vite'
|
|
4
|
+
import { buildCommon } from '../../scripts/build.common'
|
|
5
|
+
|
|
6
|
+
const entry = fg.sync(['src/**/*.ts', 'src/**/*.tsx', '!src/**/*.test.ts', '!src/**/tests'])
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
...mergeConfig(buildCommon({
|
|
10
|
+
external: ['vue', 'classnames', /^@v-c\/util/],
|
|
11
|
+
}), {
|
|
12
|
+
build: {
|
|
13
|
+
lib: {
|
|
14
|
+
entry,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
} as UserConfig),
|
|
18
|
+
})
|