@v-c/notification 0.0.3 → 2.0.0-beta.1
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/Notification.d.ts +286 -0
- package/dist/Notification.js +237 -0
- package/dist/NotificationList/Content.d.ts +88 -0
- package/dist/NotificationList/Content.js +74 -0
- package/dist/NotificationList/index.d.ts +156 -0
- package/dist/NotificationList/index.js +204 -0
- package/dist/NotificationProvider.d.ts +20 -1
- package/dist/NotificationProvider.js +16 -3
- package/dist/Notifications.d.ts +136 -8
- package/dist/Notifications.js +118 -109
- package/dist/Progress.d.ts +8 -0
- package/dist/Progress.js +18 -0
- package/dist/hooks/useClosable.d.ts +22 -0
- package/dist/hooks/useClosable.js +33 -0
- package/dist/hooks/useListPosition/index.d.ts +17 -0
- package/dist/hooks/useListPosition/index.js +48 -0
- package/dist/hooks/useListPosition/useSizes.d.ts +13 -0
- package/dist/hooks/useListPosition/useSizes.js +29 -0
- package/dist/hooks/useNoticeTimer.d.ts +6 -0
- package/dist/hooks/useNoticeTimer.js +71 -0
- package/dist/hooks/useNotification.d.ts +8 -24
- package/dist/hooks/useNotification.js +33 -22
- package/dist/hooks/useStack.d.ts +8 -4
- package/dist/hooks/useStack.js +15 -18
- package/dist/index.d.ts +7 -5
- package/dist/index.js +5 -3
- package/docs/context.vue +1 -1
- package/docs/hooks.vue +4 -4
- package/docs/index.less +62 -143
- package/docs/maxCount.vue +1 -1
- package/docs/showProgress.vue +2 -2
- package/docs/stack.vue +1 -1
- package/package.json +5 -4
- package/src/Notification.tsx +363 -0
- package/src/NotificationList/Content.tsx +84 -0
- package/src/NotificationList/index.tsx +298 -0
- package/src/NotificationProvider.tsx +23 -3
- package/src/Notifications.tsx +103 -87
- package/src/Progress.tsx +23 -0
- package/src/hooks/useClosable.ts +54 -0
- package/src/hooks/useListPosition/index.ts +85 -0
- package/src/hooks/useListPosition/useSizes.ts +42 -0
- package/src/hooks/useNoticeTimer.ts +96 -0
- package/src/hooks/useNotification.tsx +54 -80
- package/src/hooks/useStack.ts +26 -18
- package/src/index.ts +31 -5
- package/tests/index.spec.tsx +200 -0
- package/vite.config.ts +4 -3
- package/vitest.config.ts +3 -1
- package/dist/Notice.cjs +0 -235
- package/dist/Notice.d.ts +0 -15
- package/dist/Notice.js +0 -227
- package/dist/NoticeList.cjs +0 -170
- package/dist/NoticeList.d.ts +0 -13
- package/dist/NoticeList.js +0 -164
- package/dist/NotificationProvider.cjs +0 -14
- package/dist/Notifications.cjs +0 -146
- package/dist/_virtual/rolldown_runtime.cjs +0 -21
- package/dist/hooks/useNotification.cjs +0 -93
- package/dist/hooks/useStack.cjs +0 -27
- package/dist/index.cjs +0 -7
- package/dist/interface.cjs +0 -1
- package/dist/interface.d.ts +0 -55
- package/dist/interface.js +0 -0
- package/src/Notice.tsx +0 -212
- package/src/NoticeList.tsx +0 -219
- package/src/interface.ts +0 -61
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import type { CSSProperties, PropType, TransitionGroupProps } from 'vue'
|
|
2
|
+
import type { StackInput } from '../hooks/useStack'
|
|
3
|
+
import type {
|
|
4
|
+
ComponentsType,
|
|
5
|
+
NotificationClassNames as NoticeClassNames,
|
|
6
|
+
NotificationStyles as NoticeStyles,
|
|
7
|
+
NotificationProps,
|
|
8
|
+
} from '../Notification'
|
|
9
|
+
import { classNames as clsx } from '@v-c/util'
|
|
10
|
+
import { getTransitionGroupProps } from '@v-c/util/dist/utils/transition'
|
|
11
|
+
import { computed, defineComponent, inject, nextTick, onMounted, ref, shallowRef, toRef, watch } from 'vue'
|
|
12
|
+
import useListPosition from '../hooks/useListPosition'
|
|
13
|
+
import useStack from '../hooks/useStack'
|
|
14
|
+
import Notification from '../Notification'
|
|
15
|
+
import { NotificationContext } from '../NotificationProvider'
|
|
16
|
+
import Content from './Content'
|
|
17
|
+
|
|
18
|
+
type Key = string | number | symbol
|
|
19
|
+
|
|
20
|
+
export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'
|
|
21
|
+
|
|
22
|
+
export type { StackConfig, StackInput } from '../hooks/useStack'
|
|
23
|
+
export type { ComponentsType } from '../Notification'
|
|
24
|
+
|
|
25
|
+
export interface NotificationListConfig extends Omit<NotificationProps, 'prefixCls'> {
|
|
26
|
+
key: Key
|
|
27
|
+
placement?: Placement
|
|
28
|
+
times?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface NotificationClassNames extends NoticeClassNames {
|
|
32
|
+
list?: string
|
|
33
|
+
listContent?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NotificationStyles extends NoticeStyles {
|
|
37
|
+
list?: CSSProperties
|
|
38
|
+
listContent?: CSSProperties
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface NotificationListProps {
|
|
42
|
+
configList?: NotificationListConfig[]
|
|
43
|
+
prefixCls?: string
|
|
44
|
+
placement: Placement
|
|
45
|
+
pauseOnHover?: boolean
|
|
46
|
+
classNames?: NotificationClassNames
|
|
47
|
+
styles?: NotificationStyles
|
|
48
|
+
components?: ComponentsType
|
|
49
|
+
stack?: StackInput
|
|
50
|
+
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
51
|
+
class?: string
|
|
52
|
+
style?: CSSProperties
|
|
53
|
+
onNoticeClose?: (key: Key) => void
|
|
54
|
+
onAllRemoved?: (placement: Placement) => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const noticeSlotKeys: (keyof NoticeClassNames)[] = [
|
|
58
|
+
'wrapper',
|
|
59
|
+
'root',
|
|
60
|
+
'icon',
|
|
61
|
+
'section',
|
|
62
|
+
'title',
|
|
63
|
+
'description',
|
|
64
|
+
'actions',
|
|
65
|
+
'close',
|
|
66
|
+
'progress',
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
function fillClassNames(
|
|
70
|
+
classNamesList: (NotificationClassNames | undefined)[],
|
|
71
|
+
): NotificationClassNames {
|
|
72
|
+
return noticeSlotKeys.reduce<NotificationClassNames>((merged, key) => {
|
|
73
|
+
merged[key] = clsx(...classNamesList.map(cn => cn?.[key]))
|
|
74
|
+
return merged
|
|
75
|
+
}, {})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function fillStyles(stylesList: (NotificationStyles | undefined)[]): NotificationStyles {
|
|
79
|
+
return noticeSlotKeys.reduce<NotificationStyles>((merged, key) => {
|
|
80
|
+
merged[key] = Object.assign({}, ...stylesList.map(s => s?.[key]))
|
|
81
|
+
return merged
|
|
82
|
+
}, {})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const NotificationList = defineComponent({
|
|
86
|
+
name: 'NotificationList',
|
|
87
|
+
inheritAttrs: false,
|
|
88
|
+
props: {
|
|
89
|
+
configList: { type: Array as PropType<NotificationListConfig[]>, default: () => [] },
|
|
90
|
+
prefixCls: { type: String, default: 'vc-notification' },
|
|
91
|
+
placement: { type: String as PropType<Placement>, required: true },
|
|
92
|
+
pauseOnHover: { type: Boolean, default: undefined },
|
|
93
|
+
classNames: { type: Object as PropType<NotificationClassNames>, default: undefined },
|
|
94
|
+
styles: { type: Object as PropType<NotificationStyles>, default: undefined },
|
|
95
|
+
components: { type: Object as PropType<ComponentsType>, default: undefined },
|
|
96
|
+
stack: { type: [Boolean, Object] as PropType<StackInput>, default: undefined },
|
|
97
|
+
motion: {
|
|
98
|
+
type: [Object, Function] as PropType<NotificationListProps['motion']>,
|
|
99
|
+
default: undefined,
|
|
100
|
+
},
|
|
101
|
+
class: { type: String, default: undefined },
|
|
102
|
+
style: { type: Object as PropType<CSSProperties>, default: undefined },
|
|
103
|
+
onNoticeClose: { type: Function as PropType<(key: Key) => void>, default: undefined },
|
|
104
|
+
onAllRemoved: { type: Function as PropType<(placement: Placement) => void>, default: undefined },
|
|
105
|
+
},
|
|
106
|
+
setup(props, { attrs }) {
|
|
107
|
+
const ctx = inject(NotificationContext, ref({}))
|
|
108
|
+
|
|
109
|
+
// ========================== Data ==========================
|
|
110
|
+
const keys = computed(() =>
|
|
111
|
+
props.configList.map(config => ({
|
|
112
|
+
config,
|
|
113
|
+
key: String(config.key),
|
|
114
|
+
})),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// ===================== Motion Config ======================
|
|
118
|
+
const placementMotion = computed(() => {
|
|
119
|
+
if (typeof props.motion === 'function') {
|
|
120
|
+
return props.motion(props.placement)
|
|
121
|
+
}
|
|
122
|
+
return props.motion
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const motionGroupProps = computed<TransitionGroupProps>(() => {
|
|
126
|
+
const motionVal = placementMotion.value
|
|
127
|
+
if (!motionVal) {
|
|
128
|
+
return {}
|
|
129
|
+
}
|
|
130
|
+
if (motionVal.name) {
|
|
131
|
+
return getTransitionGroupProps(motionVal.name, motionVal as any)
|
|
132
|
+
}
|
|
133
|
+
return { ...motionVal }
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// ====================== Stack State =======================
|
|
137
|
+
const [stackEnabled, stackParams] = useStack(toRef(props, 'stack'))
|
|
138
|
+
const listHovering = shallowRef(false)
|
|
139
|
+
const expanded = computed(
|
|
140
|
+
() =>
|
|
141
|
+
stackEnabled.value
|
|
142
|
+
&& (listHovering.value || keys.value.length <= stackParams.value.threshold),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// ====================== Stack Layout ======================
|
|
146
|
+
const stackPosition = computed(() => {
|
|
147
|
+
if (!stackEnabled.value || expanded.value) {
|
|
148
|
+
return undefined
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
offset: stackParams.value.offset,
|
|
152
|
+
threshold: stackParams.value.threshold,
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// ====================== List Measure ======================
|
|
157
|
+
const gap = shallowRef(0)
|
|
158
|
+
const contentRef = shallowRef<{ nativeElement: HTMLElement | null } | null>(null)
|
|
159
|
+
const configListRef = computed(() => props.configList)
|
|
160
|
+
const [
|
|
161
|
+
notificationPosition,
|
|
162
|
+
setNodeSize,
|
|
163
|
+
totalHeight,
|
|
164
|
+
topNoticeHeight,
|
|
165
|
+
topNoticeWidth,
|
|
166
|
+
] = useListPosition(configListRef, stackPosition, gap)
|
|
167
|
+
|
|
168
|
+
const hasConfigList = computed(() => keys.value.length > 0)
|
|
169
|
+
|
|
170
|
+
function syncGap() {
|
|
171
|
+
const node = contentRef.value?.nativeElement
|
|
172
|
+
if (!node)
|
|
173
|
+
return
|
|
174
|
+
const { gap: cssGap, rowGap } = window.getComputedStyle(node)
|
|
175
|
+
const next = Number.parseFloat(rowGap || cssGap) || 0
|
|
176
|
+
if (next !== gap.value) {
|
|
177
|
+
gap.value = next
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
onMounted(syncGap)
|
|
182
|
+
watch(hasConfigList, () => {
|
|
183
|
+
nextTick(syncGap)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// ====================== All Removed =======================
|
|
187
|
+
function checkAllClosed() {
|
|
188
|
+
if (!props.placement) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
if (keys.value.length === 0) {
|
|
192
|
+
props.onAllRemoved?.(props.placement)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return () => {
|
|
197
|
+
const {
|
|
198
|
+
prefixCls = 'vc-notification',
|
|
199
|
+
placement,
|
|
200
|
+
classNames,
|
|
201
|
+
styles,
|
|
202
|
+
components,
|
|
203
|
+
pauseOnHover,
|
|
204
|
+
class: className,
|
|
205
|
+
style,
|
|
206
|
+
onNoticeClose,
|
|
207
|
+
} = props
|
|
208
|
+
|
|
209
|
+
const listPrefixCls = `${prefixCls}-list`
|
|
210
|
+
const ctxClassNames = (ctx.value as any)?.classNames
|
|
211
|
+
|
|
212
|
+
const renderItems = () =>
|
|
213
|
+
keys.value.map(({ config }) => {
|
|
214
|
+
const {
|
|
215
|
+
key,
|
|
216
|
+
placement: itemPlacement,
|
|
217
|
+
onClose: configOnClose,
|
|
218
|
+
...notificationConfig
|
|
219
|
+
} = config
|
|
220
|
+
const strKey = String(key)
|
|
221
|
+
const dataIndex = keys.value.findIndex(item => item.key === strKey)
|
|
222
|
+
const notificationIndex
|
|
223
|
+
= dataIndex === -1 ? undefined : keys.value.length - dataIndex - 1
|
|
224
|
+
const stackInThreshold
|
|
225
|
+
= stackEnabled.value
|
|
226
|
+
&& notificationIndex !== undefined
|
|
227
|
+
&& notificationIndex < stackParams.value.threshold
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<Notification
|
|
231
|
+
{...(notificationConfig as any)}
|
|
232
|
+
key={strKey}
|
|
233
|
+
ref={(el: any) => {
|
|
234
|
+
const node = el?.nativeElement ?? null
|
|
235
|
+
setNodeSize(strKey, node)
|
|
236
|
+
}}
|
|
237
|
+
prefixCls={prefixCls}
|
|
238
|
+
class={clsx(ctxClassNames?.notice, config.class)}
|
|
239
|
+
classNames={fillClassNames([classNames, config.classNames])}
|
|
240
|
+
styles={fillStyles([styles, config.styles])}
|
|
241
|
+
components={{ ...components, ...config.components }}
|
|
242
|
+
hovering={stackEnabled.value && listHovering.value}
|
|
243
|
+
pauseOnHover={config.pauseOnHover ?? pauseOnHover}
|
|
244
|
+
offset={notificationPosition.value.get(strKey)}
|
|
245
|
+
notificationIndex={notificationIndex}
|
|
246
|
+
stackInThreshold={!!stackInThreshold}
|
|
247
|
+
onClose={() => {
|
|
248
|
+
configOnClose?.()
|
|
249
|
+
onNoticeClose?.(key)
|
|
250
|
+
}}
|
|
251
|
+
/>
|
|
252
|
+
)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<div
|
|
257
|
+
class={clsx(
|
|
258
|
+
prefixCls,
|
|
259
|
+
listPrefixCls,
|
|
260
|
+
`${prefixCls}-${placement}`,
|
|
261
|
+
ctxClassNames?.list,
|
|
262
|
+
className,
|
|
263
|
+
(attrs as any).class,
|
|
264
|
+
classNames?.list,
|
|
265
|
+
{
|
|
266
|
+
[`${prefixCls}-stack`]: stackEnabled.value,
|
|
267
|
+
[`${prefixCls}-stack-expanded`]: expanded.value,
|
|
268
|
+
[`${listPrefixCls}-hovered`]: listHovering.value,
|
|
269
|
+
},
|
|
270
|
+
)}
|
|
271
|
+
onMouseenter={() => {
|
|
272
|
+
listHovering.value = true
|
|
273
|
+
}}
|
|
274
|
+
onMouseleave={() => {
|
|
275
|
+
listHovering.value = false
|
|
276
|
+
}}
|
|
277
|
+
style={{ ...styles?.list, ...style }}
|
|
278
|
+
>
|
|
279
|
+
<Content
|
|
280
|
+
ref={contentRef as any}
|
|
281
|
+
listPrefixCls={listPrefixCls}
|
|
282
|
+
height={totalHeight.value}
|
|
283
|
+
topNoticeHeight={topNoticeHeight.value}
|
|
284
|
+
topNoticeWidth={topNoticeWidth.value}
|
|
285
|
+
class={classNames?.listContent}
|
|
286
|
+
style={styles?.listContent}
|
|
287
|
+
motionProps={motionGroupProps.value}
|
|
288
|
+
onAfterLeave={checkAllClosed}
|
|
289
|
+
>
|
|
290
|
+
{renderItems()}
|
|
291
|
+
</Content>
|
|
292
|
+
</div>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
export default NotificationList
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
-
import { inject, provide, ref } from 'vue'
|
|
1
|
+
import type { InjectionKey, PropType, Ref } from 'vue'
|
|
2
|
+
import { computed, defineComponent, inject, provide, ref } from 'vue'
|
|
3
3
|
|
|
4
4
|
export interface NotificationContextProps {
|
|
5
5
|
classNames?: {
|
|
@@ -7,7 +7,10 @@ export interface NotificationContextProps {
|
|
|
7
7
|
list?: string
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
export const NotificationContext: InjectionKey<Ref<NotificationContextProps>> = Symbol(
|
|
12
|
+
'NotificationContext',
|
|
13
|
+
)
|
|
11
14
|
|
|
12
15
|
export function useNotificationProvider(props: Ref<NotificationContextProps>) {
|
|
13
16
|
provide(NotificationContext, props)
|
|
@@ -17,3 +20,20 @@ export function useNotificationProvider(props: Ref<NotificationContextProps>) {
|
|
|
17
20
|
export function useNotificationContext() {
|
|
18
21
|
return inject(NotificationContext, ref({}))
|
|
19
22
|
}
|
|
23
|
+
|
|
24
|
+
const NotificationProvider = defineComponent({
|
|
25
|
+
name: 'NotificationProvider',
|
|
26
|
+
props: {
|
|
27
|
+
classNames: {
|
|
28
|
+
type: Object as PropType<NotificationContextProps['classNames']>,
|
|
29
|
+
default: undefined,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
setup(props, { slots }) {
|
|
33
|
+
const ctx = computed<NotificationContextProps>(() => ({ classNames: props.classNames }))
|
|
34
|
+
provide(NotificationContext, ctx)
|
|
35
|
+
return () => slots.default?.()
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export default NotificationProvider
|
package/src/Notifications.tsx
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
import type { VueNode } from '@v-c/util/dist/type'
|
|
2
|
-
import type { CSSProperties, TransitionGroupProps } from 'vue'
|
|
3
|
-
import type {
|
|
2
|
+
import type { CSSProperties, PropType, TransitionGroupProps } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
ComponentsType,
|
|
5
|
+
NotificationClassNames,
|
|
6
|
+
NotificationListConfig,
|
|
7
|
+
NotificationStyles,
|
|
8
|
+
Placement,
|
|
9
|
+
StackInput,
|
|
10
|
+
} from './NotificationList'
|
|
4
11
|
import { defineComponent, shallowRef, Teleport, watch } from 'vue'
|
|
5
|
-
import
|
|
12
|
+
import NotificationList from './NotificationList'
|
|
13
|
+
|
|
14
|
+
type Key = string | number | symbol
|
|
6
15
|
|
|
7
16
|
export interface NotificationsProps {
|
|
8
17
|
prefixCls?: string
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
classNames?: NotificationClassNames
|
|
19
|
+
styles?: NotificationStyles
|
|
20
|
+
components?: ComponentsType
|
|
12
21
|
className?: (placement: Placement) => string
|
|
13
22
|
style?: (placement: Placement) => CSSProperties
|
|
23
|
+
|
|
24
|
+
container?: HTMLElement | ShadowRoot
|
|
25
|
+
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
26
|
+
|
|
27
|
+
maxCount?: number
|
|
28
|
+
pauseOnHover?: boolean
|
|
29
|
+
stack?: StackInput
|
|
30
|
+
|
|
14
31
|
onAllRemoved?: VoidFunction
|
|
15
|
-
stack?: StackConfig
|
|
16
32
|
renderNotifications?: (
|
|
17
33
|
node: VueNode,
|
|
18
34
|
info: { prefixCls: string, key: Key },
|
|
@@ -20,41 +36,50 @@ export interface NotificationsProps {
|
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
export interface NotificationsRef {
|
|
23
|
-
open: (config:
|
|
39
|
+
open: (config: NotificationListConfig) => void
|
|
24
40
|
close: (key: Key) => void
|
|
25
41
|
destroy: () => void
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
44
|
+
type Placements = Partial<Record<Placement, NotificationListConfig[]>>
|
|
45
|
+
|
|
46
|
+
const Notifications = defineComponent({
|
|
47
|
+
name: 'Notifications',
|
|
48
|
+
inheritAttrs: false,
|
|
49
|
+
props: {
|
|
50
|
+
prefixCls: { type: String, default: 'vc-notification' },
|
|
51
|
+
classNames: { type: Object as PropType<NotificationClassNames>, default: undefined },
|
|
52
|
+
styles: { type: Object as PropType<NotificationStyles>, default: undefined },
|
|
53
|
+
components: { type: Object as PropType<ComponentsType>, default: undefined },
|
|
54
|
+
className: { type: Function as PropType<(p: Placement) => string>, default: undefined },
|
|
55
|
+
style: { type: Function as PropType<(p: Placement) => CSSProperties>, default: undefined },
|
|
56
|
+
container: { type: Object as PropType<HTMLElement | ShadowRoot>, default: undefined },
|
|
57
|
+
motion: {
|
|
58
|
+
type: [Object, Function] as PropType<NotificationsProps['motion']>,
|
|
59
|
+
default: undefined,
|
|
60
|
+
},
|
|
61
|
+
maxCount: { type: Number, default: undefined },
|
|
62
|
+
pauseOnHover: { type: Boolean, default: undefined },
|
|
63
|
+
stack: { type: [Boolean, Object] as PropType<StackInput>, default: undefined },
|
|
64
|
+
onAllRemoved: { type: Function as PropType<VoidFunction>, default: undefined },
|
|
65
|
+
renderNotifications: {
|
|
66
|
+
type: Function as PropType<NotificationsProps['renderNotifications']>,
|
|
67
|
+
default: undefined,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
setup(props, { expose }) {
|
|
71
|
+
const configList = shallowRef<NotificationListConfig[]>([])
|
|
72
|
+
const placements = shallowRef<Placements>({})
|
|
73
|
+
const emptyRef = shallowRef(false)
|
|
45
74
|
|
|
46
|
-
// ========================= Refs =========================
|
|
47
75
|
expose({
|
|
48
|
-
open: (config:
|
|
76
|
+
open: (config: NotificationListConfig) => {
|
|
49
77
|
const list = configList.value
|
|
50
|
-
let clone = [...
|
|
51
|
-
// Replace if exist
|
|
78
|
+
let clone = [...list]
|
|
52
79
|
const index = clone.findIndex(item => item.key === config.key)
|
|
53
|
-
const innerConfig:
|
|
54
|
-
...config,
|
|
55
|
-
}
|
|
80
|
+
const innerConfig: NotificationListConfig = { ...config }
|
|
56
81
|
if (index >= 0) {
|
|
57
|
-
innerConfig.times = (
|
|
82
|
+
innerConfig.times = (list[index]?.times ?? 0) + 1
|
|
58
83
|
clone[index] = innerConfig
|
|
59
84
|
}
|
|
60
85
|
else {
|
|
@@ -67,98 +92,89 @@ const Notifications = defineComponent<NotificationsProps>(
|
|
|
67
92
|
}
|
|
68
93
|
configList.value = clone
|
|
69
94
|
},
|
|
70
|
-
close:
|
|
95
|
+
close: (key: Key) => {
|
|
96
|
+
configList.value = configList.value.filter(item => item.key !== key)
|
|
97
|
+
},
|
|
71
98
|
destroy: () => {
|
|
72
99
|
configList.value = []
|
|
73
100
|
},
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// ====================== Placements ======================
|
|
77
|
-
|
|
78
|
-
const placements = shallowRef<Placements>({})
|
|
101
|
+
} as NotificationsRef)
|
|
79
102
|
|
|
80
103
|
watch(
|
|
81
104
|
configList,
|
|
82
105
|
() => {
|
|
83
|
-
const
|
|
106
|
+
const next: Placements = {}
|
|
84
107
|
configList.value.forEach((config) => {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
nextPlacements[placement].push(config)
|
|
89
|
-
}
|
|
108
|
+
const placement = config.placement ?? 'topRight'
|
|
109
|
+
next[placement] = next[placement] || []
|
|
110
|
+
next[placement]!.push(config)
|
|
90
111
|
})
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const placement = _placement as Placement
|
|
94
|
-
nextPlacements[placement] = nextPlacements[placement] || []
|
|
112
|
+
Object.keys(placements.value).forEach((placement) => {
|
|
113
|
+
next[placement as Placement] = next[placement as Placement] || []
|
|
95
114
|
})
|
|
96
|
-
placements.value =
|
|
115
|
+
placements.value = next
|
|
97
116
|
},
|
|
117
|
+
{ immediate: true },
|
|
98
118
|
)
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
const onAllNoticeRemoved = (placement: Placement) => {
|
|
120
|
+
function onAllNoticeRemoved(placement: Placement) {
|
|
102
121
|
const clone = { ...placements.value }
|
|
103
|
-
|
|
104
|
-
if (!list.length) {
|
|
122
|
+
if (!(clone[placement] || []).length) {
|
|
105
123
|
delete clone[placement]
|
|
106
124
|
}
|
|
107
125
|
placements.value = clone
|
|
108
126
|
}
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
else if (emptyRef.value) {
|
|
120
|
-
// Trigger only when from exist to empty
|
|
121
|
-
props?.onAllRemoved?.()
|
|
122
|
-
emptyRef.value = false
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
)
|
|
128
|
+
watch(placements, () => {
|
|
129
|
+
if (Object.keys(placements.value).length > 0) {
|
|
130
|
+
emptyRef.value = true
|
|
131
|
+
}
|
|
132
|
+
else if (emptyRef.value) {
|
|
133
|
+
props.onAllRemoved?.()
|
|
134
|
+
emptyRef.value = false
|
|
135
|
+
}
|
|
136
|
+
})
|
|
126
137
|
|
|
127
138
|
return () => {
|
|
128
|
-
const { container } = props
|
|
129
|
-
const prefixCls = props.prefixCls ?? defaults.prefixCls ?? ''
|
|
130
|
-
// ======================== Render ========================
|
|
139
|
+
const { container, prefixCls = 'vc-notification' } = props
|
|
131
140
|
if (!container) {
|
|
132
141
|
return null
|
|
133
142
|
}
|
|
134
143
|
|
|
144
|
+
const placementList = Object.keys(placements.value) as Placement[]
|
|
145
|
+
|
|
135
146
|
return (
|
|
136
147
|
<Teleport to={container}>
|
|
137
|
-
{
|
|
138
|
-
const placementConfigList = placements.value[placement as Placement]
|
|
148
|
+
{placementList.map((placement) => {
|
|
139
149
|
const list = (
|
|
140
|
-
<
|
|
150
|
+
<NotificationList
|
|
141
151
|
key={placement}
|
|
142
|
-
configList={
|
|
143
|
-
placement={placement
|
|
152
|
+
configList={placements.value[placement]}
|
|
153
|
+
placement={placement}
|
|
144
154
|
prefixCls={prefixCls}
|
|
145
|
-
|
|
146
|
-
|
|
155
|
+
pauseOnHover={props.pauseOnHover}
|
|
156
|
+
classNames={props.classNames}
|
|
157
|
+
styles={props.styles}
|
|
158
|
+
components={props.components}
|
|
159
|
+
class={props.className?.(placement)}
|
|
160
|
+
style={props.style?.(placement)}
|
|
147
161
|
motion={props.motion}
|
|
148
162
|
stack={props.stack}
|
|
149
|
-
|
|
150
|
-
|
|
163
|
+
onNoticeClose={(key) => {
|
|
164
|
+
configList.value = configList.value.filter(item => item.key !== key)
|
|
165
|
+
}}
|
|
166
|
+
onAllRemoved={onAllNoticeRemoved}
|
|
151
167
|
/>
|
|
152
168
|
)
|
|
153
|
-
|
|
169
|
+
|
|
170
|
+
return props.renderNotifications
|
|
171
|
+
? props.renderNotifications(list, { prefixCls, key: placement })
|
|
172
|
+
: list
|
|
154
173
|
})}
|
|
155
174
|
</Teleport>
|
|
156
175
|
)
|
|
157
176
|
}
|
|
158
177
|
},
|
|
159
|
-
|
|
160
|
-
name: 'Notifications',
|
|
161
|
-
},
|
|
162
|
-
)
|
|
178
|
+
})
|
|
163
179
|
|
|
164
180
|
export default Notifications
|
package/src/Progress.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CSSProperties, FunctionalComponent } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface NotificationProgressProps {
|
|
4
|
+
class?: string
|
|
5
|
+
style?: CSSProperties
|
|
6
|
+
percent: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Progress: FunctionalComponent<NotificationProgressProps> = (props) => {
|
|
10
|
+
return (
|
|
11
|
+
<progress
|
|
12
|
+
class={props.class}
|
|
13
|
+
max="100"
|
|
14
|
+
value={props.percent}
|
|
15
|
+
style={props.style}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Progress.props = ['class', 'style', 'percent'] as any
|
|
21
|
+
Progress.inheritAttrs = false
|
|
22
|
+
|
|
23
|
+
export default Progress
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { VueNode } from '@v-c/util/dist/type'
|
|
2
|
+
import type { ComputedRef, MaybeRef } from 'vue'
|
|
3
|
+
import pickAttrs from '@v-c/util/dist/pickAttrs'
|
|
4
|
+
import { computed, unref } from 'vue'
|
|
5
|
+
|
|
6
|
+
export type ClosableConfig = {
|
|
7
|
+
closeIcon?: VueNode
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
onClose?: VoidFunction
|
|
10
|
+
} & Record<string, any>
|
|
11
|
+
|
|
12
|
+
export type ClosableType = boolean | ClosableConfig | null | undefined
|
|
13
|
+
|
|
14
|
+
export interface ParsedClosableConfig {
|
|
15
|
+
closeIcon: VueNode
|
|
16
|
+
disabled: boolean
|
|
17
|
+
onClose?: VoidFunction
|
|
18
|
+
[key: string]: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalize the closable option into an enabled flag, parsed config, and aria props.
|
|
23
|
+
*/
|
|
24
|
+
export default function useClosable(closable: MaybeRef<ClosableType>): [
|
|
25
|
+
ComputedRef<boolean>,
|
|
26
|
+
ComputedRef<ParsedClosableConfig>,
|
|
27
|
+
ComputedRef<Record<string, any>>,
|
|
28
|
+
] {
|
|
29
|
+
const closableObj = computed<ClosableConfig>(() => {
|
|
30
|
+
const value = unref(closable)
|
|
31
|
+
if (value === false) {
|
|
32
|
+
return { closeIcon: null, disabled: true }
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === 'object' && value !== null) {
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
37
|
+
return {}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const enabled = computed(() => !!unref(closable))
|
|
41
|
+
|
|
42
|
+
const closableConfig = computed<ParsedClosableConfig>(() => {
|
|
43
|
+
const obj = closableObj.value
|
|
44
|
+
return {
|
|
45
|
+
...obj,
|
|
46
|
+
closeIcon: 'closeIcon' in obj ? obj.closeIcon : '×',
|
|
47
|
+
disabled: obj.disabled ?? false,
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const closableAriaProps = computed(() => pickAttrs(closableConfig.value, true))
|
|
52
|
+
|
|
53
|
+
return [enabled, closableConfig, closableAriaProps]
|
|
54
|
+
}
|