plugin-ui-for-kzt 0.0.22 → 0.0.25

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 (102) hide show
  1. package/dist/assets/0e28e37419c99ac65b12.png +0 -0
  2. package/dist/assets/264165b2b0e8a6840eb0.png +0 -0
  3. package/dist/components/BaseBadge/BaseBadge.vue.d.ts +1 -1
  4. package/dist/components/BaseButton/BaseButton.vue.d.ts +3 -3
  5. package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +4 -4
  6. package/dist/components/{Spinner/Spinner.vue.d.ts → BaseDefaultPages/BaseDefaultPages.vue.d.ts} +9 -15
  7. package/dist/components/BaseDropdown/BaseDropdown.vue.d.ts +3 -3
  8. package/dist/components/BaseField/BaseField.vue.d.ts +2 -2
  9. package/dist/components/BaseInput/BaseInput.vue.d.ts +7 -7
  10. package/dist/components/BaseInputCalendar/BaseInputCalendar.vue.d.ts +5 -5
  11. package/dist/components/BaseInputCurrency/BaseInputCurrency.vue.d.ts +6 -6
  12. package/dist/components/BaseInputEmail/BaseInputEmail.vue.d.ts +5 -5
  13. package/dist/components/BaseInputPhone/BaseInputPhone.vue.d.ts +5 -5
  14. package/dist/components/BaseOpenedListItem/BaseOpenedListItem.vue.d.ts +3 -3
  15. package/dist/components/{Tooltip/Tooltip.vue.d.ts → BasePageLoader/BasePageLoader.vue.d.ts} +24 -11
  16. package/dist/components/BasePagination/BasePagination.vue.d.ts +1 -1
  17. package/dist/components/BaseRadio/BaseRadio.vue.d.ts +4 -4
  18. package/dist/components/BaseSegmentedButtons/BaseSegmentedButtons.vue.d.ts +3 -3
  19. package/dist/components/BaseSelect/BaseSelect.vue.d.ts +4 -4
  20. package/dist/components/BaseTable/BaseTable.vue.d.ts +44 -0
  21. package/dist/components/BaseTabs/BaseTabs.vue.d.ts +25 -0
  22. package/dist/components/BaseTag/BaseTag.vue.d.ts +1 -1
  23. package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +5 -5
  24. package/dist/components/BaseToast/BaseToast.vue.d.ts +69 -0
  25. package/dist/components/BaseToggle/BaseToggle.vue.d.ts +4 -4
  26. package/dist/components/BaseUpload/BaseUpload.vue.d.ts +11 -0
  27. package/dist/components/{DataTable/DataTable.vue.d.ts → BaseUpload/CropModal.vue.d.ts} +3 -6
  28. package/dist/composables/useToast.d.ts +2 -0
  29. package/dist/index.d.ts +7 -5
  30. package/dist/index.js +1 -1
  31. package/dist/index.js.LICENSE.txt +15 -0
  32. package/dist/plugins/toastPlugin.d.ts +4 -0
  33. package/dist/sprite.svg +1 -1
  34. package/dist/store/toast.d.ts +8 -0
  35. package/example/App.vue +201 -28
  36. package/example/TestImage.vue +6 -0
  37. package/package.json +2 -1
  38. package/src/assets/404.png +0 -0
  39. package/src/assets/icons/arrow-down-stick.svg +4 -0
  40. package/src/assets/icons/edit-table.svg +5 -0
  41. package/src/assets/icons/ellipsis.svg +5 -0
  42. package/src/assets/icons/loading-page-default.svg +4 -0
  43. package/src/assets/icons/loading-page-error.svg +6 -0
  44. package/src/assets/icons/loading-page-success.svg +5 -0
  45. package/src/assets/icons/loading-page-warning.svg +6 -0
  46. package/src/assets/icons/more-dots.svg +5 -0
  47. package/src/assets/icons/time-table.svg +7 -0
  48. package/src/assets/icons/toast-error.svg +3 -0
  49. package/src/assets/icons/toast-info.svg +3 -0
  50. package/src/assets/icons/toast-success.svg +3 -0
  51. package/src/assets/icons/toast-warning.svg +3 -0
  52. package/src/assets/icons/trash-table.svg +7 -0
  53. package/src/assets/tech-work.png +0 -0
  54. package/src/components/BaseCheckbox/BaseCheckbox.vue +76 -46
  55. package/src/components/BaseChips/BaseChips.vue +3 -1
  56. package/src/components/BaseDefaultPages/BaseDefaultPages.vue +140 -0
  57. package/src/components/BaseDefaultPages/README.md +128 -0
  58. package/src/components/BaseOpenedListItem/BaseOpenedListItem.vue +3 -3
  59. package/src/components/BasePageLoader/BasePageLoader.vue +211 -0
  60. package/src/components/BasePageLoader/README.md +80 -0
  61. package/src/components/BaseRadio/BaseRadio.vue +266 -233
  62. package/src/components/BaseSelect/BaseSelect.vue +7 -3
  63. package/src/components/BaseTable/BaseTable.vue +411 -0
  64. package/src/components/BaseTable/README.md +294 -0
  65. package/src/components/BaseTabs/BaseTabs.vue +193 -0
  66. package/src/components/BaseToast/BaseToast.vue +200 -0
  67. package/src/components/BaseToast/README.md +103 -0
  68. package/src/components/BaseTooltip/BaseTooltip.vue +1 -0
  69. package/src/components/BaseUpload/BaseUpload.vue +36 -2
  70. package/src/components/BaseUpload/CropModal.vue +210 -0
  71. package/src/composables/useToast.ts +10 -0
  72. package/src/index.ts +20 -13
  73. package/src/plugins/toastPlugin.ts +100 -0
  74. package/src/store/toast.ts +59 -0
  75. package/src/styles/root.scss +2 -0
  76. package/src/styles/toast.scss +36 -0
  77. package/src/types/default-pages.d.ts +6 -0
  78. package/src/types/loading-page.d.ts +12 -0
  79. package/src/types/pagination.d.ts +1 -0
  80. package/src/types/tab.d.ts +17 -0
  81. package/src/types/table.d.ts +33 -0
  82. package/src/types/toast.d.ts +25 -0
  83. package/src/types/uploadedFile.d.ts +7 -0
  84. package/webpack.config.js +12 -0
  85. package/dist/components/Toaster/Toaster.vue.d.ts +0 -80
  86. package/dist/components/Toaster/timer.d.ts +0 -12
  87. package/dist/plugins/toasterPlugin.d.ts +0 -26
  88. package/src/components/DataTable/DataTable.vue +0 -169
  89. package/src/components/DataTable/README.md +0 -57
  90. package/src/components/Spinner/README.md +0 -35
  91. package/src/components/Spinner/Spinner.vue +0 -60
  92. package/src/components/Toaster/README.md +0 -70
  93. package/src/components/Toaster/Toaster.vue +0 -235
  94. package/src/components/Toaster/timer.ts +0 -45
  95. package/src/components/Tooltip/README.md +0 -37
  96. package/src/components/Tooltip/Tooltip.vue +0 -96
  97. package/src/components/icons/CloseIcon.vue +0 -5
  98. package/src/components/icons/ErrorIcon.vue +0 -7
  99. package/src/components/icons/InfoIcon.vue +0 -7
  100. package/src/components/icons/SuccessIcon.vue +0 -6
  101. package/src/components/icons/WarningIcon.vue +0 -7
  102. package/src/plugins/toasterPlugin.ts +0 -179
