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,294 @@
|
|
|
1
|
+
# BaseTable – универсальная таблица с пагинацией, сортировкой, выбором строк и анимацией
|
|
2
|
+
|
|
3
|
+
## 1. Обзор
|
|
4
|
+
|
|
5
|
+
`BaseTable` – готовый к использованию UI‑компонент, который покрывает самые распространённые сценарии работы с табличными данными:
|
|
6
|
+
|
|
7
|
+
| Возможность | Как реализовано |
|
|
8
|
+
|-------------|-----------------|
|
|
9
|
+
| **Пагинация** | `BasePagination` под капотом, управляется пропом `pagination`. |
|
|
10
|
+
| **Сортировка** | Указание `sortable: true` в колонке → клик генерирует событие `sort-change`. |
|
|
11
|
+
| **Выбор строк** | Чек‑боксы в каждой строке и «Выбрать всё» в заголовке. |
|
|
12
|
+
| **Отключённые строки** | Флаг `row.disabled` → визуальная стилизация и отключение чек‑бокса/кнопок. |
|
|
13
|
+
| **Кастомные ячейки** | Слоты `cell-{key}` позволяют переопределять отрисовку любой ячейки. |
|
|
14
|
+
| **Колонка‑действий** | Слот `actions` + массив `actions` (иконки + обработчики). |
|
|
15
|
+
| **Меню строки** | Слот `row-menu` (например, выпадающий `BaseDropdown`). |
|
|
16
|
+
|
|
17
|
+
## 2. API компонента
|
|
18
|
+
|
|
19
|
+
### Props
|
|
20
|
+
|
|
21
|
+
| Prop | Тип | По‑умолчанию | Описание |
|
|
22
|
+
|------|-----|--------------|----------|
|
|
23
|
+
| `data` | `Row[]` | `[]` | Массив строк, которые будут показаны в таблице. |
|
|
24
|
+
| `columns` | `Column[]` | `[]` | Описание колонок (см. ниже). |
|
|
25
|
+
| `rowKey` | `string \| ((row: Row) => unknown)` | `undefined` | Ключ/функция, используемая для уникального `key` строки. Если не задано – берётся `row.__uid` или JSON‑строка. |
|
|
26
|
+
| `showRowSelection` | `boolean` | `false` | Показывать чек‑бокс выбора строки и «Выбрать всё» в заголовке. |
|
|
27
|
+
| `checkboxTitle` | `string` | `''` | Текст рядом с чек‑боксом «Выбрать всё». |
|
|
28
|
+
| `showActions` | `boolean` | `false` | Показать колонку‑действий (по‑умолчанию слоты `actions`). |
|
|
29
|
+
| `actions` | `{ key: string; icon: string; handler: (row: Row) => void }[]` | `[]` | Массив действий, отрендеренных в колонке‑действий. Если `showActions` = `true`, а `actions` пустой – в слоте `actions` вы можете задать собственный UI. |
|
|
30
|
+
| `showPagination` | `boolean` | `true` (авто‑вычисляется) | Показать блок пагинации в футере. |
|
|
31
|
+
| `pagination` | `TPaginationProps` | `{ perPage: 10 }` | Параметры пагинации. Поддерживаемые свойства: <br>• `perPage` – количество строк на страницу.<br>• `currentPage` – текущая страница (управляемое значение).<br>• любые остальные свойства, которые принимает `BasePagination`. |
|
|
32
|
+
| `showHeader` | `boolean` | `true` | Если `false`, полностью убирает `<thead>` (можно использовать собственный `slot` `header`). |
|
|
33
|
+
| `rowSelectionDisabled` | `boolean` | `false` | Отключить возможность менять выбранные строки (чек‑боксы будут `disabled`). |
|
|
34
|
+
| `rowClass` | `((row: Row) => string) \| string` | `''` | Дополнительный CSS‑класс для строки. Если функция – возвращает класс в зависимости от строки. |
|
|
35
|
+
| `rowDisabledProp` | `string` | `'disabled'` | Имя свойства в объекте `Row`, которое считается флагом **disabled**. По‑умолчанию `row.disabled`. Позволяет переименовать, если в модели используется другое поле. |
|
|
36
|
+
| `...rest` | — | — | Все остальные props передаются напрямую в `BasePagination` (через `v-bind="pagination"`). |
|
|
37
|
+
|
|
38
|
+
### Events
|
|
39
|
+
|
|
40
|
+
| Event | Payload | Описание |
|
|
41
|
+
|-------|---------|----------|
|
|
42
|
+
| `update:selected` | `Row[]` | Вызывается каждый раз, когда меняется набор выбранных строк. |
|
|
43
|
+
| `select-all` | `boolean` | Было ли отмечено «Выбрать всё». |
|
|
44
|
+
| `sort-change` | `{ key: string; order: 'asc' \| 'desc' }` | Пользователь кликнул по сортируемой колонке. Компонент **не** сортирует данные сам, вы должны обновить массив `data` согласно полученному payload. |
|
|
45
|
+
| `page-change` | `number` | Текущая страница изменилась (клик по пагинации). |
|
|
46
|
+
|
|
47
|
+
### Slots
|
|
48
|
+
|
|
49
|
+
| Slot | Параметры | Описание |
|
|
50
|
+
|------|-----------|----------|
|
|
51
|
+
| `header` | — | Полностью заменяет содержимое `<thead>` (можно переопределить заголовок, добавить собственные ячейки). Если слот не задан, используется авто‑генерация из `columns`. |
|
|
52
|
+
| `cell-{key}` | `{ row: Row; value: any }` | Кастомный рендер ячейки конкретного столбца. Пример: `<template #cell-name="{ row }"><strong>{{ row.name }}</strong></template>` |
|
|
53
|
+
| `actions` | `{ row: Row }` | Содержимое колонки‑действий. По‑умолчанию выводятся кнопки из массива `actions`. |
|
|
54
|
+
| `row-menu` | `{ row: Row }` | Дополнительный столбец‑меню (например, `BaseDropdown`). |
|
|
55
|
+
| `empty` | — | Содержимое строки, показываемой когда `data` пустой (по‑умолчанию – `Нет данных`). |
|
|
56
|
+
| `default` (для `<BaseTable>`) | — | Плейсхолдер – обычно не используется, так как контент задаётся через перечисленные выше слоты. |
|
|
57
|
+
|
|
58
|
+
export interface Row {
|
|
59
|
+
/** Любые пользовательские поля */
|
|
60
|
+
[key: string]: unknown
|
|
61
|
+
/** Флаг, отключающий строку (по‑умолчанию `disabled`) */
|
|
62
|
+
disabled?: boolean
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Column {
|
|
66
|
+
/** Ключ доступа к полю из Row */
|
|
67
|
+
key: string
|
|
68
|
+
/** Текст в заголовке колонки */
|
|
69
|
+
label?: string
|
|
70
|
+
/** Пояснительная подсказка (title) */
|
|
71
|
+
tooltip?: string
|
|
72
|
+
/** Иконки слева и справа от заголовка (компоненты) */
|
|
73
|
+
icons?: [any, any] // любая vue‑компонента
|
|
74
|
+
/** Делает колонку сортируемой */
|
|
75
|
+
sortable?: boolean
|
|
76
|
+
/** Ширина/стилизация – задаётся через CSS (необязательно) */
|
|
77
|
+
width?: string | number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface TPaginationProps {
|
|
81
|
+
perPage?: number // сколько строк на страницу
|
|
82
|
+
currentPage?: number // текущая страница (управляемый проп)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
## 3. Примеры использования
|
|
86
|
+
|
|
87
|
+
### 3.1. Базовая таблица
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<BaseTable :data="rows" :columns="cols" />
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<script setup lang="ts">
|
|
94
|
+
import BaseTable from '@/components/BaseTable/BaseTable.vue'
|
|
95
|
+
|
|
96
|
+
const cols = [
|
|
97
|
+
{ key: 'id', label: 'ID' },
|
|
98
|
+
{ key: 'title', label: 'Заголовок' },
|
|
99
|
+
{ key: 'date', label: 'Дата', sortable: true },
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
const rows = [
|
|
103
|
+
{ id: 1, title: 'Заказ #123', date: '2024‑03‑01' },
|
|
104
|
+
{ id: 2, title: 'Заказ #124', date: '2024‑03‑02' },
|
|
105
|
+
// …
|
|
106
|
+
]
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
> В этом примере все функции по умолчанию (без пагинации, без выбора строк).
|
|
110
|
+
|
|
111
|
+
### 3.2. Выбор строк (чек‑боксы)
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<BaseTable
|
|
115
|
+
:data="items"
|
|
116
|
+
:columns="cols"
|
|
117
|
+
:show-row-selection="true"
|
|
118
|
+
@update:selected="onSelection"
|
|
119
|
+
/>
|
|
120
|
+
</template>
|
|
121
|
+
|
|
122
|
+
<script setup lang="ts">
|
|
123
|
+
import BaseTable from '@/components/BaseTable/BaseTable.vue'
|
|
124
|
+
|
|
125
|
+
const cols = [{ key: 'name', label: 'Имя' }, { key: 'email', label: 'Email' }]
|
|
126
|
+
|
|
127
|
+
const items = [
|
|
128
|
+
{ id: 1, name: 'Иван', email: 'ivan@example.com' },
|
|
129
|
+
{ id: 2, name: 'Мария', email: 'maria@example.com' },
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
function onSelection(selected) {
|
|
133
|
+
console.log('Выбрано строк:', selected)
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
> При активировании `showRowSelection` в заголовке появляется чек‑бокс «Выбрать всё». Выбранные строки доступны в `selected`.
|
|
138
|
+
|
|
139
|
+
<a name="example-sorting"></a>
|
|
140
|
+
|
|
141
|
+
### 3.3. Сортируемые колонки
|
|
142
|
+
|
|
143
|
+
```vue
|
|
144
|
+
<template>
|
|
145
|
+
<BaseTable
|
|
146
|
+
:data="sortedData"
|
|
147
|
+
:columns="cols"
|
|
148
|
+
@sort-change="handleSort"
|
|
149
|
+
/>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<script setup lang="ts">
|
|
153
|
+
import { ref, computed } from 'vue'
|
|
154
|
+
import BaseTable from '@/components/BaseTable/BaseTable.vue'
|
|
155
|
+
|
|
156
|
+
const cols = [
|
|
157
|
+
{ key: 'name', label: 'Имя', sortable: true },
|
|
158
|
+
{ key: 'age', label: 'Возраст', sortable: true },
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
const raw = [
|
|
162
|
+
{ id: 1, name: 'Иван', age: 31 },
|
|
163
|
+
{ id: 2, name: 'Мария', age: 27 },
|
|
164
|
+
{ id: 3, name: 'Петр', age: 45 },
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
const sortKey = ref<string | null>(null)
|
|
168
|
+
const sortOrder = ref<'asc' | 'desc'>('asc')
|
|
169
|
+
|
|
170
|
+
function handleSort({ key, order }) {
|
|
171
|
+
sortKey.value = key
|
|
172
|
+
sortOrder.value = order
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Сортируем массив реактивно */
|
|
176
|
+
const sortedData = computed(() => {
|
|
177
|
+
if (!sortKey.value) return raw
|
|
178
|
+
return [...raw].sort((a, b) => {
|
|
179
|
+
const aVal = a[sortKey.value!]
|
|
180
|
+
const bVal = b[sortKey.value!]
|
|
181
|
+
if (aVal === bVal) return 0
|
|
182
|
+
if (sortOrder.value === 'asc') return aVal > bVal ? 1 : -1
|
|
183
|
+
else return aVal < bVal ? 1 : -1
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
</script>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> Компонент генерирует событие `sort-change`, а реальную сортировку вы делаете в родителе (как показано).
|
|
190
|
+
|
|
191
|
+
<a name="example-pagination"></a>
|
|
192
|
+
|
|
193
|
+
### 3.4. Пагинация
|
|
194
|
+
|
|
195
|
+
```vue
|
|
196
|
+
<template>
|
|
197
|
+
<BaseTable
|
|
198
|
+
:data="bigList"
|
|
199
|
+
:columns="cols"
|
|
200
|
+
:pagination="{ perPage: 20, currentPage: 1 }"
|
|
201
|
+
@page-change="page => console.log('Страница:', page)"
|
|
202
|
+
/>
|
|
203
|
+
</template>
|
|
204
|
+
|
|
205
|
+
<script setup lang="ts">
|
|
206
|
+
import BaseTable from '@/components/BaseTable/BaseTable.vue'
|
|
207
|
+
|
|
208
|
+
const cols = [{ key: 'index', label: '#' }, { key: 'value', label: 'Значение' }]
|
|
209
|
+
|
|
210
|
+
// Генерируем 200 строк
|
|
211
|
+
const bigList = Array.from({ length: 200 }, (_, i) => ({
|
|
212
|
+
index: i + 1,
|
|
213
|
+
value: `Элемент ${i + 1}`,
|
|
214
|
+
}))
|
|
215
|
+
</script>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
> Компонент автоматически рассчитывает количество страниц (`Math.ceil(data.length / perPage)`) и отображает `BasePagination` в футере.
|
|
219
|
+
|
|
220
|
+
<a name="example-disabled"></a>
|
|
221
|
+
|
|
222
|
+
### 3.5. Отключённые строки
|
|
223
|
+
|
|
224
|
+
```vue
|
|
225
|
+
<template>
|
|
226
|
+
<BaseTable
|
|
227
|
+
:data="rows"
|
|
228
|
+
:columns="cols"
|
|
229
|
+
:show-row-selection="true"
|
|
230
|
+
:show-actions="true"
|
|
231
|
+
:actions="rowActions"
|
|
232
|
+
/>
|
|
233
|
+
</template>
|
|
234
|
+
|
|
235
|
+
<script setup lang="ts">
|
|
236
|
+
import BaseTable from '@/components/BaseTable/BaseTable.vue'
|
|
237
|
+
|
|
238
|
+
const cols = [{ key: 'name', label: 'Имя' }]
|
|
239
|
+
|
|
240
|
+
const rows = [
|
|
241
|
+
{ id: 1, name: 'Активный 1' },
|
|
242
|
+
{ id: 2, name: 'Отключённый', disabled: true }, // <-- будет в сером стиле
|
|
243
|
+
{ id: 3, name: 'Активный 2' },
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
const rowActions = [
|
|
247
|
+
{
|
|
248
|
+
key: 'edit',
|
|
249
|
+
icon: 'edit',
|
|
250
|
+
handler: (row) => console.log('edit', row),
|
|
251
|
+
},
|
|
252
|
+
]
|
|
253
|
+
</script>
|
|
254
|
+
|
|
255
|
+
* Что происходит:
|
|
256
|
+
- Строка с полем `disabled: true` получает класс `--is-disabled`.
|
|
257
|
+
- Чек‑бокс и все кнопки в этой строке получают атрибут `disabled`.
|
|
258
|
+
- `pointer-events` отключены, поэтому пользователь не может кликнуть по ячейкам.
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
### 3.6. Кастомные ячейки через слоты
|
|
262
|
+
|
|
263
|
+
<BaseTable :data="products" :columns="cols">
|
|
264
|
+
<!-- Ячейка «price» с форматированием -->
|
|
265
|
+
<template #cell-price="{ row }">
|
|
266
|
+
<span class="price">{{ formatPrice(row.price) }}</span>
|
|
267
|
+
</template>
|
|
268
|
+
|
|
269
|
+
<!-- Ячейка «status» с цветовой меткой -->
|
|
270
|
+
<template #cell-status="{ row }">
|
|
271
|
+
<span :class="['status', row.status]">{{ row.status }}</span>
|
|
272
|
+
</template>
|
|
273
|
+
</BaseTable>
|
|
274
|
+
|
|
275
|
+
### 3.7. Кастомные действия (вместо массива `actions`)
|
|
276
|
+
|
|
277
|
+
Если вам нужен более сложный UI в колонке‑действий, просто переопределите слот `actions`:
|
|
278
|
+
|
|
279
|
+
<BaseTable
|
|
280
|
+
:data="users"
|
|
281
|
+
:columns="cols"
|
|
282
|
+
:show-actions="true"
|
|
283
|
+
>
|
|
284
|
+
<template #actions="{ row }">
|
|
285
|
+
<BaseButton size="extra-small" @click="openProfile(row)">
|
|
286
|
+
<BaseIcon name="user" />
|
|
287
|
+
</BaseButton>
|
|
288
|
+
|
|
289
|
+
<BaseDropdown>
|
|
290
|
+
<BaseDropdownItem @click="archive(row)">В архив</BaseDropdownItem>
|
|
291
|
+
<BaseDropdownItem @click="remove(row)" danger>Удалить</BaseDropdownItem>
|
|
292
|
+
</BaseDropdown>
|
|
293
|
+
</template>
|
|
294
|
+
</BaseTable>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="base-tabs" :class="classList">
|
|
3
|
+
<div class="base-tabs__header">
|
|
4
|
+
<base-button
|
|
5
|
+
v-for="tab in tabs"
|
|
6
|
+
:key="tab.id"
|
|
7
|
+
size="custom"
|
|
8
|
+
color="custom"
|
|
9
|
+
class="base-tabs__tab"
|
|
10
|
+
:class="{ 'base-tabs__tab--active': activeTab === tab.id }"
|
|
11
|
+
@click="selectTab(tab.id)"
|
|
12
|
+
:disabled="tab.disabled"
|
|
13
|
+
>
|
|
14
|
+
<div v-if="tab.icon" class="base-tab__icon">
|
|
15
|
+
<component :is="tab.icon" />
|
|
16
|
+
</div>
|
|
17
|
+
<span>{{ tab.title }}</span>
|
|
18
|
+
</base-button>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="base-tabs__content">
|
|
21
|
+
<slot />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { ref, computed, watch } from 'vue';
|
|
28
|
+
import type { TTabProps } from '../../types/tab.d';
|
|
29
|
+
import BaseButton from '../BaseButton/BaseButton.vue';
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(defineProps<TTabProps>(), {
|
|
32
|
+
size: 'medium',
|
|
33
|
+
modelValue: 0,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const emit = defineEmits(['update:modelValue']);
|
|
37
|
+
|
|
38
|
+
const activeTab = ref(props.modelValue);
|
|
39
|
+
|
|
40
|
+
watch(() => props.modelValue, (newValue) => {
|
|
41
|
+
activeTab.value = newValue;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const selectTab = (id: number) => {
|
|
45
|
+
if (!props.tabs[id]?.disabled) {
|
|
46
|
+
activeTab.value = id;
|
|
47
|
+
emit('update:modelValue', id);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const classList = computed(() => [`base-tabs--${props.size}`]);
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style lang="scss" scoped>
|
|
55
|
+
@import '../../styles/variables';
|
|
56
|
+
@import '../../styles/root';
|
|
57
|
+
|
|
58
|
+
.base-tabs {
|
|
59
|
+
width: 100%;
|
|
60
|
+
|
|
61
|
+
&__header {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: stretch;
|
|
64
|
+
position: relative;
|
|
65
|
+
z-index: 1;
|
|
66
|
+
|
|
67
|
+
&::before {
|
|
68
|
+
content: '';
|
|
69
|
+
width: 100%;
|
|
70
|
+
border-bottom: 1px solid var(--primary-black-200);
|
|
71
|
+
position: absolute;
|
|
72
|
+
bottom: 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&__tab {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
padding: var(--spacing-m) var(--spacing-l);
|
|
80
|
+
background: none;
|
|
81
|
+
border: none;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
color: var(--primary-black-700);
|
|
84
|
+
transition: color 0.2s ease, border-color 0.2s ease;
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
position: relative;
|
|
87
|
+
|
|
88
|
+
@include hover {
|
|
89
|
+
& {
|
|
90
|
+
color: var(--primary-black);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@include pressed {
|
|
95
|
+
color: var(--primary-black);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@include is-disabled-state {
|
|
99
|
+
color: var(--primary-black-400);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&--active {
|
|
103
|
+
color: var(--primary-blue);
|
|
104
|
+
border-bottom: 2px solid var(--primary-blue);
|
|
105
|
+
z-index: 2;
|
|
106
|
+
|
|
107
|
+
@include hover {
|
|
108
|
+
& {
|
|
109
|
+
color: var(--primary-blue-700);
|
|
110
|
+
border-color: transparent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@include pressed {
|
|
115
|
+
color: var(--primary-blue-deep);
|
|
116
|
+
border-color: transparent;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.base-tab__icon {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Размеры
|
|
127
|
+
&--extra-small {
|
|
128
|
+
.base-tabs__header {
|
|
129
|
+
gap: var(--spacing-l);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.base-tabs__tab {
|
|
133
|
+
height: 38px;
|
|
134
|
+
padding: var(--spacing-s) var(--spacing-m);
|
|
135
|
+
font: var(--typography-text-s-medium);
|
|
136
|
+
gap: var(--spacing-s);
|
|
137
|
+
|
|
138
|
+
&--active {
|
|
139
|
+
font: var(--typography-text-s-semibold);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.base-tab__icon {
|
|
143
|
+
width: 20px;
|
|
144
|
+
height: 20px;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
&--small {
|
|
150
|
+
.base-tabs__header {
|
|
151
|
+
gap: var(--spacing-xl);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.base-tabs__tab {
|
|
155
|
+
height: 48px;
|
|
156
|
+
padding: var(--spacing-m) var(--spacing-l);
|
|
157
|
+
font: var(--typography-text-m-medium);
|
|
158
|
+
gap: var(--spacing-m);
|
|
159
|
+
|
|
160
|
+
&--active {
|
|
161
|
+
font: var(--typography-text-m-semibold);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.base-tab__icon {
|
|
165
|
+
width: 24px;
|
|
166
|
+
height: 24px;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&--medium {
|
|
172
|
+
.base-tabs__header {
|
|
173
|
+
gap: var(--spacing-xl);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.base-tabs__tab {
|
|
177
|
+
height: 56px;
|
|
178
|
+
padding: var(--spacing-l) var(--spacing-xl);
|
|
179
|
+
font: var(--typography-text-l-medium);
|
|
180
|
+
gap: var(--spacing-m);
|
|
181
|
+
|
|
182
|
+
&--active {
|
|
183
|
+
font: var(--typography-text-l-semibold);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.base-tab__icon {
|
|
187
|
+
width: 32px;
|
|
188
|
+
height: 32px;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
</style>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="base-toast" :class="classList" v-show="isOpen">
|
|
3
|
+
<div v-if="showIcon" class="base-toast__icon">
|
|
4
|
+
<base-icon :name="iconName" size="medium" />
|
|
5
|
+
</div>
|
|
6
|
+
<div class="base-toast__content">
|
|
7
|
+
<div class="base-toast__title">
|
|
8
|
+
{{ title }}
|
|
9
|
+
</div>
|
|
10
|
+
<div v-if="description" class="base-toast__description">
|
|
11
|
+
{{ description }}
|
|
12
|
+
</div>
|
|
13
|
+
<div
|
|
14
|
+
v-if="primaryActionText || secondaryActionText"
|
|
15
|
+
class="base-toast__actions"
|
|
16
|
+
>
|
|
17
|
+
<base-button
|
|
18
|
+
v-if="secondaryActionText"
|
|
19
|
+
color="secondary"
|
|
20
|
+
size="extra-small"
|
|
21
|
+
@click="emit('secondaryAction')"
|
|
22
|
+
>
|
|
23
|
+
{{ secondaryActionText }}
|
|
24
|
+
</base-button>
|
|
25
|
+
<base-button
|
|
26
|
+
v-if="primaryActionText"
|
|
27
|
+
color="primary"
|
|
28
|
+
size="extra-small"
|
|
29
|
+
@click="emit('primaryAction')"
|
|
30
|
+
>
|
|
31
|
+
{{ primaryActionText }}
|
|
32
|
+
</base-button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<base-button
|
|
36
|
+
v-if="closable"
|
|
37
|
+
color="custom"
|
|
38
|
+
size="custom"
|
|
39
|
+
class="base-toast__close"
|
|
40
|
+
@click="emit('close')"
|
|
41
|
+
>
|
|
42
|
+
<base-icon name="close" size="medium" />
|
|
43
|
+
</base-button>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import BaseIcon from '../BaseIcon/BaseIcon.vue';
|
|
49
|
+
import BaseButton from '../BaseButton/BaseButton.vue';
|
|
50
|
+
import { computed } from 'vue';
|
|
51
|
+
import type { IBaseToastProps } from '../../types/toast';
|
|
52
|
+
|
|
53
|
+
const props = withDefaults(defineProps<IBaseToastProps>(), {
|
|
54
|
+
type: 'info',
|
|
55
|
+
showIcon: true,
|
|
56
|
+
closable: true,
|
|
57
|
+
withBackground: false,
|
|
58
|
+
isOpen: true,
|
|
59
|
+
duration: undefined,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const emit = defineEmits<{
|
|
63
|
+
(e: 'close'): void;
|
|
64
|
+
(e: 'primaryAction'): void;
|
|
65
|
+
(e: 'secondaryAction'): void;
|
|
66
|
+
}>();
|
|
67
|
+
|
|
68
|
+
const iconName = computed(() => {
|
|
69
|
+
switch (props.type) {
|
|
70
|
+
case 'success':
|
|
71
|
+
return 'toast-success';
|
|
72
|
+
case 'warning':
|
|
73
|
+
return 'toast-warning';
|
|
74
|
+
case 'error':
|
|
75
|
+
return 'toast-error';
|
|
76
|
+
case 'info':
|
|
77
|
+
default:
|
|
78
|
+
return 'toast-info';
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const classList = computed(() => [
|
|
83
|
+
`base-toast--${props.type}`,
|
|
84
|
+
{ 'base-toast--has-description': props.description },
|
|
85
|
+
{ 'base-toast--has-background': props.withBackground },
|
|
86
|
+
]);
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<style lang="scss" scoped>
|
|
90
|
+
@import '../../styles/variables';
|
|
91
|
+
@import '../../styles/root';
|
|
92
|
+
|
|
93
|
+
.base-toast {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: flex-start;
|
|
96
|
+
gap: var(--spacing-s);
|
|
97
|
+
padding: var(--spacing-m);
|
|
98
|
+
border: 1px solid var(--primary-black-200);
|
|
99
|
+
border-radius: var(--corner-radius-m);
|
|
100
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
101
|
+
width: 100%;
|
|
102
|
+
max-width: 620px;
|
|
103
|
+
min-width: 335px;
|
|
104
|
+
background: var(--bg-light);
|
|
105
|
+
position: relative;
|
|
106
|
+
margin: 8px;
|
|
107
|
+
animation: slideIn 0.3s ease forwards;
|
|
108
|
+
|
|
109
|
+
@media (max-width: 768px) {
|
|
110
|
+
max-width: calc(100% - 16px);
|
|
111
|
+
min-width: calc(100% - 16px);
|
|
112
|
+
margin: 8px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&--info,
|
|
116
|
+
&--success,
|
|
117
|
+
&--warning,
|
|
118
|
+
&--error {
|
|
119
|
+
border: none;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&.base-toast--has-background {
|
|
123
|
+
&.base-toast--info {
|
|
124
|
+
background: var(--primary-blue-100);
|
|
125
|
+
}
|
|
126
|
+
&.base-toast--success {
|
|
127
|
+
background: var(--success-green-light-05);
|
|
128
|
+
}
|
|
129
|
+
&.base-toast--warning {
|
|
130
|
+
background: var(--warning-orange-light-05);
|
|
131
|
+
}
|
|
132
|
+
&.base-toast--error {
|
|
133
|
+
background: var(--error-red-light-05);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
&--has-description {
|
|
138
|
+
align-items: stretch;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
&__icon {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: flex-start;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&__content {
|
|
147
|
+
flex: 1;
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
gap: var(--spacing-m);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
&__title {
|
|
154
|
+
color: var(--primary-text-primary);
|
|
155
|
+
font: var(--typography-text-m-medium);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&__description {
|
|
159
|
+
color: var(--primary-text-secondary);
|
|
160
|
+
font: var(--typography-text-s-regular);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&__actions {
|
|
164
|
+
display: flex;
|
|
165
|
+
gap: var(--spacing-l);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&__close {
|
|
169
|
+
height: 100%;
|
|
170
|
+
background: none;
|
|
171
|
+
border: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
&.base-toast--leave-active {
|
|
175
|
+
animation: slideOut 0.3s ease;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@keyframes slideIn {
|
|
180
|
+
from {
|
|
181
|
+
transform: translateY(100%);
|
|
182
|
+
opacity: 0;
|
|
183
|
+
}
|
|
184
|
+
to {
|
|
185
|
+
transform: translateY(0);
|
|
186
|
+
opacity: 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@keyframes slideOut {
|
|
191
|
+
from {
|
|
192
|
+
transform: translateY(0);
|
|
193
|
+
opacity: 1;
|
|
194
|
+
}
|
|
195
|
+
to {
|
|
196
|
+
transform: translateY(100%);
|
|
197
|
+
opacity: 0;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
</style>
|