startup-ui 0.12.0 → 1.0.1

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 (123) hide show
  1. package/AGENTS.md +18 -0
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +1 -0
  4. package/LICENSE +21 -0
  5. package/README.md +54 -0
  6. package/dist/defaults.css +83 -0
  7. package/dist/index.css +1 -1
  8. package/dist/startup-ui.cjs.js +33 -586
  9. package/dist/startup-ui.cjs.js.map +1 -1
  10. package/dist/startup-ui.es.js +7157 -8837
  11. package/dist/startup-ui.es.js.map +1 -1
  12. package/dist/types/components/SActionIcon.d.ts +2 -1
  13. package/dist/types/components/SActionIcon.d.ts.map +1 -1
  14. package/dist/types/components/SCanvas.d.ts +0 -1
  15. package/dist/types/components/SCanvas.d.ts.map +1 -1
  16. package/dist/types/components/SCheckbox.d.ts.map +1 -1
  17. package/dist/types/components/SCheckboxGroup.d.ts +4 -0
  18. package/dist/types/components/SCheckboxGroup.d.ts.map +1 -1
  19. package/dist/types/components/SColumnSettings.d.ts +4 -4
  20. package/dist/types/components/SColumnSettings.d.ts.map +1 -1
  21. package/dist/types/components/SConfirm/SConfirm.d.ts +2 -0
  22. package/dist/types/components/SConfirm/SConfirm.d.ts.map +1 -1
  23. package/dist/types/components/SCopyText.d.ts.map +1 -1
  24. package/dist/types/components/SDatePicker.d.ts +3 -4
  25. package/dist/types/components/SDatePicker.d.ts.map +1 -1
  26. package/dist/types/components/SDialog.d.ts.map +1 -1
  27. package/dist/types/components/SFilterGroup.d.ts +3 -3
  28. package/dist/types/components/SFilterGroup.d.ts.map +1 -1
  29. package/dist/types/components/SFooter.d.ts.map +1 -1
  30. package/dist/types/components/SForm.d.ts.map +1 -1
  31. package/dist/types/components/SFormRow.d.ts.map +1 -1
  32. package/dist/types/components/SHtmlEditor.d.ts +20 -0
  33. package/dist/types/components/SHtmlEditor.d.ts.map +1 -1
  34. package/dist/types/components/SImagePreview.d.ts.map +1 -1
  35. package/dist/types/components/SInput.d.ts +9 -2
  36. package/dist/types/components/SInput.d.ts.map +1 -1
  37. package/dist/types/components/SMenu.d.ts +39 -0
  38. package/dist/types/components/SMenu.d.ts.map +1 -0
  39. package/dist/types/components/SNote.d.ts.map +1 -1
  40. package/dist/types/components/SPagination.d.ts.map +1 -1
  41. package/dist/types/components/SProgressbar.d.ts.map +1 -1
  42. package/dist/types/components/SRadio.d.ts.map +1 -1
  43. package/dist/types/components/SRadioGroup.d.ts +4 -0
  44. package/dist/types/components/SRadioGroup.d.ts.map +1 -1
  45. package/dist/types/components/SSelect.d.ts +6 -0
  46. package/dist/types/components/SSelect.d.ts.map +1 -1
  47. package/dist/types/components/SStatus.d.ts.map +1 -1
  48. package/dist/types/components/STable.d.ts +4 -4
  49. package/dist/types/components/STable.d.ts.map +1 -1
  50. package/dist/types/components/SToggle.d.ts.map +1 -1
  51. package/dist/types/components/STooltip.d.ts +0 -1
  52. package/dist/types/components/STooltip.d.ts.map +1 -1
  53. package/dist/types/components/STree.d.ts +11 -5
  54. package/dist/types/components/STree.d.ts.map +1 -1
  55. package/dist/types/components/SUpload.d.ts +5 -4
  56. package/dist/types/components/SUpload.d.ts.map +1 -1
  57. package/dist/types/components/SVerticalMenu.d.ts.map +1 -1
  58. package/dist/types/components/htmlEditor/contentStyle.d.ts +9 -0
  59. package/dist/types/components/htmlEditor/contentStyle.d.ts.map +1 -0
  60. package/dist/types/components/icons.d.ts +23 -0
  61. package/dist/types/components/icons.d.ts.map +1 -0
  62. package/dist/types/config.d.ts +49 -0
  63. package/dist/types/config.d.ts.map +1 -0
  64. package/dist/types/global-components.d.ts +3 -6
  65. package/dist/types/global-components.d.ts.map +1 -1
  66. package/dist/types/index.d.ts +12 -2
  67. package/dist/types/index.d.ts.map +1 -1
  68. package/dist/types/locale/index.d.ts +49 -0
  69. package/dist/types/locale/index.d.ts.map +1 -0
  70. package/dist/types/locale/messages/en-US.d.ts +4 -0
  71. package/dist/types/locale/messages/en-US.d.ts.map +1 -0
  72. package/dist/types/locale/messages/en.d.ts +4 -0
  73. package/dist/types/locale/messages/en.d.ts.map +1 -0
  74. package/dist/types/locale/messages/ru.d.ts +4 -0
  75. package/dist/types/locale/messages/ru.d.ts.map +1 -0
  76. package/dist/types/locale/types.d.ts +74 -0
  77. package/dist/types/locale/types.d.ts.map +1 -0
  78. package/dist/types/plugin.d.ts +2 -1
  79. package/dist/types/plugin.d.ts.map +1 -1
  80. package/dist/types/utils/deepMerge.d.ts +9 -0
  81. package/dist/types/utils/deepMerge.d.ts.map +1 -0
  82. package/dist/types/utils/options.d.ts +25 -0
  83. package/dist/types/utils/options.d.ts.map +1 -0
  84. package/llms/components/data/sfilter.md +194 -0
  85. package/llms/components/data/spagination.md +114 -0
  86. package/llms/components/data/stable.md +638 -0
  87. package/llms/components/data/stree.md +213 -0
  88. package/llms/components/forms/scheckbox.md +139 -0
  89. package/llms/components/forms/sdatepicker.md +161 -0
  90. package/llms/components/forms/sform.md +240 -0
  91. package/llms/components/forms/shtmleditor.md +143 -0
  92. package/llms/components/forms/sinput.md +165 -0
  93. package/llms/components/forms/sradio.md +164 -0
  94. package/llms/components/forms/sselect.md +149 -0
  95. package/llms/components/forms/sswitch.md +69 -0
  96. package/llms/components/forms/supload.md +189 -0
  97. package/llms/components/interfaces/sactionbar.md +40 -0
  98. package/llms/components/interfaces/sactionicon.md +126 -0
  99. package/llms/components/interfaces/salert.md +87 -0
  100. package/llms/components/interfaces/sbutton.md +167 -0
  101. package/llms/components/interfaces/scolumnsettings.md +204 -0
  102. package/llms/components/interfaces/sconfirm.md +57 -0
  103. package/llms/components/interfaces/scopytext.md +67 -0
  104. package/llms/components/interfaces/sdashboard.md +130 -0
  105. package/llms/components/interfaces/sdialog.md +158 -0
  106. package/llms/components/interfaces/simagepreview.md +98 -0
  107. package/llms/components/interfaces/snote.md +64 -0
  108. package/llms/components/interfaces/sprogressbar.md +48 -0
  109. package/llms/components/interfaces/sstat.md +79 -0
  110. package/llms/components/interfaces/sstatus.md +76 -0
  111. package/llms/components/interfaces/stag.md +70 -0
  112. package/llms/components/interfaces/stimeline.md +47 -0
  113. package/llms/components/interfaces/stoggle.md +120 -0
  114. package/llms/components/interfaces/stooltip.md +88 -0
  115. package/llms/components/template/scanvas.md +61 -0
  116. package/llms/components/template/smenu.md +88 -0
  117. package/llms/components/template/sverticalmenu.md +113 -0
  118. package/llms/llms.txt +49 -0
  119. package/package.json +37 -4
  120. package/dist/types/components/SDropdownMenu.d.ts +0 -39
  121. package/dist/types/components/SDropdownMenu.d.ts.map +0 -1
  122. package/dist/types/components/SHorizontalMenu.d.ts +0 -33
  123. package/dist/types/components/SHorizontalMenu.d.ts.map +0 -1
