@v-c/notification 2.0.0 → 2.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/dist/NotificationList/index.d.ts +1 -1
- package/dist/NotificationList/index.js +7 -6
- package/dist/Notifications.js +1 -1
- package/package.json +12 -2
- package/bump.config.ts +0 -6
- package/docs/context.vue +0 -34
- package/docs/hooks.vue +0 -89
- package/docs/index.less +0 -265
- package/docs/maxCount.vue +0 -24
- package/docs/motion.ts +0 -33
- package/docs/notification.stories.vue +0 -31
- package/docs/showProgress.vue +0 -34
- package/docs/stack.vue +0 -39
- package/src/Notification.tsx +0 -326
- package/src/NotificationList/Content.tsx +0 -76
- package/src/NotificationList/index.tsx +0 -322
- package/src/NotificationProvider.tsx +0 -19
- package/src/Notifications.tsx +0 -153
- package/src/Progress.tsx +0 -27
- package/src/hooks/useClosable.ts +0 -53
- package/src/hooks/useListPosition/index.ts +0 -69
- package/src/hooks/useListPosition/useSizes.ts +0 -43
- package/src/hooks/useNoticeTimer.ts +0 -85
- package/src/hooks/useNotification.tsx +0 -181
- package/src/hooks/useStack.ts +0 -36
- package/src/index.ts +0 -50
- package/src/interface.ts +0 -45
- package/tests/notification.test.tsx +0 -70
- package/tsconfig.json +0 -7
- package/vite.config.ts +0 -19
- package/vitest.config.ts +0 -11
package/src/Notification.tsx
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
import type { Component, CSSProperties, HTMLAttributes } from 'vue'
|
|
2
|
-
import type { VueNode } from '@v-c/util/dist/type'
|
|
3
|
-
import type { ClosableType } from './hooks/useClosable'
|
|
4
|
-
import type { NotificationProgressProps } from './Progress'
|
|
5
|
-
import { clsx } from '@v-c/util'
|
|
6
|
-
import { computed, defineComponent, ref, shallowRef, watch } from 'vue'
|
|
7
|
-
import useClosable from './hooks/useClosable'
|
|
8
|
-
import useNoticeTimer from './hooks/useNoticeTimer'
|
|
9
|
-
import DefaultProgress from './Progress'
|
|
10
|
-
|
|
11
|
-
export interface NotificationClassNames {
|
|
12
|
-
wrapper?: string
|
|
13
|
-
root?: string
|
|
14
|
-
icon?: string
|
|
15
|
-
section?: string
|
|
16
|
-
title?: string
|
|
17
|
-
description?: string
|
|
18
|
-
actions?: string
|
|
19
|
-
close?: string
|
|
20
|
-
progress?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface NotificationStyles {
|
|
24
|
-
wrapper?: CSSProperties
|
|
25
|
-
root?: CSSProperties
|
|
26
|
-
icon?: CSSProperties
|
|
27
|
-
section?: CSSProperties
|
|
28
|
-
title?: CSSProperties
|
|
29
|
-
description?: CSSProperties
|
|
30
|
-
actions?: CSSProperties
|
|
31
|
-
close?: CSSProperties
|
|
32
|
-
progress?: CSSProperties
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ComponentsType {
|
|
36
|
-
progress?: Component<NotificationProgressProps>
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface NotificationProps {
|
|
40
|
-
// Style
|
|
41
|
-
prefixCls: string
|
|
42
|
-
className?: string
|
|
43
|
-
style?: CSSProperties
|
|
44
|
-
classNames?: NotificationClassNames
|
|
45
|
-
styles?: NotificationStyles
|
|
46
|
-
components?: ComponentsType
|
|
47
|
-
|
|
48
|
-
// UI
|
|
49
|
-
title?: VueNode
|
|
50
|
-
description?: VueNode
|
|
51
|
-
icon?: VueNode
|
|
52
|
-
actions?: VueNode
|
|
53
|
-
role?: string
|
|
54
|
-
closable?: ClosableType
|
|
55
|
-
offset?: number
|
|
56
|
-
notificationIndex?: number
|
|
57
|
-
stackInThreshold?: boolean
|
|
58
|
-
props?: HTMLAttributes & Record<string, any>
|
|
59
|
-
|
|
60
|
-
// Behavior
|
|
61
|
-
duration?: number | false | null
|
|
62
|
-
showProgress?: boolean
|
|
63
|
-
times?: number
|
|
64
|
-
hovering?: boolean
|
|
65
|
-
pauseOnHover?: boolean
|
|
66
|
-
|
|
67
|
-
// Function
|
|
68
|
-
onClick?: (e: MouseEvent) => void
|
|
69
|
-
onMouseEnter?: (e: MouseEvent) => void
|
|
70
|
-
onMouseLeave?: (e: MouseEvent) => void
|
|
71
|
-
/** @deprecated Please use `closable.onClose` instead. */
|
|
72
|
-
onClose?: () => void
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface NoticeStyle extends CSSProperties {
|
|
76
|
-
'--notification-index'?: number
|
|
77
|
-
'--notification-y'?: string
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const Notification = defineComponent<NotificationProps>(
|
|
81
|
-
(props, { attrs, expose }) => {
|
|
82
|
-
const nodeRef = ref<HTMLDivElement | null>(null)
|
|
83
|
-
const percent = shallowRef(0)
|
|
84
|
-
const hovering = shallowRef(false)
|
|
85
|
-
|
|
86
|
-
expose({
|
|
87
|
-
nativeElement: nodeRef,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// ========================= Close ==========================
|
|
91
|
-
const closableRef = computed(() => props.closable)
|
|
92
|
-
const [mergedClosable, closableConfig, closeBtnAriaProps] = useClosable(closableRef)
|
|
93
|
-
|
|
94
|
-
const onInternalClose = () => {
|
|
95
|
-
closableConfig.value.onClose?.()
|
|
96
|
-
props.onClose?.()
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ======================== Duration ========================
|
|
100
|
-
const mergedDuration = computed(() => {
|
|
101
|
-
if (props.duration === undefined) {
|
|
102
|
-
return 4.5
|
|
103
|
-
}
|
|
104
|
-
return props.duration
|
|
105
|
-
})
|
|
106
|
-
const [onResume, onPause] = useNoticeTimer(
|
|
107
|
-
mergedDuration,
|
|
108
|
-
onInternalClose,
|
|
109
|
-
(next) => {
|
|
110
|
-
percent.value = next
|
|
111
|
-
},
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
// Sync pause/resume to forced and local hover.
|
|
115
|
-
watch(
|
|
116
|
-
[
|
|
117
|
-
() => props.hovering,
|
|
118
|
-
hovering,
|
|
119
|
-
() => props.pauseOnHover,
|
|
120
|
-
],
|
|
121
|
-
() => {
|
|
122
|
-
const pauseOnHover = props.pauseOnHover ?? true
|
|
123
|
-
if (!pauseOnHover) {
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
if (props.hovering) {
|
|
127
|
-
onPause()
|
|
128
|
-
}
|
|
129
|
-
else if (!hovering.value) {
|
|
130
|
-
onResume()
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
{ immediate: true },
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
// Cache offset/notificationIndex so transitions still have a value
|
|
137
|
-
// even when the controlling list omits them temporarily.
|
|
138
|
-
const offsetRef = shallowRef(props.offset)
|
|
139
|
-
const indexRef = shallowRef(props.notificationIndex)
|
|
140
|
-
watch(() => props.offset, (next) => {
|
|
141
|
-
if (next !== undefined) {
|
|
142
|
-
offsetRef.value = next
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
watch(() => props.notificationIndex, (next) => {
|
|
146
|
-
if (next !== undefined) {
|
|
147
|
-
indexRef.value = next
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
// ======================== Hover ==========================
|
|
152
|
-
const onInternalMouseEnter = (e: MouseEvent) => {
|
|
153
|
-
hovering.value = true
|
|
154
|
-
if ((props.pauseOnHover ?? true)) {
|
|
155
|
-
onPause()
|
|
156
|
-
}
|
|
157
|
-
props.onMouseEnter?.(e)
|
|
158
|
-
;(props.props?.onMouseenter as any)?.(e)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const onInternalMouseLeave = (e: MouseEvent) => {
|
|
162
|
-
hovering.value = false
|
|
163
|
-
const pauseOnHover = props.pauseOnHover ?? true
|
|
164
|
-
if (pauseOnHover && !props.hovering) {
|
|
165
|
-
onResume()
|
|
166
|
-
}
|
|
167
|
-
props.onMouseLeave?.(e)
|
|
168
|
-
;(props.props?.onMouseleave as any)?.(e)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const onInternalCloseClick = (e: MouseEvent) => {
|
|
172
|
-
e.preventDefault()
|
|
173
|
-
e.stopPropagation()
|
|
174
|
-
onInternalClose()
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return () => {
|
|
178
|
-
const {
|
|
179
|
-
prefixCls,
|
|
180
|
-
className,
|
|
181
|
-
style,
|
|
182
|
-
classNames: ncs,
|
|
183
|
-
styles: nss,
|
|
184
|
-
components,
|
|
185
|
-
title,
|
|
186
|
-
description,
|
|
187
|
-
icon,
|
|
188
|
-
actions,
|
|
189
|
-
role,
|
|
190
|
-
stackInThreshold,
|
|
191
|
-
props: rootProps,
|
|
192
|
-
showProgress,
|
|
193
|
-
duration,
|
|
194
|
-
} = props
|
|
195
|
-
|
|
196
|
-
const noticePrefixCls = `${prefixCls}-notice`
|
|
197
|
-
|
|
198
|
-
// ======================== Content =========================
|
|
199
|
-
const titleNode = title !== undefined && title !== null
|
|
200
|
-
? (
|
|
201
|
-
<div class={clsx(`${noticePrefixCls}-title`, ncs?.title)} style={nss?.title}>
|
|
202
|
-
{title}
|
|
203
|
-
</div>
|
|
204
|
-
)
|
|
205
|
-
: null
|
|
206
|
-
|
|
207
|
-
const descNode = description !== undefined && description !== null
|
|
208
|
-
? (
|
|
209
|
-
<div class={clsx(`${noticePrefixCls}-description`, ncs?.description)} style={nss?.description}>
|
|
210
|
-
{description}
|
|
211
|
-
</div>
|
|
212
|
-
)
|
|
213
|
-
: null
|
|
214
|
-
|
|
215
|
-
const hasTitle = titleNode !== null
|
|
216
|
-
const hasDescription = descNode !== null
|
|
217
|
-
|
|
218
|
-
let contentNode: VueNode = null
|
|
219
|
-
if (hasTitle && hasDescription) {
|
|
220
|
-
contentNode = (
|
|
221
|
-
<div class={clsx(`${noticePrefixCls}-section`, ncs?.section)} style={nss?.section}>
|
|
222
|
-
{titleNode}
|
|
223
|
-
{descNode}
|
|
224
|
-
</div>
|
|
225
|
-
)
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
contentNode = titleNode || descNode
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (icon !== undefined && icon !== null) {
|
|
232
|
-
contentNode = (
|
|
233
|
-
<div class={clsx(`${noticePrefixCls}-wrapper`, ncs?.wrapper)} style={nss?.wrapper}>
|
|
234
|
-
<div class={clsx(`${noticePrefixCls}-icon`, ncs?.icon)} style={nss?.icon}>
|
|
235
|
-
{icon}
|
|
236
|
-
</div>
|
|
237
|
-
{contentNode}
|
|
238
|
-
</div>
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const actionsNode = actions
|
|
243
|
-
? (
|
|
244
|
-
<div class={clsx(`${noticePrefixCls}-actions`, ncs?.actions)} style={nss?.actions}>
|
|
245
|
-
{actions}
|
|
246
|
-
</div>
|
|
247
|
-
)
|
|
248
|
-
: null
|
|
249
|
-
|
|
250
|
-
// ========================= Render =========================
|
|
251
|
-
const mergedOffset = props.offset ?? offsetRef.value
|
|
252
|
-
const mergedIndex = props.notificationIndex ?? indexRef.value ?? 0
|
|
253
|
-
const safePercent = Math.min(Math.max(percent.value * 100, 0), 100)
|
|
254
|
-
const validPercent = 100 - safePercent
|
|
255
|
-
const ProgressComponent = (components?.progress || DefaultProgress) as any
|
|
256
|
-
|
|
257
|
-
const mergedStyle: NoticeStyle = {
|
|
258
|
-
'--notification-index': mergedIndex,
|
|
259
|
-
...nss?.root,
|
|
260
|
-
...style,
|
|
261
|
-
}
|
|
262
|
-
if (mergedOffset !== undefined) {
|
|
263
|
-
mergedStyle['--notification-y'] = `${mergedOffset}px`
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const mergedRole = role ?? (rootProps as any)?.role ?? 'alert'
|
|
267
|
-
|
|
268
|
-
return (
|
|
269
|
-
<div
|
|
270
|
-
{...rootProps}
|
|
271
|
-
ref={nodeRef}
|
|
272
|
-
role={mergedRole}
|
|
273
|
-
data-notification-index={mergedIndex}
|
|
274
|
-
class={clsx(
|
|
275
|
-
noticePrefixCls,
|
|
276
|
-
className,
|
|
277
|
-
(attrs as any).class,
|
|
278
|
-
ncs?.root,
|
|
279
|
-
{
|
|
280
|
-
[`${noticePrefixCls}-closable`]: mergedClosable.value,
|
|
281
|
-
[`${noticePrefixCls}-stack-in-threshold`]: stackInThreshold,
|
|
282
|
-
},
|
|
283
|
-
)}
|
|
284
|
-
style={{
|
|
285
|
-
...mergedStyle,
|
|
286
|
-
...((attrs as any).style as CSSProperties | undefined),
|
|
287
|
-
}}
|
|
288
|
-
onClick={props.onClick}
|
|
289
|
-
onMouseenter={onInternalMouseEnter}
|
|
290
|
-
onMouseleave={onInternalMouseLeave}
|
|
291
|
-
>
|
|
292
|
-
{contentNode}
|
|
293
|
-
{actionsNode}
|
|
294
|
-
|
|
295
|
-
{mergedClosable.value && (
|
|
296
|
-
<button
|
|
297
|
-
class={clsx(`${noticePrefixCls}-close`, ncs?.close)}
|
|
298
|
-
aria-label="Close"
|
|
299
|
-
{...closeBtnAriaProps.value}
|
|
300
|
-
style={nss?.close}
|
|
301
|
-
onClick={onInternalCloseClick}
|
|
302
|
-
>
|
|
303
|
-
{closableConfig.value.closeIcon}
|
|
304
|
-
</button>
|
|
305
|
-
)}
|
|
306
|
-
|
|
307
|
-
{showProgress && typeof duration === 'number' && duration > 0 && (
|
|
308
|
-
<ProgressComponent
|
|
309
|
-
{...{
|
|
310
|
-
className: clsx(`${noticePrefixCls}-progress`, ncs?.progress),
|
|
311
|
-
percent: validPercent,
|
|
312
|
-
style: nss?.progress,
|
|
313
|
-
} as NotificationProgressProps}
|
|
314
|
-
/>
|
|
315
|
-
)}
|
|
316
|
-
</div>
|
|
317
|
-
)
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
name: 'Notification',
|
|
322
|
-
inheritAttrs: false,
|
|
323
|
-
},
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
export default Notification
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type { CSSProperties } from 'vue'
|
|
2
|
-
import { clsx } from '@v-c/util'
|
|
3
|
-
import { defineComponent, ref } from 'vue'
|
|
4
|
-
|
|
5
|
-
export interface ContentProps {
|
|
6
|
-
listPrefixCls: string
|
|
7
|
-
height: number
|
|
8
|
-
topNoticeHeight?: number
|
|
9
|
-
topNoticeWidth?: number
|
|
10
|
-
className?: string
|
|
11
|
-
style?: CSSProperties
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface ContentStyle extends CSSProperties {
|
|
15
|
-
'--top-notificiation-height': string
|
|
16
|
-
'--top-notificiation-width': string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const Content = defineComponent<ContentProps>(
|
|
20
|
-
(props, { slots, expose }) => {
|
|
21
|
-
const contentRef = ref<HTMLDivElement | null>(null)
|
|
22
|
-
let prevHeight = props.height
|
|
23
|
-
|
|
24
|
-
expose({
|
|
25
|
-
nativeElement: contentRef,
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
return () => {
|
|
29
|
-
const {
|
|
30
|
-
listPrefixCls,
|
|
31
|
-
height,
|
|
32
|
-
topNoticeHeight = 0,
|
|
33
|
-
topNoticeWidth = 0,
|
|
34
|
-
className,
|
|
35
|
-
style,
|
|
36
|
-
} = props
|
|
37
|
-
|
|
38
|
-
const heightStatus = height < prevHeight ? 'decrease' : 'increase'
|
|
39
|
-
prevHeight = height
|
|
40
|
-
|
|
41
|
-
const contentPrefixCls = `${listPrefixCls}-content`
|
|
42
|
-
// Force height to a string so Vue's style patcher always re-applies the
|
|
43
|
-
// unit. Passing a plain number (e.g. 0 → 216) was getting silently
|
|
44
|
-
// skipped during patch — the CSS variables on the same vnode style
|
|
45
|
-
// were applied, but `height: 216` was not patched onto the DOM until
|
|
46
|
-
// a manual `inst.update()` was invoked.
|
|
47
|
-
const contentStyle: ContentStyle = {
|
|
48
|
-
...style,
|
|
49
|
-
height: `${height}px`,
|
|
50
|
-
'--top-notificiation-height': `${topNoticeHeight}px`,
|
|
51
|
-
'--top-notificiation-width': `${topNoticeWidth}px`,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div
|
|
56
|
-
ref={contentRef}
|
|
57
|
-
class={clsx(contentPrefixCls, `${contentPrefixCls}-${heightStatus}`, className)}
|
|
58
|
-
style={contentStyle}
|
|
59
|
-
>
|
|
60
|
-
{slots.default?.()}
|
|
61
|
-
</div>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'NotificationListContent',
|
|
67
|
-
inheritAttrs: false,
|
|
68
|
-
// Declare runtime props so Vue tracks reactive reads inside the render
|
|
69
|
-
// closure. Without this, `props.height` in `const { height } = props`
|
|
70
|
-
// does not subscribe to updates, so the rendered `height: 0px` stays
|
|
71
|
-
// stale even after position recomputes with measured node sizes.
|
|
72
|
-
props: ['listPrefixCls', 'height', 'topNoticeHeight', 'topNoticeWidth', 'className', 'style'] as any,
|
|
73
|
-
},
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
export default Content
|