vue-toast-kit 1.0.0

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1145 -0
  3. package/dist/composables/useToast.d.ts +28 -0
  4. package/dist/composables/useToastContext.d.ts +6 -0
  5. package/dist/composables/useToastState.d.ts +7 -0
  6. package/dist/core/GroupManager.d.ts +16 -0
  7. package/dist/core/ToastBuffer.d.ts +19 -0
  8. package/dist/core/ToastQueue.d.ts +55 -0
  9. package/dist/core/UndoTimer.d.ts +23 -0
  10. package/dist/core/types.d.ts +114 -0
  11. package/dist/index.d.ts +23 -0
  12. package/dist/module.d.ts +1 -0
  13. package/dist/nuxt/module.cjs +2 -0
  14. package/dist/nuxt/module.cjs.map +1 -0
  15. package/dist/nuxt/module.d.ts +1 -0
  16. package/dist/nuxt/module.js +34 -0
  17. package/dist/nuxt/module.js.map +1 -0
  18. package/dist/plugin.d.ts +6 -0
  19. package/dist/style.css +1 -0
  20. package/dist/testing.d.ts +14 -0
  21. package/dist/vue-toast-kit.cjs +2 -0
  22. package/dist/vue-toast-kit.cjs.map +1 -0
  23. package/dist/vue-toast-kit.d.ts +540 -0
  24. package/dist/vue-toast-kit.js +1000 -0
  25. package/dist/vue-toast-kit.js.map +1 -0
  26. package/package.json +89 -0
  27. package/src/components/Toast.vue +222 -0
  28. package/src/components/ToastActions.vue +34 -0
  29. package/src/components/ToastContainer.vue +257 -0
  30. package/src/components/ToastIcon.vue +53 -0
  31. package/src/components/ToastProgressBar.vue +18 -0
  32. package/src/composables/useToast.ts +152 -0
  33. package/src/composables/useToastContext.ts +63 -0
  34. package/src/composables/useToastState.ts +18 -0
  35. package/src/core/GroupManager.ts +105 -0
  36. package/src/core/ToastBuffer.ts +45 -0
  37. package/src/core/ToastQueue.ts +377 -0
  38. package/src/core/UndoTimer.ts +90 -0
  39. package/src/core/types.ts +142 -0
  40. package/src/env.d.ts +7 -0
  41. package/src/index.ts +51 -0
  42. package/src/nuxt/composables.ts +13 -0
  43. package/src/nuxt/module.ts +52 -0
  44. package/src/nuxt/plugin.ts +8 -0
  45. package/src/plugin.ts +18 -0
  46. package/src/styles/animations.css +106 -0
  47. package/src/styles/base.css +201 -0
  48. package/src/styles/themes/dark.css +30 -0
  49. package/src/styles/themes/light.css +30 -0
  50. package/src/styles/themes/system.css +32 -0
  51. package/src/styles/tokens.css +74 -0
  52. package/src/testing.ts +81 -0