@@ -0,0 +1,103 @@
1
+ # Компонент `BaseToast` и Плагин `ToastPlugin`
2
+
3
+ Компонент `BaseToast` — это универсальный компонент уведомлений (тостов) для отображения кратких сообщений пользователю с поддержкой различных типов (инфо, успех, предупреждение, ошибка) и кастомизацией. Плагин `ToastPlugin` интегрирует компонент в приложение Vue.js, предоставляя удобный API для управления тостами через глобальный объект `$toast`.
4
+
5
+ ## Использование
6
+
7
+ ### Базовый пример
8
+ Используйте composable `useToast` для вызова тостов из любого компонента:
9
+
10
+ ```vue
11
+ <template>
12
+ <div>
13
+ <base-button @click="openToast">Показать тост</base-button>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { useToast } from '../composables/useToast';
19
+
20
+ const toast = useToast();
21
+
22
+ const openToast = () => {
23
+ toast.show('toast-1', {
24
+ title: 'Успех',
25
+ type: 'success',
26
+ description: 'Операция выполнена успешно!',
27
+ showIcon: true,
28
+ closable: true,
29
+ primaryActionText: 'Подтвердить',
30
+ secondaryActionText: 'Отмена',
31
+ });
32
+ };
33
+ </script>
34
+ ```
35
+
36
+ ### API
37
+ #### Методы
38
+ - **`$toast.show(id: string, props: IBaseToastProps)`**: Показывает новый тост.
39
+ - `id`: Уникальный идентификатор тоста (тосты с одинаковым `id` заменяют друг друга).
40
+ - `props`: Объект с настройками тоста (см. ниже).
41
+ - **`$toast.hide(id: string)`**: Скрывает тост по указанному `id`.
42
+ - **`$toast.clear()`**: Удаляет все тосты.
43
+
44
+ #### Свойства `IBaseToastProps`
45
+ - `title: string` (обязательно): Заголовок тоста.
46
+ - `type?: 'info' | 'success' | 'warning' | 'error'`: Тип тоста (по умолчанию `'info'`).
47
+ - `withBackground?: boolean`: Добавляет цветной фон в зависимости от типа (по умолчанию `false`).
48
+ - `description?: string`: Описание тоста.
49
+ - `showIcon?: boolean`: Показывать иконку (по умолчанию `true`).
50
+ - `closable?: boolean`: Показывать кнопку закрытия (по умолчанию `true`).
51
+ - `primaryActionText?: string`: Текст для основной кнопки действия.
52
+ - `secondaryActionText?: string`: Текст для вторичной кнопки действия.
53
+ - `duration?: number`: Время отображения в миллисекундах (по умолчанию рассчитывается: 5000 мс для текста ≤ 80 символов, 10000 мс для > 80 символов).
54
+
55
+ ### Примеры
56
+
57
+ #### Тост с кастомным временем
58
+ ```typescript
59
+ toast.show('toast-custom', {
60
+ title: 'Кастомный тост',
61
+ type: 'warning',
62
+ description: 'Это сообщение покажется на 3 секунды.',
63
+ duration: 3000,
64
+ });
65
+ ```
66
+
67
+ #### Тост с действиями
68
+ ```typescript
69
+ toast.show('toast-action', {
70
+ title: 'Действие',
71
+ type: 'info',
72
+ primaryActionText: 'Подтвердить',
73
+ secondaryActionText: 'Отмена',
74
+ onPrimaryAction: () => console.log('Подтверждено'),
75
+ onSecondaryAction: () => console.log('Отменено'),
76
+ });
77
+ ```
78
+
79
+ #### Замена тоста
80
+ ```typescript
81
+ toast.show('toast-1', { title: 'Тост 1', type: 'success' });
82
+ toast.show('toast-1', { title: 'Тост 2', type: 'success' }); // Заменит первый тост
83
+ ```
84
+
85
+ #### Несколько тостов
86
+ ```typescript
87
+ toast.show('toast-info', { title: 'Инфо', type: 'info' });
88
+ toast.show('toast-success', { title: 'Успех', type: 'success' });
89
+ ```
90
+ Тосты отображаются в колонке (максимум 4 на десктопе, 3 на мобильных).
91
+
92
+
93
+ ### Анимации
94
+ Анимации `slideIn` и `slideOut` определены в `BaseToast.vue`. Их можно переопределить, добавив собственные `@keyframes` в глобальные стили.
95
+
96
+ ## Поведение
97
+
98
+ - **Позиционирование**: Тосты отображаются в правом верхнему углу экрана (`top: 20px`, `right: 20px`) с максимальной шириной 620px на десктопе. На мобильных устройствах (ширина ≤ 768px) растягиваются на всю ширину с отступами 8px.
99
+ - **Автозакрытие**: По умолчанию 5 секунд для сообщений ≤ 80 символов, 10 секунд для > 80 символов. Можно задать кастомное время через `duration`.
100
+ - **Замена тостов**: Тосты с одинаковым `id` заменяют друг друга. Тосты с разными `id` отображаются в колонке.
101
+ - **Ограничение**: Максимум 4 тоста на десктопе, 3 на мобильных. При превышении удаляется самый старый тост.
102
+ - **Закрытие**: Тосты закрываются при нажатии на кнопку закрытия (если `closable`), по истечении времени или при смене страницы (событие `popstate`).
103
+
@@ -32,6 +32,7 @@
32
32
  <script setup lang="ts">
