@v-c/notification 2.0.0-beta.1 → 2.0.0-rc.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.
Files changed (51) hide show
  1. package/dist/Notification.d.ts +7 -234
  2. package/dist/Notification.js +199 -135
  3. package/dist/NotificationList/Content.d.ts +3 -80
  4. package/dist/NotificationList/Content.js +33 -47
  5. package/dist/NotificationList/index.d.ts +7 -128
  6. package/dist/NotificationList/index.js +139 -122
  7. package/dist/NotificationProvider.d.ts +1 -20
  8. package/dist/NotificationProvider.js +2 -13
  9. package/dist/Notifications.d.ts +10 -132
  10. package/dist/Notifications.js +123 -108
  11. package/dist/Progress.d.ts +3 -3
  12. package/dist/Progress.js +24 -11
  13. package/dist/hooks/useClosable.d.ts +6 -11
  14. package/dist/hooks/useClosable.js +7 -6
  15. package/dist/hooks/useListPosition/index.d.ts +14 -13
  16. package/dist/hooks/useListPosition/index.js +16 -23
  17. package/dist/hooks/useListPosition/useSizes.d.ts +3 -6
  18. package/dist/hooks/useListPosition/useSizes.js +12 -10
  19. package/dist/hooks/useNoticeTimer.d.ts +5 -4
  20. package/dist/hooks/useNoticeTimer.js +33 -41
  21. package/dist/hooks/useNotification.d.ts +25 -7
  22. package/dist/hooks/useNotification.js +20 -25
  23. package/dist/hooks/useStack.d.ts +9 -8
  24. package/dist/hooks/useStack.js +17 -11
  25. package/dist/index.d.ts +9 -8
  26. package/dist/index.js +2 -2
  27. package/dist/interface.d.ts +30 -0
  28. package/dist/interface.js +0 -0
  29. package/docs/context.vue +1 -1
  30. package/docs/hooks.vue +4 -4
  31. package/docs/index.less +143 -62
  32. package/docs/maxCount.vue +1 -1
  33. package/docs/showProgress.vue +2 -2
  34. package/docs/stack.vue +1 -1
  35. package/package.json +2 -3
  36. package/src/Notification.tsx +128 -165
  37. package/src/NotificationList/Content.tsx +36 -54
  38. package/src/NotificationList/index.tsx +131 -135
  39. package/src/NotificationProvider.tsx +3 -23
  40. package/src/Notifications.tsx +62 -84
  41. package/src/Progress.tsx +19 -15
  42. package/src/hooks/useClosable.ts +15 -16
  43. package/src/hooks/useListPosition/index.ts +24 -40
  44. package/src/hooks/useListPosition/useSizes.ts +16 -15
  45. package/src/hooks/useNoticeTimer.ts +34 -45
  46. package/src/hooks/useNotification.tsx +71 -43
  47. package/src/hooks/useStack.ts +20 -24
  48. package/src/index.ts +28 -13
  49. package/src/interface.ts +45 -0
  50. package/vitest.config.ts +1 -3
  51. package/tests/index.spec.tsx +0 -200
package/src/index.ts CHANGED
@@ -1,35 +1,50 @@
1
- import type { NotificationAPI, NotificationConfig } from './hooks/useNotification'
2
- import type { ComponentsType, NotificationProps } from './Notification'
3
- import type { NotificationProgressProps } from './Progress'
4
1
  import useNotification from './hooks/useNotification'
5
2
  import Notification from './Notification'
6
3
  import NotificationList from './NotificationList'
7
- import NotificationProvider, { useNotificationProvider } from './NotificationProvider'
4
+ import { useNotificationContext, useNotificationProvider } from './NotificationProvider'
8
5
  import Progress from './Progress'
9
6
 
7
+ import type { NotificationAPI, NotificationConfig } from './hooks/useNotification'
8
+ import type {
9
+ ComponentsType,
10
+ NotificationClassNames as NoticeClassNames,
11
+ NotificationProps,
12
+ NotificationStyles as NoticeStyles,
13
+ } from './Notification'
14
+ import type {
15
+ NotificationClassNames,
16
+ NotificationListProps,
17
+ NotificationStyles,
18
+ Placement,
19
+ } from './NotificationList'
20
+ import type { NotificationProgressProps } from './Progress'
21
+ import type { Key, NotificationListConfig, StackConfig } from './interface'
22
+ import type { ClosableType, ParsedClosableConfig } from './hooks/useClosable'
23
+
10
24
  export {
11
25
  Notification,
12
26
  NotificationList,
13
- NotificationProvider,
14
27
  Progress,
15
28
  useNotification,
29
+ useNotificationContext,
16
30
  useNotificationProvider,
17
31
  }
18
32
 