@@ -0,0 +1,13 @@
1
+ // Nuxt-compatible re-exports for auto-import and manual import
2
+ // Usage: import { useToast } from 'vue-toast-kit/nuxt/composables'
3
+
4
+ export { useToast, toast } from '../composables/useToast'
5
+ export type { ToastApi } from '../composables/useToast'
6
+
7
+ export { useToastState } from '../composables/useToastState'
8
+
9
+ export {
10
+ useToastContext,
11
+ createToastContext,
12
+ getOrCreateGlobalContext,
13
+ } from '../composables/useToastContext'
@@ -0,0 +1,52 @@
1
+ import { defineNuxtModule, addPlugin, createResolver, addImports, addComponent } from '@nuxt/kit'
2
+ import type { GlobalToastOptions } from '../core/types'
3
+
4
+ export interface ModuleOptions extends GlobalToastOptions {
5
+ registerComponent?: boolean
6
+ }
7
+
8
+ export default defineNuxtModule<ModuleOptions>({
9
+ meta: {
10
+ name: 'vue-toast-kit',
11
+ configKey: 'vueToastKit',
12
+ compatibility: { nuxt: '>=3.0.0' },
13
+ },
14
+ defaults: {
15
+ position: 'bottom-right',
16
+ maxVisible: 5,
17
+ duration: 4000,
18
+ theme: 'system',
19
+ closable: true,
20
+ pauseOnHover: true,
21
+ pauseOnFocusLoss: true,
22
+ registerComponent: true,
23
+ },
24
+ setup(options, nuxt) {
25
+ const resolver = createResolver(import.meta.url)
26
+
27
+ // Pass module options to runtimeConfig
28
+ nuxt.options.runtimeConfig.public.vueToastKit = options as Record<string, unknown>
29
+
30
+ // Register Nuxt plugin
31
+ addPlugin(resolver.resolve('./plugin'))
32
+
33
+ // Auto-imports composables
34
+ addImports([
35
+ { name: 'useToast', from: resolver.resolve('../composables/useToast') },
36
+ { name: 'useToastState', from: resolver.resolve('../composables/useToastState') },
37
+ { name: 'createToastContext', from: resolver.resolve('../composables/useToastContext') },
38
+ { name: 'toast', from: resolver.resolve('../composables/useToast') },
39
+ ])
40
+
41
+ // Auto-import components
42
+ if (options.registerComponent !== false) {
43
+ addComponent({
44
+ name: 'ToastContainer',
45
+ filePath: resolver.resolve('../components/ToastContainer.vue'),
46
+ })
47
+ }
48
+
49
+ // Inject CSS
50
+ nuxt.options.css.push('vue-toast-kit/style.css')
51
+ },
52
+ })
@@ -0,0 +1,8 @@
1
+ import { defineNuxtPlugin } from '#app'
2
+ import { VueToastPlugin } from '../plugin'
3
+ import type { VueToastPluginOptions } from '../plugin'
4
+
5
+ export default defineNuxtPlugin<{ toastOptions: VueToastPluginOptions }>((nuxtApp) => {
6
+ const options = (nuxtApp.$config?.public?.vueToastKit ?? {}) as VueToastPluginOptions
7
+ nuxtApp.vueApp.use(VueToastPlugin, options)
8
+ })
package/src/plugin.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { App, Plugin } from 'vue'
2
+ import { installContext } from './composables/useToastContext'
3
+ import ToastContainer from './components/ToastContainer.vue'
4
+ import type { GlobalToastOptions } from './core/types'
5
+
6
+ export interface VueToastPluginOptions extends GlobalToastOptions {
7
+ registerComponent?: boolean
8
+ }
9
+
10
+ export const VueToastPlugin: Plugin<VueToastPluginOptions | void> = {
11
+ install(app: App, options?: VueToastPluginOptions) {
12
+ installContext(app, options ?? {})
13
+
14
+ if (options?.registerComponent !== false) {
15
+ app.component('ToastContainer', ToastContainer)
16
+ }
17
+ },
18
+ }
@@ -0,0 +1,106 @@
1
+ /* === Enter/Leave анимации по позиции === */
2
+
3
+ /* top-right */
4
+ .vtk-slide-top-right-enter-active,
5
+ .vtk-slide-top-right-leave-active {
6
+ transition:
7
+ transform var(--vtk-transition-duration) var(--vtk-transition-easing),
8
+ opacity var(--vtk-transition-duration) var(--vtk-transition-easing);
9
+ }
10
+ .vtk-slide-top-right-enter-from { transform: translateX(calc(100% + var(--vtk-container-offset-x))); opacity: 0; }
11
+ .vtk-slide-top-right-leave-to { transform: translateX(calc(100% + var(--vtk-container-offset-x))); opacity: 0; }
12
+
13
+ /* top-left */
14
+ .vtk-slide-top-left-enter-active,
15
+ .vtk-slide-top-left-leave-active {
16
+ transition:
17
+ transform var(--vtk-transition-duration) var(--vtk-transition-easing),
18
+ opacity var(--vtk-transition-duration) var(--vtk-transition-easing);
19
+ }
20
+ .vtk-slide-top-left-enter-from { transform: translateX(calc(-100% - var(--vtk-container-offset-x))); opacity: 0; }
21
+ .vtk-slide-top-left-leave-to { transform: translateX(calc(-100% - var(--vtk-container-offset-x))); opacity: 0; }
22
+
23
+ /* top-center */
24
+ .vtk-slide-top-center-enter-active,
25
+ .vtk-slide-top-center-leave-active {
26
+ transition:
27
+ transform var(--vtk-transition-duration) var(--vtk-transition-easing),
28
+ opacity var(--vtk-transition-duration) var(--vtk-transition-easing);
29
+ }
30
+ .vtk-slide-top-center-enter-from { transform: translateY(calc(-100% - var(--vtk-container-offset-y))) scale(0.85); opacity: 0; }
31
+ .vtk-slide-top-center-leave-to { transform: translateY(calc(-100% - var(--vtk-container-offset-y))) scale(0.85); opacity: 0; }
32
+
33
+ /* bottom-right */
34
+ .vtk-slide-bottom-right-enter-active,
35
+ .vtk-slide-bottom-right-leave-active {
36
+ transition:
37
+ transform var(--vtk-transition-duration) var(--vtk-transition-easing),
38
+ opacity var(--vtk-transition-duration) var(--vtk-transition-easing);
39
+ }
40
+ .vtk-slide-bottom-right-enter-from { transform: translateX(calc(100% + var(--vtk-container-offset-x))); opacity: 0; }
41
+ .vtk-slide-bottom-right-leave-to { transform: translateX(calc(100% + var(--vtk-container-offset-x))); opacity: 0; }
42
+
43
+ /* bottom-left */
44
+ .vtk-slide-bottom-left-enter-active,
45
+ .vtk-slide-bottom-left-leave-active {
46
+ transition:
47
+ transform var(--vtk-transition-duration) var(--vtk-transition-easing),
48
+ opacity var(--vtk-transition-duration) var(--vtk-transition-easing);
49
+ }
50
+ .vtk-slide-bottom-left-enter-from { transform: translateX(calc(-100% - var(--vtk-container-offset-x))); opacity: 0; }
51
+ .vtk-slide-bottom-left-leave-to { transform: translateX(calc(-100% - var(--vtk-container-offset-x))); opacity: 0; }
52
+
53
+ /* bottom-center */
54
+ .vtk-slide-bottom-center-enter-active,
55
+ .vtk-slide-bottom-center-leave-active {
56
+ transition:
57
+ transform var(--vtk-transition-duration) var(--vtk-transition-easing),
58
+ opacity var(--vtk-transition-duration) var(--vtk-transition-easing);
59
+ }
60
+ .vtk-slide-bottom-center-enter-from { transform: translateY(calc(100% + var(--vtk-container-offset-y))) scale(0.85); opacity: 0; }
61
+ .vtk-slide-bottom-center-leave-to { transform: translateY(calc(100% + var(--vtk-container-offset-y))) scale(0.85); opacity: 0; }
62
+
63
+ /* === prefers-reduced-motion — только fade === */
64
+ @media (prefers-reduced-motion: reduce) {
65
+ .vtk-slide-top-right-enter-from,
66
+ .vtk-slide-top-left-enter-from,
67
+ .vtk-slide-top-center-enter-from,
68
+ .vtk-slide-bottom-right-enter-from,
69
+ .vtk-slide-bottom-left-enter-from,
70
+ .vtk-slide-bottom-center-enter-from,
71
+ .vtk-slide-top-right-leave-to,
72
+ .vtk-slide-top-left-leave-to,
73
+ .vtk-slide-top-center-leave-to,
74
+ .vtk-slide-bottom-right-leave-to,
75
+ .vtk-slide-bottom-left-leave-to,
76
+ .vtk-slide-bottom-center-leave-to {
77
+ transform: none !important;
78
+ opacity: 0;
79
+ }
80
+
81
+ .vtk-slide-top-right-enter-active,
82
+ .vtk-slide-top-left-enter-active,
83
+ .vtk-slide-top-center-enter-active,
84
+ .vtk-slide-bottom-right-enter-active,
85
+ .vtk-slide-bottom-left-enter-active,
86
+ .vtk-slide-bottom-center-enter-active,
87
+ .vtk-slide-top-right-leave-active,
88
+ .vtk-slide-top-left-leave-active,
89
+ .vtk-slide-top-center-leave-active,
90
+ .vtk-slide-bottom-right-leave-active,
91
+ .vtk-slide-bottom-left-leave-active,
92
+ .vtk-slide-bottom-center-leave-active {
93
+ transition-duration: 150ms !important;
94
+ }
95
+ }
96
+
97
+ /* === Spinner для loading === */
98
+ @keyframes vtk-spin {
99
+ to { transform: rotate(360deg); }
100
+ }
101
+
102
+ /* === Progress bar === */
103
+ @keyframes vtk-progress {
104
+ from { transform: scaleX(1); }
105
+ to { transform: scaleX(0); }
106
+ }
@@ -0,0 +1,201 @@
1
+ /* === Контейнер === */
2
+ .vtk-container {
3
+ position: fixed;
4
+ z-index: var(--vtk-z-index);
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--vtk-gap);
8
+ pointer-events: none;
9
+ max-width: var(--vtk-max-width);
10
+ width: 100%;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ .vtk-container--top-left { top: var(--vtk-container-offset-y); left: var(--vtk-container-offset-x); align-items: flex-start; }
15
+ .vtk-container--top-center { top: var(--vtk-container-offset-y); left: 50%; transform: translateX(-50%); align-items: center; }
16
+ .vtk-container--top-right { top: var(--vtk-container-offset-y); right: var(--vtk-container-offset-x); align-items: flex-end; }
17
+ .vtk-container--bottom-left { bottom: var(--vtk-container-offset-y); left: var(--vtk-container-offset-x); align-items: flex-start; flex-direction: column-reverse; }
18
+ .vtk-container--bottom-center { bottom: var(--vtk-container-offset-y); left: 50%; transform: translateX(-50%); align-items: center; flex-direction: column-reverse; }
19
+ .vtk-container--bottom-right { bottom: var(--vtk-container-offset-y); right: var(--vtk-container-offset-x); align-items: flex-end; flex-direction: column-reverse; }
20
+
21
+ /* === Тост === */
22
+ .vtk-toast {
23
+ position: relative;
24
+ display: flex;
25
+ align-items: flex-start;
26
+ gap: 0.625rem;
27
+ padding: var(--vtk-padding-y) var(--vtk-padding-x);
28
+ background: var(--vtk-color-bg);
29
+ border: var(--vtk-border-width) solid var(--vtk-color-border);
30
+ border-radius: var(--vtk-border-radius);
31
+ box-shadow: var(--vtk-shadow);
32
+ font-family: var(--vtk-font-family);
33
+ font-size: var(--vtk-font-size);
34
+ font-weight: var(--vtk-font-weight-normal);
35
+ line-height: var(--vtk-line-height);
36
+ color: var(--vtk-color-text);
37
+ pointer-events: all;
38
+ overflow: hidden;
39
+ max-width: 100%;
40
+ min-width: var(--vtk-min-width);
41
+ box-sizing: border-box;
42
+ cursor: default;
43
+ user-select: none;
44
+ }
45
+
46
+ .vtk-toast--success { border-color: var(--vtk-color-success-border); background: var(--vtk-color-success-bg); }
47
+ .vtk-toast--error { border-color: var(--vtk-color-error-border); background: var(--vtk-color-error-bg); }
48
+ .vtk-toast--warning { border-color: var(--vtk-color-warning-border); background: var(--vtk-color-warning-bg); }
49
+ .vtk-toast--info { border-color: var(--vtk-color-info-border); background: var(--vtk-color-info-bg); }
50
+ .vtk-toast--loading { border-color: var(--vtk-color-loading-border); background: var(--vtk-color-loading-bg); }
51
+
52
+ /* === Иконка === */
53
+ .vtk-icon {
54
+ flex-shrink: 0;
55
+ width: var(--vtk-icon-size);
56
+ height: var(--vtk-icon-size);
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ }
61
+
62
+ .vtk-icon--success { color: var(--vtk-color-success); }
63
+ .vtk-icon--error { color: var(--vtk-color-error); }
64
+ .vtk-icon--warning { color: var(--vtk-color-warning); }
65
+ .vtk-icon--info { color: var(--vtk-color-info); }
66
+ .vtk-icon--loading { color: var(--vtk-color-loading); }
67
+
68
+ .vtk-icon svg {
69
+ width: 100%;
70
+ height: 100%;
71
+ }
72
+
73
+ /* CSS spinner */
74
+ .vtk-spinner {
75
+ width: var(--vtk-icon-size);
76
+ height: var(--vtk-icon-size);
77
+ border: 2px solid transparent;
78
+ border-top-color: var(--vtk-color-loading);
79
+ border-right-color: var(--vtk-color-loading);
80
+ border-radius: 50%;
81
+ animation: vtk-spin 0.7s linear infinite;
82
+ flex-shrink: 0;
83
+ }
84
+
85
+ /* === Контент === */
86
+ .vtk-content {
87
+ flex: 1;
88
+ min-width: 0;
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 0.25rem;
92
+ }
93
+
94
+ .vtk-message {
95
+ font-size: var(--vtk-font-size);
96
+ line-height: var(--vtk-line-height);
97
+ color: var(--vtk-color-text);
98
+ word-break: break-word;
99
+ }
100
+
101
+ /* === Действия === */
102
+ .vtk-actions {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 0.375rem;
106
+ margin-top: 0.25rem;
107
+ flex-wrap: wrap;
108
+ }
109
+
110
+ .vtk-btn {
111
+ display: inline-flex;
112
+ align-items: center;
113
+ gap: 0.25rem;
114
+ padding: var(--vtk-btn-padding);
115
+ font-family: var(--vtk-font-family);
116
+ font-size: var(--vtk-btn-font-size);
117
+ font-weight: var(--vtk-btn-font-weight);
118
+ color: var(--vtk-btn-color);
119
+ background: var(--vtk-btn-bg);
120
+ border: none;
121
+ border-radius: var(--vtk-btn-radius);
122
+ cursor: pointer;
123
+ transition: background var(--vtk-transition-duration) var(--vtk-transition-easing);
124
+ line-height: 1;
125
+ }
126
+
127
+ .vtk-btn:hover { background: var(--vtk-btn-hover-bg); }
128
+ .vtk-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }
129
+
130
+ .vtk-btn--action { color: var(--vtk-color-info); font-weight: 600; }
131
+ .vtk-btn--undo { color: var(--vtk-color-warning); font-weight: 600; }
132
+ .vtk-btn--close { color: var(--vtk-color-text-muted); padding: 0.25rem; margin-inline-start: auto; flex-shrink: 0; }
133
+
134
+ /* === Счётчик группы === */
135
+ .vtk-group-count {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ min-width: 1.25rem;
140
+ height: 1.25rem;
141
+ padding: 0 0.3rem;
142
+ font-size: 0.6875rem;
143
+ font-weight: 700;
144
+ background: rgba(0, 0, 0, 0.10);
145
+ border-radius: 999px;
146
+ cursor: pointer;
147
+ flex-shrink: 0;
148
+ }
149
+
150
+ /* === Прогресс-бар === */
151
+ .vtk-progress {
152
+ position: absolute;
153
+ bottom: 0;
154
+ left: 0;
155
+ right: 0;
156
+ height: var(--vtk-progress-height);
157
+ overflow: hidden;
158
+ border-radius: 0 0 var(--vtk-border-radius) var(--vtk-border-radius);
159
+ }
160
+
161
+ .vtk-progress__bar {
162
+ height: 100%;
163
+ width: 100%;
164
+ transform-origin: left center;
165
+ background: var(--vtk-progress-color);
166
+ opacity: var(--vtk-progress-opacity);
167
+ transition: transform 100ms linear;
168
+ }
169
+
170
+ .vtk-toast--success .vtk-progress__bar { background: var(--vtk-color-success); opacity: 0.4; }
171
+ .vtk-toast--error .vtk-progress__bar { background: var(--vtk-color-error); opacity: 0.4; }
172
+ .vtk-toast--warning .vtk-progress__bar { background: var(--vtk-color-warning); opacity: 0.4; }
173
+ .vtk-toast--info .vtk-progress__bar { background: var(--vtk-color-info); opacity: 0.4; }
174
+ .vtk-toast--loading .vtk-progress__bar { background: var(--vtk-color-loading); opacity: 0.4; }
175
+
176
+ /* === RTL === */
177
+ [dir="rtl"] .vtk-btn--close { margin-inline-start: auto; }
178
+
179
+ /* === Mobile === */
180
+ @media (max-width: 480px) {
181
+ .vtk-container {
182
+ --vtk-container-offset-x: 0.75rem;
183
+ --vtk-container-offset-y: 0.75rem;
184
+ --vtk-max-width: calc(100vw - 1.5rem);
185
+ --vtk-min-width: 0;
186
+ }
187
+
188
+ .vtk-container--top-center,
189
+ .vtk-container--bottom-center {
190
+ left: var(--vtk-container-offset-x);
191
+ right: var(--vtk-container-offset-x);
192
+ transform: none;
193
+ width: auto;
194
+ }
195
+ }
196
+
197
+ /* === Reduced motion === */
198
+ @media (prefers-reduced-motion: reduce) {
199
+ .vtk-progress__bar { transition: none; }
200
+ .vtk-spinner { animation-duration: 1.5s; }
201
+ }
@@ -0,0 +1,30 @@
1
+ .vtk-theme-dark,
2
+ [data-vtk-theme="dark"] {
3
+ --vtk-color-bg: #1c1c1e;
4
+ --vtk-color-text: #f5f5f7;
5
+ --vtk-color-text-muted: #98989f;
6
+ --vtk-color-border: rgba(255, 255, 255, 0.10);
7
+ --vtk-shadow: 0 4px 20px rgba(0, 0, 0, 0.40), 0 1px 6px rgba(0, 0, 0, 0.20);
8
+
9
+ --vtk-color-success: #34d399;
10
+ --vtk-color-success-bg: #052e16;
11
+ --vtk-color-success-border: rgba(52, 211, 153, 0.20);
12
+
13
+ --vtk-color-error: #f87171;
14
+ --vtk-color-error-bg: #2d0a0a;
15
+ --vtk-color-error-border: rgba(248, 113, 113, 0.20);
16
+
17
+ --vtk-color-warning: #fbbf24;
18
+ --vtk-color-warning-bg: #2d1a00;
19
+ --vtk-color-warning-border: rgba(251, 191, 36, 0.20);
20
+
21
+ --vtk-color-info: #60a5fa;
22
+ --vtk-color-info-bg: #0c1a2e;
23
+ --vtk-color-info-border: rgba(96, 165, 250, 0.20);
24
+
25
+ --vtk-color-loading: #a78bfa;
26
+ --vtk-color-loading-bg: #1a0a2e;
27
+ --vtk-color-loading-border: rgba(167, 139, 250, 0.20);
28
+
29
+ --vtk-btn-hover-bg: rgba(255, 255, 255, 0.08);
30
+ }
@@ -0,0 +1,30 @@
1
+ .vtk-theme-light,
2
+ [data-vtk-theme="light"] {
3
+ --vtk-color-bg: #ffffff;
4
+ --vtk-color-text: #1a1a1a;
5
+ --vtk-color-text-muted: #6b7280;
6
+ --vtk-color-border: rgba(0, 0, 0, 0.08);
7
+ --vtk-shadow: 0 4px 16px rgba(0, 0, 0, 0.10), 0 1px 4px rgba(0, 0, 0, 0.06);
8
+
9
+ --vtk-color-success: #16a34a;
10
+ --vtk-color-success-bg: #f0fdf4;
11
+ --vtk-color-success-border: #bbf7d0;
12
+
13
+ --vtk-color-error: #dc2626;
14
+ --vtk-color-error-bg: #fef2f2;
15
+ --vtk-color-error-border: #fecaca;
16
+
17
+ --vtk-color-warning: #d97706;
18
+ --vtk-color-warning-bg: #fffbeb;
19
+ --vtk-color-warning-border: #fde68a;
20
+
21
+ --vtk-color-info: #2563eb;
22
+ --vtk-color-info-bg: #eff6ff;
23
+ --vtk-color-info-border: #bfdbfe;
24
+
25
+ --vtk-color-loading: #7c3aed;
26
+ --vtk-color-loading-bg: #f5f3ff;
27
+ --vtk-color-loading-border: #ddd6fe;
28
+
29
+ --vtk-btn-hover-bg: rgba(0, 0, 0, 0.06);
30
+ }
@@ -0,0 +1,32 @@
1
+ @media (prefers-color-scheme: dark) {
2
+ .vtk-theme-system,
3
+ [data-vtk-theme="system"] {
4
+ --vtk-color-bg: #1c1c1e;
5
+ --vtk-color-text: #f5f5f7;
6
+ --vtk-color-text-muted: #98989f;
7
+ --vtk-color-border: rgba(255, 255, 255, 0.10);
8
+ --vtk-shadow: 0 4px 20px rgba(0, 0, 0, 0.40), 0 1px 6px rgba(0, 0, 0, 0.20);
9
+
10
+ --vtk-color-success: #34d399;
11
+ --vtk-color-success-bg: #052e16;
12
+ --vtk-color-success-border: rgba(52, 211, 153, 0.20);
13
+
14
+ --vtk-color-error: #f87171;
15
+ --vtk-color-error-bg: #2d0a0a;
16
+ --vtk-color-error-border: rgba(248, 113, 113, 0.20);
17
+
18
+ --vtk-color-warning: #fbbf24;
19
+ --vtk-color-warning-bg: #2d1a00;
20
+ --vtk-color-warning-border: rgba(251, 191, 36, 0.20);
21
+
22
+ --vtk-color-info: #60a5fa;
23
+ --vtk-color-info-bg: #0c1a2e;
24
+ --vtk-color-info-border: rgba(96, 165, 250, 0.20);
25
+
26
+ --vtk-color-loading: #a78bfa;
27
+ --vtk-color-loading-bg: #1a0a2e;
28
+ --vtk-color-loading-border: rgba(167, 139, 250, 0.20);
29
+
30
+ --vtk-btn-hover-bg: rgba(255, 255, 255, 0.08);
31
+ }
32
+ }
@@ -0,0 +1,74 @@
1
+ :root {
2
+ /* === Цвета фона и текста === */
3
+ --vtk-color-bg: #ffffff;
4
+ --vtk-color-text: #1a1a1a;
5
+ --vtk-color-text-muted: #6b7280;
6
+ --vtk-color-border: rgba(0, 0, 0, 0.08);
7
+
8
+ /* === Цвета типов === */
9
+ --vtk-color-success: #16a34a;
10
+ --vtk-color-success-bg: #f0fdf4;
11
+ --vtk-color-success-border: #bbf7d0;
12
+
13
+ --vtk-color-error: #dc2626;
14
+ --vtk-color-error-bg: #fef2f2;
15
+ --vtk-color-error-border: #fecaca;
16
+
17
+ --vtk-color-warning: #d97706;
18
+ --vtk-color-warning-bg: #fffbeb;
19
+ --vtk-color-warning-border: #fde68a;
20
+
21
+ --vtk-color-info: #2563eb;
22
+ --vtk-color-info-bg: #eff6ff;
23
+ --vtk-color-info-border: #bfdbfe;
24
+
25
+ --vtk-color-loading: #7c3aed;
26
+ --vtk-color-loading-bg: #f5f3ff;
27
+ --vtk-color-loading-border: #ddd6fe;
28
+
29
+ /* === Прогресс-бар === */
30
+ --vtk-progress-color: currentColor;
31
+ --vtk-progress-opacity: 0.25;
32
+
33
+ /* === Типографика === */
34
+ --vtk-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
35
+ --vtk-font-size: 0.875rem;
36
+ --vtk-font-size-sm: 0.75rem;
37
+ --vtk-font-weight: 500;
38
+ --vtk-font-weight-normal: 400;
39
+ --vtk-line-height: 1.5;
40
+
41
+ /* === Форма === */
42
+ --vtk-border-radius: 10px;
43
+ --vtk-border-width: 1px;
44
+ --vtk-shadow: 0 4px 16px rgba(0, 0, 0, 0.10), 0 1px 4px rgba(0, 0, 0, 0.06);
45
+
46
+ /* === Размеры === */
47
+ --vtk-padding-x: 1rem;
48
+ --vtk-padding-y: 0.75rem;
49
+ --vtk-icon-size: 1.25rem;
50
+ --vtk-progress-height: 3px;
51
+ --vtk-max-width: 400px;
52
+ --vtk-min-width: 280px;
53
+ --vtk-gap: 0.5rem;
54
+
55
+ /* === Кнопки === */
56
+ --vtk-btn-color: inherit;
57
+ --vtk-btn-bg: transparent;
58
+ --vtk-btn-hover-bg: rgba(0, 0, 0, 0.06);
59
+ --vtk-btn-radius: 6px;
60
+ --vtk-btn-font-size: 0.8125rem;
61
+ --vtk-btn-font-weight: 600;
62
+ --vtk-btn-padding: 0.25rem 0.625rem;
63
+
64
+ /* === Анимации === */
65
+ --vtk-transition-duration: 300ms;
66
+ --vtk-transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
67
+
68
+ /* === Z-index === */
69
+ --vtk-z-index: 9999;
70
+
71
+ /* === Контейнер === */
72
+ --vtk-container-offset-x: 1rem;
73
+ --vtk-container-offset-y: 1rem;
74
+ }
package/src/testing.ts ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Testing utilities for vue-toast-kit.
3
+ * Compatible with Vitest and Jest.
4
+ *
5
+ * Usage:
6
+ * import { createMockToast, mockUseToast } from 'vue-toast-kit/testing'
7
+ */
8
+ import { ref } from 'vue'
9
+ import type { ToastItem, ToastOptions, ToastType, ToastPriority } from './core/types'
10
+ import type { ToastApi } from './composables/useToast'
11
+
12
+ // ── createMockToast ───────────────────────────────────────────────────────────
13
+
14
+ /** Create a minimal ToastItem stub for unit tests. */
15
+ export function createMockToast(overrides: Partial<ToastItem> & { options?: Partial<ToastOptions> } = {}): ToastItem {
16
+ const defaultOptions = {
17
+ id: overrides.id ?? 'mock-id',
18
+ type: 'info' as ToastType,
19
+ priority: 'normal' as ToastPriority,
20
+ duration: 4000,
21
+ closable: true,
22
+ pauseOnHover: true,
23
+ pauseOnFocusLoss: true,
24
+ swipeToDismiss: true,
25
+ persist: false,
26
+ ...overrides.options,
27
+ }
28
+
29
+ const item: ToastItem = {
30
+ id: overrides.id ?? `mock-${Math.random().toString(36).slice(2)}`,
31
+ message: overrides.message ?? 'Test toast',
32
+ options: defaultOptions as ToastItem['options'],
33
+ createdAt: overrides.createdAt ?? Date.now(),
34
+ remaining: overrides.remaining ?? ref(1),
35
+ isPaused: overrides.isPaused ?? ref(false),
36
+ groupCount: overrides.groupCount ?? ref(1),
37
+ isGrouped: overrides.isGrouped ?? ref(false),
38
+ pause: overrides.pause ?? (() => {}),
39
+ resume: overrides.resume ?? (() => {}),
40
+ dismiss: overrides.dismiss ?? (() => {}),
41
+ update: overrides.update ?? (() => {}),
42
+ }
43
+
44
+ return item
45
+ }
46
+
47
+ // ── mockUseToast ──────────────────────────────────────────────────────────────
48
+
49
+ /**
50
+ * Create a spy-friendly mock of the ToastApi.
51
+ * All methods are no-ops that return empty strings unless overridden.
52
+ *
53
+ * @example
54
+ * vi.mock('vue-toast-kit', () => ({ useToast: () => mockUseToast() }))
55
+ */
56
+ export function mockUseToast(overrides: Partial<ToastApi> = {}): ToastApi {
57
+ const noop = () => ''
58
+ const noopVoid = () => {}
59
+
60
+ function toast(_message: string, _options?: ToastOptions): string {
61
+ return ''
62
+ }
63
+
64
+ toast.success = overrides.success ?? noop
65
+ toast.error = overrides.error ?? noop
66
+ toast.warning = overrides.warning ?? noop
67
+ toast.info = overrides.info ?? noop
68
+ toast.loading = overrides.loading ?? noop
69
+ toast.custom = overrides.custom ?? noop
70
+ toast.dismiss = overrides.dismiss ?? noopVoid
71
+ toast.update = overrides.update ?? noopVoid
72
+ toast.updateMessage = overrides.updateMessage ?? noopVoid
73
+ toast.isActive = overrides.isActive ?? (() => false)
74
+ toast.promise = overrides.promise ?? (<T>(p: Promise<T>) => p)
75
+ toast.undo = overrides.undo ?? noop
76
+ toast.dismissAll = overrides.dismissAll ?? noopVoid
77
+ toast.pauseAll = overrides.pauseAll ?? noopVoid
78
+ toast.resumeAll = overrides.resumeAll ?? noopVoid
79
+
80
+ return toast as unknown as ToastApi
81
+ }