33
33
  import { computed, onMounted, onUnmounted, ref } from 'vue';
34
34
  import type { ITooltipProps } from '../../types/tooltip';
35
+ import BaseIcon from '../BaseIcon/BaseIcon.vue';
35
36
 
36
37
  const props = withDefaults(defineProps<ITooltipProps>(), {
37
38
  position: 'top',
@@ -111,10 +111,12 @@ import BaseIcon from '../BaseIcon/BaseIcon.vue';
111
111
  import { useModal } from '../../composables/useModal';
112
112
  import type { UploadedFile, IpropsUpload } from '../../types/uploadedFile.d';
113
113
  import ImageModal from './ImageModal.vue';
114
+ import CropModal from './CropModal.vue';
114
115
 
115
116
  const props = withDefaults(defineProps<IpropsUpload>(), {
116
117
  multiple: true,
117
118
  maxFileSize: 25 * 1024 * 1024, // 25 MB в байтах
119
+ enableCrop: false,
118
120
  });
119
121
 
120
122
  const emit = defineEmits(['update:files']);
@@ -141,6 +143,11 @@ const triggerFileInput = () => {
141
143
  fileInput.value?.click();
142
144
  };
143
145
 
146
+ const isImageFile = (file: File) => {
147
+ const fileExtension = `.${file.name.split('.').pop()?.toLowerCase() || ''}`;
148
+ return mediaExtensions.includes(fileExtension);
149
+ };
150
+
144
151
  const handleFileUpload = (event: Event) => {
145
152
  const target = event.target as HTMLInputElement;
146
153
  const files = target.files;
@@ -161,7 +168,11 @@ const handleFileUpload = (event: Event) => {
161
168
  return;
162
169
  }
163
170
 
164
- uploadedFiles.value.push({ name: file.name, size: file.size, file });
171
+ if (props.enableCrop && isImageFile(file)) {
172
+ openCropModal(file);
173
+ } else {
174
+ uploadedFiles.value.push({ name: file.name, size: file.size, file });
175
+ }
165
176
  });
166
177
 
167
178
  isUploading.value = false;
@@ -169,6 +180,29 @@ const handleFileUpload = (event: Event) => {
169
180
  }
170
181
  };