19
33
  export type {
34
+ ClosableType,
20
35
  ComponentsType,
36
+ Key,
37
+ NoticeClassNames,
38
+ NoticeStyles,
21
39
  NotificationAPI,
22
- NotificationConfig,
23
- NotificationProgressProps,
24
- NotificationProps,
25
- }
26
-
27
- export type {
28
40
  NotificationClassNames,
41
+ NotificationConfig,
29
42
  NotificationListConfig,
30
43
  NotificationListProps,
44
+ NotificationProgressProps,
45
+ NotificationProps,
31
46
  NotificationStyles,
47
+ ParsedClosableConfig,
32
48
  Placement,
33
49
  StackConfig,
34
- StackInput,
35
- } from './NotificationList'
50
+ }
@@ -0,0 +1,45 @@
1
+ import type { ClosableType } from './hooks/useClosable'
2
+ import type {
3
+ ComponentsType,
4
+ NotificationProps,
5
+ } from './Notification'
6
+
7
+ export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'
8
+
9
+ export type Key = string | number
10
+
11
+ export type StackConfig
12
+ = | boolean
13
+ | {
14
+ /**
15
+ * When the notice count exceeds this threshold, notices will be stacked.
16
+ * @default 3
17
+ */
18
+ threshold?: number
19
+ /**
20
+ * Vertical offset applied between stacked notices.
21
+ * @default 8
22
+ */
23
+ offset?: number
24
+ }
25
+
26
+ /**
27
+ * Configuration accepted by the public `api.open` call.
28
+ * Mirrors rc-notification@2.0 NotificationListConfig.
29
+ */
30
+ export interface NotificationListConfig extends Omit<NotificationProps, 'prefixCls'> {
31
+ key: Key
32
+ placement?: Placement
33
+ times?: number
34
+ }
35
+
36
+ export type Placements = Partial<Record<Placement, NotificationListConfig[]>>
37
+
38
+ export type InnerOpenConfig = NotificationListConfig & { times?: number }
39
+
40
+ // Re-export common surfaces consumers used to import from interface.ts directly.
41
+ export type {
42
+ ClosableType,
43
+ ComponentsType,
44
+ NotificationProps,
45
+ }
package/vitest.config.ts CHANGED
@@ -4,8 +4,6 @@ import configShared from '../../vitest.config'
4
4
  export default mergeConfig(
5
5
  configShared,
6
6
  defineProject({
7
- test: {
8
- environment: 'jsdom',
9
- },
7
+
10
8
  }),
11
9
  )
