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.
- package/dist/assets/0e28e37419c99ac65b12.png +0 -0
- package/dist/assets/264165b2b0e8a6840eb0.png +0 -0
- package/dist/components/BaseBadge/BaseBadge.vue.d.ts +1 -1
- package/dist/components/BaseButton/BaseButton.vue.d.ts +3 -3
- package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +4 -4
- package/dist/components/{Spinner/Spinner.vue.d.ts → BaseDefaultPages/BaseDefaultPages.vue.d.ts} +9 -15
- package/dist/components/BaseDropdown/BaseDropdown.vue.d.ts +3 -3
- package/dist/components/BaseField/BaseField.vue.d.ts +2 -2
- package/dist/components/BaseInput/BaseInput.vue.d.ts +7 -7
- package/dist/components/BaseInputCalendar/BaseInputCalendar.vue.d.ts +5 -5
- package/dist/components/BaseInputCurrency/BaseInputCurrency.vue.d.ts +6 -6
- package/dist/components/BaseInputEmail/BaseInputEmail.vue.d.ts +5 -5
- package/dist/components/BaseInputPhone/BaseInputPhone.vue.d.ts +5 -5
- package/dist/components/BaseOpenedListItem/BaseOpenedListItem.vue.d.ts +3 -3
- package/dist/components/{Tooltip/Tooltip.vue.d.ts → BasePageLoader/BasePageLoader.vue.d.ts} +24 -11
- package/dist/components/BasePagination/BasePagination.vue.d.ts +1 -1
- package/dist/components/BaseRadio/BaseRadio.vue.d.ts +4 -4
- package/dist/components/BaseSegmentedButtons/BaseSegmentedButtons.vue.d.ts +3 -3
- package/dist/components/BaseSelect/BaseSelect.vue.d.ts +4 -4
- package/dist/components/BaseTable/BaseTable.vue.d.ts +44 -0
- package/dist/components/BaseTabs/BaseTabs.vue.d.ts +25 -0
- package/dist/components/BaseTag/BaseTag.vue.d.ts +1 -1
- package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +5 -5
- package/dist/components/BaseToast/BaseToast.vue.d.ts +69 -0
- package/dist/components/BaseToggle/BaseToggle.vue.d.ts +4 -4
- package/dist/components/BaseUpload/BaseUpload.vue.d.ts +11 -0
- package/dist/components/{DataTable/DataTable.vue.d.ts → BaseUpload/CropModal.vue.d.ts} +3 -6
- package/dist/composables/useToast.d.ts +2 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +15 -0
- package/dist/plugins/toastPlugin.d.ts +4 -0
- package/dist/sprite.svg +1 -1
- package/dist/store/toast.d.ts +8 -0
- package/example/App.vue +201 -28
- package/example/TestImage.vue +6 -0
- package/package.json +2 -1
- package/src/assets/404.png +0 -0
- package/src/assets/icons/arrow-down-stick.svg +4 -0
- package/src/assets/icons/edit-table.svg +5 -0
- package/src/assets/icons/ellipsis.svg +5 -0
- package/src/assets/icons/loading-page-default.svg +4 -0
- package/src/assets/icons/loading-page-error.svg +6 -0
- package/src/assets/icons/loading-page-success.svg +5 -0
- package/src/assets/icons/loading-page-warning.svg +6 -0
- package/src/assets/icons/more-dots.svg +5 -0
- package/src/assets/icons/time-table.svg +7 -0
- package/src/assets/icons/toast-error.svg +3 -0
- package/src/assets/icons/toast-info.svg +3 -0
- package/src/assets/icons/toast-success.svg +3 -0
- package/src/assets/icons/toast-warning.svg +3 -0
- package/src/assets/icons/trash-table.svg +7 -0
- package/src/assets/tech-work.png +0 -0
- package/src/components/BaseCheckbox/BaseCheckbox.vue +76 -46
- package/src/components/BaseChips/BaseChips.vue +3 -1
- package/src/components/BaseDefaultPages/BaseDefaultPages.vue +140 -0
- package/src/components/BaseDefaultPages/README.md +128 -0
- package/src/components/BaseOpenedListItem/BaseOpenedListItem.vue +3 -3
- package/src/components/BasePageLoader/BasePageLoader.vue +211 -0
- package/src/components/BasePageLoader/README.md +80 -0
- package/src/components/BaseRadio/BaseRadio.vue +266 -233
- package/src/components/BaseSelect/BaseSelect.vue +7 -3
- package/src/components/BaseTable/BaseTable.vue +411 -0
- package/src/components/BaseTable/README.md +294 -0
- package/src/components/BaseTabs/BaseTabs.vue +193 -0
- package/src/components/BaseToast/BaseToast.vue +200 -0
- package/src/components/BaseToast/README.md +103 -0
- package/src/components/BaseTooltip/BaseTooltip.vue +1 -0
- package/src/components/BaseUpload/BaseUpload.vue +36 -2
- package/src/components/BaseUpload/CropModal.vue +210 -0
- package/src/composables/useToast.ts +10 -0
- package/src/index.ts +20 -13
- package/src/plugins/toastPlugin.ts +100 -0
- package/src/store/toast.ts +59 -0
- package/src/styles/root.scss +2 -0
- package/src/styles/toast.scss +36 -0
- package/src/types/default-pages.d.ts +6 -0
- package/src/types/loading-page.d.ts +12 -0
- package/src/types/pagination.d.ts +1 -0
- package/src/types/tab.d.ts +17 -0
- package/src/types/table.d.ts +33 -0
- package/src/types/toast.d.ts +25 -0
- package/src/types/uploadedFile.d.ts +7 -0
- package/webpack.config.js +12 -0
- package/dist/components/Toaster/Toaster.vue.d.ts +0 -80
- package/dist/components/Toaster/timer.d.ts +0 -12
- package/dist/plugins/toasterPlugin.d.ts +0 -26
- package/src/components/DataTable/DataTable.vue +0 -169
- package/src/components/DataTable/README.md +0 -57
- package/src/components/Spinner/README.md +0 -35
- package/src/components/Spinner/Spinner.vue +0 -60
- package/src/components/Toaster/README.md +0 -70
- package/src/components/Toaster/Toaster.vue +0 -235
- package/src/components/Toaster/timer.ts +0 -45
- package/src/components/Tooltip/README.md +0 -37
- package/src/components/Tooltip/Tooltip.vue +0 -96
- package/src/components/icons/CloseIcon.vue +0 -5
- package/src/components/icons/ErrorIcon.vue +0 -7
- package/src/components/icons/InfoIcon.vue +0 -7
- package/src/components/icons/SuccessIcon.vue +0 -6
- package/src/components/icons/WarningIcon.vue +0 -7
- 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
|
-
|
|
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(--
|
|
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>
|
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
|
|
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(
|
|
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
|
+
});
|
package/src/styles/root.scss
CHANGED
|
@@ -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
|
+
}
|