171
182
 
183
+ const openCropModal = (file: File) => {
184
+ const imageUrl = URL.createObjectURL(file);
185
+
186
+ modal.open('crop-modal', {
187
+ closable: false,
188
+ imageUrl,
189
+ fileName: file.name,
190
+ cancelText: props.cropTexts?.cancel,
191
+ confirmText: props.cropTexts?.confirm,
192
+ onConfirm: (croppedFile: File) => {
193
+ uploadedFiles.value.push({
194
+ name: croppedFile.name,
195
+ size: croppedFile.size,
196
+ file: croppedFile
197
+ });
198
+ URL.revokeObjectURL(imageUrl);
199
+ },
200
+ onCancel: () => {
201
+ URL.revokeObjectURL(imageUrl);
202
+ }
203
+ }, CropModal);
204
+ };
205
+
172
206
  const removeFile = (index: number, isMedia: boolean) => {
173
207
  const files = isMedia ? mediaFiles.value : otherFiles.value;
174
208
  const globalIndex = uploadedFiles.value.indexOf(files[index]);
@@ -355,7 +389,7 @@ gap: var(--spacing-l);
355
389
  }
356
390
 
357
391
  &__error {
358
- color: var(--primary-red-500);
392
+ color: var(--error-red);
359
393
  margin-top: 0.5rem;
360
394
  font-size: var(--typography-text-s-regular);
361
395
  }