@@ -1,200 +0,0 @@
1
- import type { NotificationAPI, NotificationConfig } from '../src'
2
- import { mount } from '@vue/test-utils'
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4
- import { defineComponent, h, nextTick } from 'vue'
5
- import { useNotification } from '../src'
6
-
7
- async function flush() {
8
- await nextTick()
9
- vi.runAllTimers()
10
- await nextTick()
11
- await nextTick()
12
- }
13
-
14
- function renderDemo(config?: NotificationConfig) {
15
- let api!: NotificationAPI
16
-
17
- const Demo = defineComponent({
18
- setup() {
19
- const [_api, holder] = useNotification(config)
20
- api = _api
21
- return () => holder()
22
- },
23
- })
24
-
25
- const wrapper = mount(Demo, {
26
- global: {
27
- stubs: {
28
- 'transition': false,
29
- 'transition-group': false,
30
- },
31
- },
32
- })
33
- return { wrapper, get api() {
34
- return api
35
- } }
36
- }
37
-
38
- describe('@v-c/notification', () => {
39
- beforeEach(() => {
40
- vi.useFakeTimers()
41
- })
42
-
43
- afterEach(() => {
44
- document.body.innerHTML = ''
45
- vi.useRealTimers()
46
- vi.restoreAllMocks()
47
- })
48
-
49
- it('opens notice with description', async () => {
50
- const { api } = renderDemo()
51
- api.open({ description: h('p', { class: 'test' }, '1'), duration: 0.1 })
52
- await flush()
53
- expect(document.querySelector('.test')).toBeTruthy()
54
-
55
- // Run all timers to flush duration
56
- await flush()
57
- expect(document.querySelector('.test')).toBeFalsy()
58
- })
59
-
60
- it('renders semantic slots when provided', async () => {
61
- const { api } = renderDemo()
62
- api.open({
63
- title: 'bamboo',
64
- description: 'little',
65
- icon: h('span'),
66
- actions: h('button', { type: 'button' }, 'light'),
67
- closable: true,
68
- showProgress: true,
69
- duration: 3,
70
- })
71
- await flush()
72
-
73
- expect(document.querySelector('.vc-notification-notice-title')).toBeTruthy()
74
- expect(document.querySelector('.vc-notification-notice-description')).toBeTruthy()
75
- expect(document.querySelector('.vc-notification-notice-icon')).toBeTruthy()
76
- expect(document.querySelector('.vc-notification-notice-actions')).toBeTruthy()
77
- expect(document.querySelector('.vc-notification-notice-close')).toBeTruthy()
78
- expect(document.querySelector('.vc-notification-notice-progress')).toBeTruthy()
79
- expect(document.querySelector('.vc-notification-notice-section')).toBeTruthy()
80
- })
81
-
82
- it('does not wrap in section when only one of title/description provided', async () => {
83
- const { api } = renderDemo()
84
- api.open({ description: 'only-description' })
85
- await flush()
86
- expect(document.querySelector('.vc-notification-notice-section')).toBeFalsy()
87
- expect(document.querySelector('.vc-notification-notice-description')).toBeTruthy()
88
- })
89
-
90
- it('forwards classNames and styles for list and listContent', async () => {
91
- const { api } = renderDemo({
92
- classNames: {
93
- list: 'root-list',
94
- listContent: 'inner-content',
95
- },
96
- styles: {
97
- list: { color: 'red' },
98
- listContent: { color: 'blue' },
99
- },
100
- })
101
- api.open({ description: 'x' })
102
- await flush()
103
- expect(document.querySelector('.vc-notification-list')?.classList.contains('root-list')).toBe(true)
104
- expect(document.querySelector('.vc-notification-list-content')?.classList.contains('inner-content')).toBe(true)
105
- })
106
-
107
- it('respects maxCount', async () => {
108
- const { api } = renderDemo({ maxCount: 1 })
109
- api.open({ description: h('span', { class: 'a' }, 'a'), duration: 0 })
110
- api.open({ description: h('span', { class: 'b' }, 'b'), duration: 0 })
111
- api.open({ description: h('span', { class: 'c' }, 'c'), duration: 0 })
112
- await flush()
113
- expect(document.querySelectorAll('.a, .b, .c')).toHaveLength(1)
114
- expect(document.querySelector('.c')).toBeTruthy()
115
- })
116
-
117
- it('triggers onClose only when close button is clicked', async () => {
118
- const { api } = renderDemo()
119
- let clicks = 0
120
- let closes = 0
121
- api.open({
122
- description: h('p', 'x'),
123
- closable: true,
124
- duration: 0,
125
- onClick: () => {
126
- clicks += 1
127
- },
128
- onClose: () => {
129
- closes += 1
130
- },
131
- })
132
- await nextTick()
133
- await nextTick()
134
- const closeBtn = document.querySelector('.vc-notification-notice-close') as HTMLButtonElement
135
- closeBtn.click()
136
- expect(clicks).toBe(0)
137
- expect(closes).toBe(1)
138
- })
139
-
140
- it('destroy removes everything', async () => {
141
- const { api } = renderDemo()
142
- api.open({ description: h('p', { class: 'test' }, 'x'), duration: 0 })
143
- await flush()
144
- expect(document.querySelector('.test')).toBeTruthy()
145
- api.destroy()
146
- await flush()
147
- expect(document.querySelector('.test')).toBeFalsy()
148
- })
149
-
150
- it('placement adds className', async () => {
151
- const { api } = renderDemo()
152
- api.open({ description: 'x', placement: 'bottomLeft' })
153
- await flush()
154
- expect(document.querySelector('.vc-notification')?.classList.contains('vc-notification-bottomLeft')).toBe(true)
155
- })
156
-
157
- it('motion as function receives placement', async () => {
158
- const motionFn = vi.fn().mockReturnValue({})
159
- const { api } = renderDemo({ motion: motionFn })
160
- api.open({ description: 'x', placement: 'bottomLeft' })
161
- await flush()
162
- expect(motionFn).toHaveBeenCalledWith('bottomLeft')
163
- })
164
-
165
- it('open with custom close icon', async () => {
166
- const { api } = renderDemo()
167
- api.open({
168
- description: 'x',
169
- closable: { closeIcon: h('span', { class: 'test-icon' }, 'X') },
170
- duration: 0,
171
- })
172
- await flush()
173
- expect(document.querySelector('.test-icon')?.textContent).toBe('X')
174
- })
175
-
176
- it('forwards data and aria attrs via props', async () => {
177
- const { api } = renderDemo()
178
- api.open({
179
- description: 'x',
180
- class: 'notice-class',
181
- props: {
182
- 'data-test': 'data-test-value',
183
- 'aria-describedby': 'desc-id',
184
- 'role': 'status',
185
- },
186
- })
187
- await flush()
188
- const notice = document.querySelector('.notice-class')
189
- expect(notice?.getAttribute('data-test')).toBe('data-test-value')
190
- expect(notice?.getAttribute('aria-describedby')).toBe('desc-id')
191
- expect(notice?.getAttribute('role')).toBe('status')
192
- })
193
-
194
- it('default role is alert', async () => {
195
- const { api } = renderDemo()
196
- api.open({ description: 'x' })
197
- await flush()
198
- expect(document.querySelector('.vc-notification-notice')?.getAttribute('role')).toBe('alert')
199
- })
200
- })