@@ -0,0 +1,638 @@
1
+ # STable
2
+
3
+ Таблица.
4
+
5
+ <SToggleGroup>
6
+ <SToggle title="В чем отличие от аналогов?">
7
+ <p>В отличие от популярных библиотек компонентов для Vue3:</p>
8
+ <ol>
9
+ <li>Семантика поддерживает и итератор строк-значений, и просто вставку строк как есть.</li>
10
+ <li>Позволяет задать состояние, когда нет данных, одним атрибутом.</li>
11
+ <li>Позволяет сделать горизонтальный скролл сверху таблицы, что полезно для длинных таблиц с большим кол-вом колонок.</li>
12
+ </ol>
13
+ </SToggle>
14
+ </SToggleGroup>
15
+
16
+ ## Базовый пример
17
+
18
+ ```vue
19
+ <template>
20
+ <STable :data="users">
21
+ <template #header>
22
+ <td>Пользователь</td>
23
+ <td>Тариф</td>
24
+ <td>Баланс</td>
25
+ <td>Роль</td>
26
+ <td>Дата регистрации</td>
27
+ <td></td>
28
+ </template>
29
+ <template #row="{ row }">
30
+ <td>{{ row.username }}</td>
31
+ <td>{{ row.plan }}</td>
32
+ <td>{{ row.balance }}</td>
33
+ <td>{{ row.role }}</td>
34
+ <td>{{ row.created_at }}</td>
35
+ <td>
36
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
37
+ @click="deleteUser(row.username)" icon="trash" danger />
38
+ </td>
39
+ </template>
40
+ <template #footer>
41
+ <td>ИТОГО</td>
42
+ <td></td>
43
+ <td>{{ totalBalance }}</td>
44
+ <td></td>
45
+ <td></td>
46
+ <td></td>
47
+ </template>
48
+ </STable>
49
+ </template>
50
+ <script setup>
51
+ import { ref, computed } from 'vue'
52
+
53
+ const users = ref([
54
+ { username: 'Ivanov', role: 'customer', plan: 'Базовый', balance: 3000, created_at: '2025-11-04' },
55
+ { username: 'Stepanov', role: 'customer', plan: 'Базовый', balance: 4500, created_at: '2025-11-05' },
56
+ { username: 'Petrov', role: 'customer', plan: 'Базовый', balance: 1716, created_at: '2025-11-05' },
57
+ ])
58
+
59
+ function deleteUser(username) {
60
+ users.value = users.value.filter(user => user.username !== username)
61
+ }
62
+
63
+ const totalBalance = computed(() => users.value.reduce((acc, user) => acc + user.balance, 0))
64
+ </script>
65
+ ```
66
+
67
+ ## Утилиты выравнивания
68
+
69
+ По умолчанию ячейки выравниваются по позиции колонки: первая — по левому краю, последняя — по правому, остальные — по центру. Выравнивание конкретной ячейки можно переопределить классами на `<td>` — `.left`, `.center`, `.right`, а `.nowrap` запрещает перенос строки:
70
+
71
+ ```vue
72
+ <template>
73
+ <STable :data="rows">
74
+ <template #header>
75
+ <td>Товар</td>
76
+ <td class="right">Цена</td>
77
+ <td class="left">Остаток</td>
78
+ <td class="nowrap">Артикул</td>
79
+ </template>
80
+ <template #row="{ row }">
81
+ <td>{{ row.name }}</td>
82
+ <td class="right">{{ row.price }}</td>
83
+ <td class="left">{{ row.stock }}</td>
84
+ <td class="nowrap">{{ row.sku }}</td>
85
+ </template>
86
+ </STable>
87
+ </template>
88
+ <script setup>
89
+ import { ref } from 'vue'
90
+
91
+ const rows = ref([
92
+ { name: 'Кофемолка', price: '3 200 ₽', stock: 12, sku: 'KF-001-XL' },
93
+ { name: 'Чайник', price: '1 850 ₽', stock: 4, sku: 'CH-204' },
94
+ ])
95
+ </script>
96
+ ```
97
+
98
+ ## Подсветка строк при наведении
99
+
100
+ Для подсветки при наведении добавляем атрибут <strong>hoverable</strong>
101
+
102
+ ```vue
103
+ <template>
104
+ <STable :data="users" hoverable>
105
+ <template #header>
106
+ <td>Пользователь</td>
107
+ <td>Тариф</td>
108
+ <td>Баланс</td>
109
+ <td>Роль</td>
110
+ <td>Дата регистрации</td>
111
+ <td></td>
112
+ </template>
113
+ <template #row="{ row }">
114
+ <td>{{ row.username }}</td>
115
+ <td>{{ row.plan }}</td>
116
+ <td>{{ row.balance }}</td>
117
+ <td>{{ row.role }}</td>
118
+ <td>{{ row.created_at }}</td>
119
+ <td>
120
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
121
+ @click="deleteUser(row.username)" icon="trash" danger />
122
+ </td>
123
+ </template>
124
+ <template #footer>
125
+ <td>ИТОГО</td>
126
+ <td></td>
127
+ <td>{{ totalBalance }}</td>
128
+ <td></td>
129
+ <td></td>
130
+ <td></td>
131
+ </template>
132
+ </STable>
133
+ </template>
134
+ <script setup>
135
+ import { ref, computed } from 'vue'
136
+
137
+ const users = ref([
138
+ { username: 'Ivanov', role: 'customer', plan: 'Базовый', balance: 3000, created_at: '2025-11-04' },
139
+ { username: 'Stepanov', role: 'customer', plan: 'Базовый', balance: 4500, created_at: '2025-11-05' },
140
+ { username: 'Petrov', role: 'customer', plan: 'Базовый', balance: 1716, created_at: '2025-11-05' },
141
+ ])
142
+
143
+ function deleteUser(username) {
144
+ users.value = users.value.filter(user => user.username !== username)
145
+ }
146
+
147
+ const totalBalance = computed(() => users.value.reduce((acc, user) => acc + user.balance, 0))
148
+ </script>
149
+ ```
150
+
151
+ ## Явные границы по краям ячеек
152
+
153
+ Для отрисовки границ добавляем атрибут <strong>bordered</strong>
154
+
155
+ ```vue
156
+ <template>
157
+ <STable :data="users" bordered>
158
+ <template #header>
159
+ <td>Пользователь</td>
160
+ <td>Тариф</td>
161
+ <td>Баланс</td>
162
+ <td>Роль</td>
163
+ <td>Дата регистрации</td>
164
+ <td></td>
165
+ </template>
166
+ <template #row="{ row }">
167
+ <td>{{ row.username }}</td>
168
+ <td>{{ row.plan }}</td>
169
+ <td>{{ row.balance }}</td>
170
+ <td>{{ row.role }}</td>
171
+ <td>{{ row.created_at }}</td>
172
+ <td>
173
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
174
+ @click="deleteUser(row.username)" icon="trash" danger />
175
+ </td>
176
+ </template>
177
+ <template #footer>
178
+ <td>ИТОГО</td>
179
+ <td></td>
180
+ <td>{{ totalBalance }}</td>
181
+ <td></td>
182
+ <td></td>
183
+ <td></td>
184
+ </template>
185
+ </STable>
186
+ </template>
187
+ <script setup>
188
+ import { ref, computed } from 'vue'
189
+
190
+ const users = ref([
191
+ { username: 'Ivanov', role: 'customer', plan: 'Базовый', balance: 3000, created_at: '2025-11-04' },
192
+ { username: 'Stepanov', role: 'customer', plan: 'Базовый', balance: 4500, created_at: '2025-11-05' },
193
+ { username: 'Petrov', role: 'customer', plan: 'Базовый', balance: 1716, created_at: '2025-11-05' },
194
+ ])
195
+
196
+ function deleteUser(username) {
197
+ users.value = users.value.filter(user => user.username !== username)
198
+ }
199
+
200
+ const totalBalance = computed(() => users.value.reduce((acc, user) => acc + user.balance, 0))
201
+ </script>
202
+ ```
203
+
204
+ ## Выделение четных строк
205
+
206
+ Для выделения четных строк добавляем атрибут <strong>striped</strong>
207
+
208
+ ```vue
209
+ <template>
210
+ <STable :data="users" striped>
211
+ <template #header>
212
+ <td>Пользователь</td>
213
+ <td>Тариф</td>
214
+ <td>Баланс</td>
215
+ <td>Роль</td>
216
+ <td>Дата регистрации</td>
217
+ <td></td>
218
+ </template>
219
+ <template #row="{ row }">
220
+ <td>{{ row.username }}</td>
221
+ <td>{{ row.plan }}</td>
222
+ <td>{{ row.balance }}</td>
223
+ <td>{{ row.role }}</td>
224
+ <td>{{ row.created_at }}</td>
225
+ <td>
226
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
227
+ @click="deleteUser(row.username)" icon="trash" danger />
228
+ </td>
229
+ </template>
230
+ <template #footer>
231
+ <td>ИТОГО</td>
232
+ <td></td>
233
+ <td>{{ totalBalance }}</td>
234
+ <td></td>
235
+ <td></td>
236
+ <td></td>
237
+ </template>
238
+ </STable>
239
+ </template>
240
+ <script setup>
241
+ import { ref, computed } from 'vue'
242
+
243
+ const users = ref([
244
+ { username: 'Ivanov', role: 'customer', plan: 'Базовый', balance: 3000, created_at: '2025-11-04' },
245
+ { username: 'Stepanov', role: 'customer', plan: 'Базовый', balance: 4500, created_at: '2025-11-05' },
246
+ { username: 'Petrov', role: 'customer', plan: 'Базовый', balance: 1716, created_at: '2025-11-05' },
247
+ { username: 'Sidorov', role: 'customer', plan: 'Базовый', balance: 6000, created_at: '2025-11-06' },
248
+ ])
249
+
250
+ function deleteUser(username) {
251
+ users.value = users.value.filter(user => user.username !== username)
252
+ }
253
+
254
+ const totalBalance = computed(() => users.value.reduce((acc, user) => acc + user.balance, 0))
255
+ </script>
256
+ ```
257
+
258
+ ## Фиксированная шапка
259
+
260
+ Чтобы зафиксировать шапку, устанавливаем высоту таблицы в атрибуте `height`.
261
+
262
+ ```vue
263
+ <template>
264
+ <div class="table-container">
265
+ <STable :data="users" height="300px">
266
+ <template #header>
267
+ <td>Пользователь</td>
268
+ <td>Имя</td>
269
+ <td>Фамилия</td>
270
+ <td>Дата рождения</td>
271
+ <td>Рост</td>
272
+ <td>Вес</td>
273
+ <td>Тариф</td>
274
+ <td>Баланс</td>
275
+ <td>Роль</td>
276
+ <td>Дата регистрации</td>
277
+ <td></td>
278
+ </template>
279
+ <template #row="{ row }">
280
+ <td>{{ row.username }}</td>
281
+ <td>{{ row.name }}</td>
282
+ <td>{{ row.secondname }}</td>
283
+ <td>{{ row.birthdate }}</td>
284
+ <td>{{ row.height }}</td>
285
+ <td>{{ row.weight }}</td>
286
+ <td>{{ row.plan }}</td>
287
+ <td>{{ row.balance }}</td>
288
+ <td>{{ row.role }}</td>
289
+ <td>{{ row.created_at }}</td>
290
+ <td>
291
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
292
+ @click="deleteUser(row.username)" icon="trash" danger />
293
+ </td>
294
+ </template>
295
+ <template #footer>
296
+ <td>ИТОГО</td>
297
+ <td></td>
298
+ <td></td>
299
+ <td></td>
300
+ <td></td>
301
+ <td></td>
302
+ <td></td>
303
+ <td>{{ totalBalance }}</td>
304
+ <td></td>
305
+ <td></td>
306
+ <td></td>
307
+ </template>
308
+ </STable>
309
+ </div>
310
+ </template>
311
+ <script setup>
312
+ import { ref, computed } from 'vue'
313
+
314
+ const base = [
315
+ { username: 'Ivanov', name: 'Иван', secondname: 'Иванов', birthdate: '1995.06.06', height: '175', weight: '80', role: 'Пользователь', plan: 'Базовый', balance: 3000, created_at: '2025-11-04' },
316
+ { username: 'Stepanov', name: 'Степан', secondname: 'Степанов', birthdate: '1990.02.14', height: '182', weight: '88', role: 'Админ', plan: '-', balance: 4500, created_at: '2025-11-05' },
317
+ { username: 'Petrov', name: 'Петр', secondname: 'Петров', birthdate: '1988.09.21', height: '178', weight: '76', role: 'Пользователь', plan: 'Премиум', balance: 1716, created_at: '2025-11-05' },
318
+ { username: 'Sidorov', name: 'Сергей', secondname: 'Сидоров', birthdate: '1993.12.01', height: '170', weight: '72', role: 'Пользователь', plan: 'Базовый', balance: 6000, created_at: '2025-11-06' },
319
+ { username: 'Alexeev', name: 'Алексей', secondname: 'Алексеев', birthdate: '1996.03.30', height: '185', weight: '90', role: 'Редактор', plan: '-', balance: 2000, created_at: '2025-11-09' },
320
+ ]
321
+ const users = ref([...base, ...base.map(u => ({ ...u, username: u.username + '2' }))])
322
+
323
+ function deleteUser(username) {
324
+ users.value = users.value.filter(user => user.username !== username)
325
+ }
326
+
327
+ const totalBalance = computed(() => users.value.reduce((acc, user) => acc + user.balance, 0))
328
+ </script>
329
+ ```
330
+
331
+ <SNote icon="lightbulb" attention>
332
+ Для высоты таблицы с фиксированной шапкой удобно использовать <code>80vh</code>, чтобы таблица занимала 80% от высоты экрана.
333
+ </SNote>
334
+
335
+ ## Горизонтальный скролл сверху
336
+
337
+ ```vue
338
+ <template>
339
+ <div class="table-container">
340
+ <STable :data="users" top-scroll>
341
+ <template #header>
342
+ <td>Пользователь</td>
343
+ <td>Имя</td>
344
+ <td>Фамилия</td>
345
+ <td>Дата рождения</td>
346
+ <td>Рост</td>
347
+ <td>Вес</td>
348
+ <td>Тариф</td>
349
+ <td>Баланс</td>
350
+ <td>Роль</td>
351
+ <td>Дата регистрации</td>
352
+ <td></td>
353
+ </template>
354
+ <template #row="{ row }">
355
+ <td>{{ row.username }}</td>
356
+ <td>{{ row.name }}</td>
357
+ <td>{{ row.secondname }}</td>
358
+ <td>{{ row.birthdate }}</td>
359
+ <td>{{ row.height }}</td>
360
+ <td>{{ row.weight }}</td>
361
+ <td>{{ row.plan }}</td>
362
+ <td>{{ row.balance }}</td>
363
+ <td>{{ row.role }}</td>
364
+ <td>{{ row.created_at }}</td>
365
+ <td>
366
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
367
+ @click="deleteUser(row.username)" icon="trash" danger />
368
+ </td>
369
+ </template>
370
+ <template #footer>
371
+ <td>ИТОГО</td>
372
+ <td></td>
373
+ <td></td>
374
+ <td></td>
375
+ <td></td>
376
+ <td></td>
377
+ <td></td>
378
+ <td>{{ totalBalance }}</td>
379
+ <td></td>
380
+ <td></td>
381
+ <td></td>
382
+ </template>
383
+ </STable>
384
+ </div>
385
+ </template>
386
+ <script setup>
387
+ import { ref, computed } from 'vue'
388
+
389
+ const users = ref([
390
+ { username: 'Ivanov', name: 'Иван', secondname: 'Иванов', birthdate: '1995.06.06', height: '175', weight: '80', role: 'Пользователь', plan: 'Базовый', balance: 3000, created_at: '2025-11-04' },
391
+ { username: 'Stepanov', name: 'Степан', secondname: 'Степанов', birthdate: '1990.02.14', height: '182', weight: '88', role: 'Админ', plan: '-', balance: 4500, created_at: '2025-11-05' },
392
+ { username: 'Petrov', name: 'Петр', secondname: 'Петров', birthdate: '1988.09.21', height: '178', weight: '76', role: 'Пользователь', plan: 'Премиум', balance: 1716, created_at: '2025-11-05' },
393
+ { username: 'Sidorov', name: 'Сергей', secondname: 'Сидоров', birthdate: '1993.12.01', height: '170', weight: '72', role: 'Пользователь', plan: 'Базовый', balance: 6000, created_at: '2025-11-06' },
394
+ { username: 'Alexeev', name: 'Алексей', secondname: 'Алексеев', birthdate: '1996.03.30', height: '185', weight: '90', role: 'Редактор', plan: '-', balance: 2000, created_at: '2025-11-09' },
395
+ ])
396
+
397
+ function deleteUser(username) {
398
+ users.value = users.value.filter(user => user.username !== username)
399
+ }
400
+
401
+ const totalBalance = computed(() => users.value.reduce((acc, user) => acc + user.balance, 0))
402
+ </script>
403
+ ```
404
+
405
+ ## Кастомный контент внутри tbody
406
+
407
+ Если нужно задать явные строки в tbody вместо перебора #row, то используем дефолтный слот (в нем для отдельных строк нужно явно указать `<tr>`).
408
+
409
+ ```vue
410
+ <template>
411
+ <STable>
412
+ <template #header>
413
+ <td>Пользователь</td>
414
+ <td>Тариф</td>
415
+ <td>Баланс</td>
416
+ <td>Роль</td>
417
+ <td>Дата регистрации</td>
418
+ <td></td>
419
+ </template>
420
+ <tr v-for="user in users" :key="user.username">
421
+ <td>{{ user.username }}</td>
422
+ <td>{{ user.plan }}</td>
423
+ <td>{{ user.balance }}</td>
424
+ <td>{{ user.role }}</td>
425
+ <td>{{ user.created_at }}</td>
426
+ <td>
427
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${user.username}»?`"
428
+ @click="deleteUser(user.username)" icon="trash" danger />
429
+ </td>
430
+ </tr>
431
+ <template #footer>
432
+ <td>ИТОГО</td>
433
+ <td></td>
434
+ <td>{{ totalBalance }}</td>
435
+ <td></td>
436
+ <td></td>
437
+ <td></td>
438
+ </template>
439
+ </STable>
440
+ </template>
441
+ <script setup>
442
+ import { ref, computed } from 'vue'
443
+
444
+ const users = ref([
445
+ { username: 'Ivanov', role: 'customer', plan: 'Базовый', balance: 3000, created_at: '2025-11-04' },
446
+ { username: 'Stepanov', role: 'customer', plan: 'Базовый', balance: 4500, created_at: '2025-11-05' },
447
+ { username: 'Petrov', role: 'customer', plan: 'Базовый', balance: 1716, created_at: '2025-11-05' },
448
+ ])
449
+
450
+ function deleteUser(username) {
451
+ users.value = users.value.filter(user => user.username !== username)
452
+ }
453
+
454
+ const totalBalance = computed(() => users.value.reduce((acc, user) => acc + user.balance, 0))
455
+ </script>
456
+ ```
457
+
458
+ ## Несколько строк в header / footer
459
+
460
+ Если это нужно, просто используем отдельные слоты headers / footers (добавляется в конце "s"), в которые уже добавляем `<tr>`.
461
+
462
+ ```vue
463
+ <template>
464
+ <STable>
465
+ <template #headers>
466
+ <tr><td>...</td></tr>
467
+ <tr><td>...</td></tr>
468
+ </template>
469
+ ...
470
+ <template #footers>
471
+ <tr><td>...</td></tr>
472
+ <tr><td>...</td></tr>
473
+ </template>
474
+ </STable>
475
+ </template>
476
+ ```
477
+
478
+ ## Сообщение о том, что нет данных
479
+
480
+ Когда задаем data и перебираем его через `<template #row>`, то часто (например, когда есть фильтры страницы) бывает нужно показать состояние «Ничего не найдено», когда в data ноль строк. По умолчанию в таком случае выводится сообщение «Ничего не найдено», которое можно заменить на кастомное через пропс/слот nodata:
481
+
482
+ ```vue
483
+ <template>
484
+ <STable :data="users" nodata="Пользователи не найдены">
485
+ <template #header>
486
+ <td>Пользователь</td>
487
+ <td>Тариф</td>
488
+ <td>Баланс</td>
489
+ <td>Роль</td>
490
+ <td>Дата регистрации</td>
491
+ <td></td>
492
+ </template>
493
+ <template #row="{ row }">
494
+ <td>{{ row.username }}</td>
495
+ <td>{{ row.plan }}</td>
496
+ <td>{{ row.balance }}</td>
497
+ <td>{{ row.role }}</td>
498
+ <td>{{ row.created_at }}</td>
499
+ <td>
500
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
501
+ @click="deleteUser(row.username)" icon="trash" danger />
502
+ </td>
503
+ </template>
504
+ <template #footer>
505
+ <td>ИТОГО</td>
506
+ <td></td>
507
+ <td></td>
508
+ <td></td>
509
+ <td></td>
510
+ <td></td>
511
+ </template>
512
+ </STable>
513
+ </template>
514
+ <script setup>
515
+ import { ref } from 'vue'
516
+
517
+ const users = ref([])
518
+
519
+ function deleteUser(username) {
520
+ users.value = users.value.filter(user => user.username !== username)
521
+ }
522
+ </script>
523
+ ```
524
+
525
+ или
526
+
527
+ ```vue
528
+ <template>
529
+ <STable :data="users">
530
+ <template #nodata>
531
+ <SNote>По заданным критериям поиска ничего не нашлось</SNote>
532
+ </template>
533
+ <template #header>
534
+ <td>Пользователь</td>
535
+ <td>Тариф</td>
536
+ <td>Баланс</td>
537
+ <td>Роль</td>
538
+ <td>Дата регистрации</td>
539
+ <td></td>
540
+ </template>
541
+ <template #row="{ row }">
542
+ <td>{{ row.username }}</td>
543
+ <td>{{ row.plan }}</td>
544
+ <td>{{ row.balance }}</td>
545
+ <td>{{ row.role }}</td>
546
+ <td>{{ row.created_at }}</td>
547
+ <td>
548
+ <SActionIcon title="Удалить" :confirm="`Вы действительно хотите удалить пользователя «${row.username}»?`"
549
+ @click="deleteUser(row.username)" icon="trash" danger />
550
+ </td>
551
+ </template>
552
+ <template #footer>
553
+ <td>ИТОГО</td>
554
+ <td></td>
555
+ <td></td>
556
+ <td></td>
557
+ <td></td>
558
+ <td></td>
559
+ </template>
560
+ </STable>
561
+ </template>
562
+ <script setup>
563
+ import { ref } from 'vue'
564
+
565
+ const users = ref([])
566
+
567
+ function deleteUser(username) {
568
+ users.value = users.value.filter(user => user.username !== username)
569
+ }
570
+ </script>
571
+ ```
572
+
573
+ ## Интерфейс компонента
574
+
575
+ ### Свойства (Props)
576
+
577
+ | Название | Тип | По умолчанию | Описание |
578
+ |----------|-----|--------------|----------|
579
+ | data | `T[] \| Record<string \| number, T>` | `undefined` | Массив или объект данных для итерации (используется вместе со слотом `#row`). |
580
+ | hoverable | boolean | `false` | Подсветка строки при наведении курсора. |
581
+ | striped | boolean | `false` | Чередование цвета фона строк (выделение четных строк). |
582
+ | bordered | boolean | `false` | Отрисовка границ (бордеров) у всех ячеек. |
583
+ | nodata | string | `'Ничего не найдено'` | Текст, выводимый, когда `data` пустое. |
584
+ | fixedHeader | boolean | `false` | Фиксированная шапка (принудительно), если не задана `height`. |
585
+ | height | string | `undefined` | Фиксированная высота таблицы (например, `300px` или `80vh`). Автоматически включает `fixedHeader`. |
586
+ | topScroll | boolean | `false` | Переносит горизонтальный скролл наверх таблицы. |
587
+
588
+ ### Слоты (Slots)
589
+
590
+ | Название | Описание |
591
+ |----------|-------------|
592
+ | header | Содержимое `<tr/>` внутри `<thead>`. Подходит для большинства таблиц (без rowspan/colspan в шапке). |
593
+ | headers | Полный контроль над `<thead>` (вместо `header`). Нужно явно писать теги `<tr>`. |
594
+ | row | Слот с областью видимости `{ row, index }`. Отрисовывается для каждого элемента из `data`. Внутри нужно писать только `<td>`. |
595
+ | default | Переопределяет содержимое `<tbody>`. Используется, если не передан `data`. Нужно явно писать теги `<tr>`. |
596
+ | footer | Содержимое `<tr/>` внутри `<tfoot>`. |
597
+ | footers | Полный контроль над `<tfoot>` (содержит теги `<tr>`). |
598
+ | nodata | Кастомный HTML для пустого состояния, заменяет пропс `nodata`. |
599
+
600
+ <style lang="scss">
601
+ .vp-doc table {
602
+ overflow-x: visible;
603
+ }
604
+
605
+ .vp-doc td {
606
+ border: none;
607
+ }
608
+
609
+ .vp-doc table h3 {
610
+ margin: 0;
611
+ }
612
+
613
+ .s-demo-preview .s-table,
614
+ .s-demo-preview .s-table table {
615
+ margin: 0 !important;
616
+ }
617
+ .s-demo-preview .s-table tr {
618
+ border-top: 0 !important;
619
+ background-color: transparent;
620
+ }
621
+ .s-demo-preview .s-table td {
622
+ border-bottom: 1px solid var(--s-border) !important;
623
+ }
624
+
625
+ .s-table tfoot {
626
+ color: var(--s-text) !important;
627
+ }
628
+
629
+ .table-container .s-table > table {
630
+ width: 120%;
631
+ }
632
+
633
+ .dark .s-table.striped {
634
+ tbody tr:nth-of-type(even) {
635
+ background-color: #282828 !important;
636
+ }
637
+ }
638
+ </style>