@@ -0,0 +1,210 @@
1
+ <template>
2
+ <div class="crop-modal">
3
+ <div class="crop-modal__container">
4
+ <div class="crop-modal__body">
5
+ <div class="crop-modal__cropper-container">
6
+ <Cropper
7
+ ref="cropperRef"
8
+ class="crop-modal__cropper"
9
+ :src="modalProps.imageUrl"
10
+ :stencil-props="{
11
+ aspectRatio: NaN,
12
+ movable: true,
13
+ resizable: true,
14
+ }"
15
+ :resize-image="{
16
+ adjustStencil: false
17
+ }"
18
+ />
19
+ </div>
20
+ </div>
21
+
22
+ <div class="crop-modal__footer">
23
+ <base-button
24
+ color="secondary"
25
+ size="small"
26
+ @click="handleCancel"
27
+ class="crop-modal__button"
28
+ >
29
+ {{ modalProps.cancelText || 'Отменить' }}
30
+ </base-button>
31
+
32
+ <base-button
33
+ color="primary"
34
+ size="small"
35
+ @click="handleConfirm"
36
+ class="crop-modal__button"
37
+ >
38
+ {{ modalProps.confirmText || 'Применить' }}
39
+ </base-button>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import {ref} from 'vue';
47
+ import {Cropper} from 'vue-advanced-cropper';
48
+ import 'vue-advanced-cropper/dist/style.css';
49
+ import BaseButton from '../BaseButton/BaseButton.vue';
50
+ import {useModal} from '../../composables/useModal';
51
+
52
+ interface CropModalProps {
53
+ modalProps: {
54
+ imageUrl: string;
55
+ fileName: string;
56
+ onConfirm: (croppedFile: File) => void;
57
+ onCancel: () => void;
58
+ cancelText?: string;
59
+ confirmText?: string;
60
+ };
61
+ }
62
+
63
+ const props = defineProps<CropModalProps>();
64
+ const modal = useModal();
65
+
66
+ const cropperRef = ref();
67
+
68
+ const handleCancel = () => {
69
+ props.modalProps.onCancel();
70
+ modal.close('crop-modal');
71
+ };
72
+
73
+ const handleConfirm = () => {
74
+ if (!cropperRef.value) return;
75
+
76
+ const {canvas} = cropperRef.value.getResult();
77
+
78
+ if (canvas) {
79
+ canvas.toBlob((blob: Blob | null) => {
80
+ if (blob) {
81
+ const croppedFile = new File([blob], props.modalProps.fileName, {
82
+ type: 'image/jpeg',
83
+ lastModified: Date.now(),
84
+ });
85
+
86
+ props.modalProps.onConfirm(croppedFile);
87
+ modal.close('crop-modal');
88
+ }
89
+ }, 'image/jpeg', 0.9);
90
+ }
91
+ };
92
+ </script>
93
+
94
+ <style lang="scss" scoped>
95
+ @import '../../styles/variables';
96
+ @import '../../styles/root';
97
+
98
+ .crop-modal {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ max-width: 588px;
103
+ max-height: 776px;
104
+
105
+ @media #{$mobile-max-576} {
106
+ max-width: 100%;
107
+ width: 100%;
108
+ }
109
+
110
+ &__container {
111
+ width: 100%;
112
+ background: rgba(0, 0, 0, 0.3);
113
+ }
114
+
115
+ &__body {
116
+ flex: 1;
117
+ overflow: hidden;
118
+ }
119
+
120
+ &__cropper-container {
121
+ height: 500px;
122
+ width: 100%;
123
+ overflow: hidden;
124
+ display: flex;
125
+ align-items: center;
126
+
127
+ @media #{$mobile-max-375} {
128
+ overflow: unset;
129
+ }
130
+ }
131
+
132
+ &__footer {
133
+ display: flex;
134
+ gap: var(--spacing-xl);
135
+ justify-content: space-between;
136
+ padding: var(--spacing-xl);
137
+ background-color: var(--primary-black-white);
138
+
139
+ @media #{$mobile-max-576} {
140
+ flex-direction: column;
141
+ justify-content: center;
142
+ gap: var(--spacing-l);
143
+ }
144
+ }
145
+
146
+ &__button {
147
+ min-width: 258px;
148
+
149
+ @media #{$mobile-landscape} {
150
+ min-width: unset;
151
+ width: 100%;
152
+ }
153
+ }
154
+ }
155
+
156
+ ::v-deep(.vue-advanced-cropper__background) {
157
+ background: rgba(0, 0, 0, 0.3);
158
+ width: 100%;
159
+ }
160
+
161
+ ::v-deep(.vue-advanced-cropper__foreground) {
162
+ border-color: var(--primary-blue);
163
+ background: rgba(0, 0, 0, 0.3);
164
+ }
165
+
166
+ ::v-deep(.vue-simple-handler) {
167
+ background: var(--primary-blue);
168
+ border-color: white;
169
+ }
170
+
171
+ ::v-deep(.vue-simple-handler:hover) {
172
+ background: var(--primary-blue-deep);
173
+ }
174
+
175
+ ::v-deep(.vue-simple-line) {
176
+ border-color: var(--primary-blue);
177
+ }
178
+ </style>
179
+
180
+ <style lang="scss">
181
+ @import '../../styles/variables';
182
+ @import '../../styles/root';
183
+
184
+ .base-modal:has(.crop-modal) {
185
+ @media screen and (max-width: 375px) {
186
+ max-height: unset !important;
187
+ height: 100vh !important;
188
+ }
189
+ }
190
+
191
+ .base-modal__content:has(.crop-modal) {
192
+ width: 100% !important;
193
+
194
+ @media screen and (max-width: 375px) {
195
+ overflow-y: unset !important;
196
+ }
197
+ }
198
+
199
+ .base-modal__wrapper:has(.crop-modal) {
200
+ padding: 0 !important;
201
+
202
+ @media #{$mobile-max-767} {
203
+ padding: 0 !important;
204
+ }
205
+
206
+ @media screen and (max-width: 375px) {
207
+ justify-content: start !important;
208
+ }
209
+ }
210
+ </style>
@@ -0,0 +1,10 @@
1
+ import { inject } from 'vue';
2
+ import type { IToastApi } from '../types/toast';
3
+
4
+ export function useToast() {
5
+ const toast = inject<IToastApi>('$toast');
6
+ if (!toast) {
7
+ throw new Error('toast plugin is not installed');
8
+ }
9
+ return toast;
10
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  import { createPinia } from "pinia";
2
- import DataTable from "./components/DataTable/DataTable.vue";
3
- import Tooltip from "./components/Tooltip/Tooltip.vue";
4
- import Spinner from "./components/Spinner/Spinner.vue";
5
2
  import ModalPlugin from "./plugins/modalPlugin";
6
3
  import { useModal } from "./composables/useModal";
7
- import ToasterPlugin, { useToast } from "./plugins/toasterPlugin";
4
+ import { useToast } from "./composables/useToast";
5
+ import ToastPlugin from "./plugins/toastPlugin";
8
6
  import "./sprite"; // Импортируем иконки для генерации спрайта
9
7
  import "./styles/root.scss";
10
8
  import BaseIcon from "./components/BaseIcon/BaseIcon.vue";
@@ -36,15 +34,17 @@ import BaseBadge from "./components/BaseBadge/BaseBadge.vue";
36
34
  import BaseTag from "./components/BaseTag/BaseTag.vue";
37
35
  import BaseBadgeGroup from "./components/BaseBadge/BaseBadgeGroup.vue";
38
36
  import BaseField from "./components/BaseField/BaseField.vue";
37
+ import BaseToast from "./components/BaseToast/BaseToast.vue";
38
+ import BasePageLoader from "./components/BasePageLoader/BasePageLoader.vue";
39
+ import BaseTabs from "./components/BaseTabs/BaseTabs.vue";
40
+ import BaseTable from "./components/BaseTable/BaseTable.vue";
41
+ import BaseDefaultPages from "./components/BaseDefaultPages/BaseDefaultPages.vue";
39
42
 
40
43
  const components = {
41
44
  BaseModal,
42
45
  BaseTag,
43
46
  BaseBadge,
44
47
  BaseBadgeGroup,
45
- DataTable,
46
- Tooltip,
47
- Spinner,
48
48
  BaseIcon,
49
49
  BaseBreadCrumbs,
50
50
  BaseButton,
@@ -69,7 +69,12 @@ const components = {
69
69
  BaseChips,
70
70
  BaseSwiper,
71
71
  BaseUpload,
72
- BaseField
72
+ BaseField,
73
+ BaseToast,
74
+ BasePageLoader,
75
+ BaseTabs,
76
+ BaseTable,
77
+ BaseDefaultPages
73
78
  };
74
79
 
75
80
  // Функция для загрузки sprite.svg
@@ -118,7 +123,7 @@ export default {
118
123
  loadSprite();
119
124
 
120
125
  app.use(ModalPlugin);
121
- app.use(ToasterPlugin);
126
+ app.use(ToastPlugin);
122
127
  },
123
128
  };
124
129
 
@@ -126,10 +131,7 @@ export {
126
131
  BaseModal,
127
132
  BaseBadgeGroup,
128
133
  BaseBadge,
129
- DataTable,
130
134
  BaseTag,
131
- Tooltip,
132
- Spinner,
133
135
  useModal,
134
136
  useToast,
135
137
  BaseIcon,
@@ -156,5 +158,10 @@ export {
156
158
  BaseChips,
157
159
  BaseSwiper,
158
160
  BaseUpload,
159
- BaseField
161
+ BaseField,
162
+ BaseToast,
163
+ BasePageLoader,
164
+ BaseTabs,
165
+ BaseTable,
166
+ BaseDefaultPages
160
167
  };
@@ -0,0 +1,100 @@
1
+ import { createApp, h, provide, defineComponent } from 'vue';
2
+ import { useToastStore } from '../store/toast';
3
+ import BaseToast from '../components/BaseToast/BaseToast.vue';
4
+ import { watch, ref } from 'vue';
5
+ import type { IBaseToastProps } from '../types/toast';
6
+
7
+ export default {
8
+ install(app: any) {
9
+ console.log('Installing ToastPlugin...');
10
+ const toastStore = useToastStore();
11
+
12
+ let toastContainer = document.querySelector('.toast-container');
13
+ if (!toastContainer) {
14
+ toastContainer = document.createElement('div');
15
+ toastContainer.classList.add('toast-container');
16
+ document.body.appendChild(toastContainer);
17
+ }
18
+
19
+ const $toast = {
20
+ show(id: string, props: IBaseToastProps) {
21
+ toastStore.addToast(id, props);
22
+ },
23
+ hide(id: string) {
24
+ toastStore.removeToast(id);
25
+ },
26
+ clear() {
27
+ toastStore.clearToasts();
28
+ },
29
+ };
30
+
31
+ app.config.globalProperties.$toast = $toast;
32
+ app.provide('$toast', $toast);
33
+
34
+ const ToastContainerComponent = defineComponent({
35
+ setup() {
36
+ provide('$toast', $toast);
37
+ const toasts = ref(toastStore.toasts);
38
+
39
+ watch(
40
+ () => toastStore.toasts,
41
+ (newToasts) => {
42
+ toasts.value = newToasts;
43
+ },
44
+ { deep: true }
45
+ );
46
+
47
+ return () =>
48
+ h(
49
+ 'div',
50
+ { class: 'toast-container-inner', 'data-has-toasts': toasts.value.some(t => t.isOpen) },
51
+ h(
52
+ 'transition-group',
53
+ { name: 'toast', tag: 'div' },
54
+ toasts.value.map(toast =>
55
+ h(
56
+ BaseToast,
57
+ {
58
+ key: toast.id,
59
+ ...toast.props,
60
+ isOpen: toast.isOpen,
61
+ onClose: () => toastStore.removeToast(toast.id),
62
+ onPrimaryAction: () => toastStore.removeToast(toast.id),
63
+ onSecondaryAction: () => toastStore.removeToast(toast.id),
64
+ }
65
+ )
66
+ )
67
+ )
68
+ );
69
+ },
70
+ });
71
+
72
+ let toastApp = (toastContainer as any).__vue_app__;
73
+ if (!toastApp) {
74
+ toastApp = createApp(ToastContainerComponent);
75
+ toastApp.mount(toastContainer);
76
+ (toastContainer as any).__vue_app__ = toastApp;
77
+ }
78
+
79
+ window.addEventListener('popstate', () => {
80
+ toastStore.clearToasts();
81
+ });
82
+
83
+ const cleanup = () => {
84
+ toastApp.unmount();
85
+ window.removeEventListener('popstate', () => toastStore.clearToasts());
86
+ if (document.body.contains(toastContainer)) {
87
+ document.body.removeChild(toastContainer);
88
+ }
89
+ };
90
+
91
+ if (app.unmount) {
92
+ const originalUnmount = app.unmount;
93
+ app.unmount = function () {
94
+ cleanup();
95
+ originalUnmount.call(app);
96
+ };
97
+ }
98
+ app._container?.addEventListener('beforeunload', cleanup);
99
+ },
100
+ };
@@ -0,0 +1,59 @@
1
+ import { defineStore } from 'pinia';
2
+ import type { IBaseToastProps, IToast } from '../types/toast';
3
+
4
+ export const useToastStore = defineStore('toast', {
5
+ state: () => ({
6
+ toasts: [] as IToast[],
7
+ }),
8
+ actions: {
9
+ addToast(id: string, props: IBaseToastProps) {
10
+ const messageLength = (props.title + (props.description || '')).length;
11
+ const duration = props.duration ?? (messageLength > 80 ? 10000 : 5000);
12
+
13
+ const existingToastIndex = this.toasts.findIndex(t => t.id === id);
14
+ if (existingToastIndex !== -1) {
15
+ const existingToast = this.toasts[existingToastIndex];
16
+ if (existingToast.timeoutId) {
17
+ clearTimeout(existingToast.timeoutId);
18
+ }
19
+ this.toasts.splice(existingToastIndex, 1);
20
+ }
21
+
22
+ const maxToasts = window.innerWidth <= 768 ? 3 : 4;
23
+ if (this.toasts.length >= maxToasts) {
24
+ const oldestToast = this.toasts.shift();
25
+ if (oldestToast?.timeoutId) {
26
+ clearTimeout(oldestToast.timeoutId);
27
+ }
28
+ }
29
+
30
+ const toast: IToast = { id, props, isOpen: true };
31
+ this.toasts.push(toast);
32
+
33
+ toast.timeoutId = window.setTimeout(() => {
34
+ this.removeToast(id);
35
+ }, duration);
36
+ },
37
+ removeToast(id: string) {
38
+ const index = this.toasts.findIndex(t => t.id === id);
39
+ if (index !== -1) {
40
+ this.toasts[index].isOpen = false;
41
+ setTimeout(() => {
42
+ const toast = this.toasts[index];
43
+ if (toast?.timeoutId) {
44
+ clearTimeout(toast.timeoutId);
45
+ }
46
+ this.toasts.splice(index, 1);
47
+ }, 300);
48
+ }
49
+ },
50
+ clearToasts() {
51
+ this.toasts.forEach(toast => {
52
+ if (toast.timeoutId) {
53
+ clearTimeout(toast.timeoutId);
54
+ }
55
+ });
56
+ this.toasts = [];
57
+ },
58
+ },
59
+ });
@@ -1,4 +1,6 @@
1
1
  @import "./index.scss";
2
+ @import "./toast.scss";
3
+
2
4
  :root {
3
5
  /* Primary colors */
4
6
  --primary-blue-deep: #0085BE;
@@ -0,0 +1,36 @@
1
+ .toast-container {
2
+ position: fixed;
3
+ top: 20px;
4
+ right: 20px;
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: 8px;
8
+ z-index: 10000; // Высокий z-index для отображения поверх контента
9
+ max-width: 620px;
10
+
11
+ @media (max-width: 768px) {
12
+ right: 8px;
13
+ left: 8px;
14
+ }
15
+
16
+ .toast-container-inner {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: 8px;
20
+
21
+ &[data-has-toasts='true'] {
22
+ display: flex;
23
+ }
24
+ }
25
+ }
26
+
27
+ .toast-enter-active,
28
+ .toast-leave-active {
29
+ transition: all 0.3s ease;
30
+ }
31
+
32
+ .toast-enter-from,
33
+ .toast-leave-to {
34
+ transform: translateY(100%);
35
+ opacity: 0;
36
+ }
@@ -0,0 +1,6 @@
1
+ export interface IDefaultPagesProps {
2
+ title: string;
3
+ description: string;
4
+ buttonText: string;
5
+ type: 'tech-work' | '404'
6
+ }