@studio-west/component-sw 0.11.9 → 0.11.11

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 (49) hide show
  1. package/README.md +5 -5
  2. package/dist/SwButton-yS_tKW9w.js +4 -0
  3. package/dist/{SwButton-jKDKwFV9.js → SwButton.vue_vue_type_script_setup_true_lang-aODPwFa6.js} +11 -11
  4. package/dist/{SwDatePicker-CJjKSM8k.js → SwDatePicker-CpmdOhuc.js} +1 -1
  5. package/dist/{SwDropdownItem-CtlMVgsX.js → SwDropdownItem-BE6ZRWT1.js} +1 -1
  6. package/dist/{SwGide-DbSSyZ-y.js → SwGide-_a5-3g_f.js} +2 -2
  7. package/dist/SwInput-CbNd7Vin.js +90 -0
  8. package/dist/{SwMessage-CovKkpf6.js → SwMessage-DdUbYQet.js} +6 -6
  9. package/dist/SwSection-CQe2kE0O.js +34 -0
  10. package/dist/SwSelect-BxbCfof-.js +1883 -0
  11. package/dist/{SwSlider-YncjYKPw.js → SwSlider-jWTzzPZg.js} +1 -1
  12. package/dist/SwSwitch-DeMdyD0-.js +47 -0
  13. package/dist/index-C3iiqwEz.js +188 -0
  14. package/dist/index.cjs +6 -1
  15. package/dist/index.js +1 -1
  16. package/package.json +6 -2
  17. package/src/Alert.ts +65 -0
  18. package/src/components/SwAlert.vue +70 -0
  19. package/src/components/SwButton.vue +50 -0
  20. package/src/components/SwButtonGroup.vue +67 -0
  21. package/src/components/SwCollapse.vue +36 -0
  22. package/src/components/SwDatePicker.vue +375 -0
  23. package/src/components/SwDropdown.vue +202 -0
  24. package/src/components/SwDropdownItem.vue +26 -0
  25. package/src/components/SwDropdownNew.vue +175 -0
  26. package/src/components/SwFormItem.vue +21 -0
  27. package/src/components/SwGide.vue +128 -0
  28. package/src/components/SwIcon.vue +16 -0
  29. package/src/components/SwInput.vue +100 -0
  30. package/src/components/SwMessage.vue +53 -0
  31. package/src/components/SwSection.vue +17 -0
  32. package/src/components/SwSelect.vue +151 -0
  33. package/src/components/SwSkeleton.vue +13 -0
  34. package/src/components/SwSkeletonItem.vue +27 -0
  35. package/src/components/SwSlider.vue +281 -0
  36. package/src/components/SwSwitch.vue +51 -0
  37. package/src/components/SwTable.vue +239 -0
  38. package/src/components/SwTableColumn.vue +25 -0
  39. package/src/components/SwTabs.vue +41 -0
  40. package/src/components/SwTabsPane.vue +44 -0
  41. package/src/index.ts +43 -0
  42. package/src/utils/index.ts +149 -0
  43. package/types/components.d.ts +72 -0
  44. package/types/index.d.ts +82 -70
  45. package/dist/SwInput-DCV1rrWa.js +0 -89
  46. package/dist/SwSection-D8ooQ21I.js +0 -37
  47. package/dist/SwSelect-C2RKinez.js +0 -72
  48. package/dist/SwSwitch-6rl1IT4p.js +0 -47
  49. package/dist/index-B5koqczP.js +0 -190
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <slot v-if="props.visual"></slot>
3
+ <div v-else class="sw-skeleton" :class="props.class"><slot name="skeleton" ><sw-skeleton-item animate size="small"/></slot></div>
4
+
5
+ </template>
6
+
7
+ <script setup>
8
+ import SwSkeletonItem from "@/components/SwSkeletonItem.vue"
9
+ const props = defineProps({
10
+ class: {type:String, default: ''},
11
+ visual: {type:Boolean, default: false}
12
+ })
13
+ </script>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <div :class="c" :style="s"><template v-if="props.animate"><div /></template></div>
3
+ </template>
4
+
5
+ <script setup>
6
+ import {computed} from "vue";
7
+
8
+ const props = defineProps({
9
+ width: {type: String, default: '100'},
10
+ class: {type:String, default: ''},
11
+ animate: {type:Boolean, default: false},
12
+ size: {type:String, default: ''}
13
+ })
14
+ const c = computed(() =>{
15
+ let s = ['sw-skeleton-item']
16
+ if(props.size.length > 0) s.push('sw-' + props.size)
17
+ if(props.animate) s.push('sw-animate')
18
+ if(props.class.length > 0) s.push(props.class)
19
+ return s
20
+ }
21
+ )
22
+ const s = computed(() =>{
23
+ return {
24
+ width: props.width + '%'
25
+ }
26
+ })
27
+ </script>
@@ -0,0 +1,281 @@
1
+ <template>
2
+ <div :class="style">
3
+ <div class="track"
4
+ @click="onTrackClick"
5
+ @mousedown.prevent="(e) => onStartDrag(e, 0)"
6
+ @touchstart.prevent="(e) => onStartDrag(e.touches[0], 0)">
7
+ <div class="fill" :style="fillStyle"></div>
8
+ </div>
9
+ <!-- Multiple thumbs -->
10
+ <div v-for="(value, index) in values"
11
+ class="thumb"
12
+ :key="index"
13
+ :style="thumbStyles[index]"
14
+ @mouseenter="showTooltip(index)"
15
+ @mouseleave="hideTooltip(index)"
16
+ @focus="showTooltip(index)"
17
+ @blur="hideTooltip(index)"
18
+ @mousedown.prevent="(e) => onStartDrag(e, index)"
19
+ @touchstart.prevent="(e) => onStartDrag(e.touches[0], index)"
20
+ >
21
+ <slot :index="index" :value="value"></slot>
22
+ <div
23
+ v-if="tooltipVisible[index] && showValue"
24
+ class="tooltip"
25
+ :class="{ 'tooltip-vertical': props.vertical }"
26
+ :style="tooltipStyle(index)"
27
+ >
28
+ {{ value }}
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup>
35
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
36
+ import { calculateTooltipPosition } from '@/utils/index.js'
37
+
38
+ const model = defineModel({type: [Number, Array], required: true})
39
+ const props = defineProps({
40
+ class: { type: String, default: ''},
41
+ size: {type:String, default: ''},
42
+ min: {type: Number, default: 0},
43
+ max: {type: Number, default: 100},
44
+ step: {type: Number, default: 1},
45
+ vertical: {type: Boolean, default: false},
46
+ showValue: {type: Boolean, default: false}
47
+ })
48
+
49
+ const tooltipVisible = ref([])
50
+ const tooltipPositions = ref([])
51
+
52
+ const style = computed(() =>{
53
+ let s = ['sw-slider']
54
+ if(props.size.length > 0) s.push('sw-' + props.size)
55
+ if(props.class.length > 0) s.push(props.class)
56
+ if(props.vertical) s.push('vertical')
57
+ return s
58
+ }
59
+ )
60
+
61
+ const showTooltip = (index) => {
62
+ const newVisibility = [...tooltipVisible.value]
63
+ newVisibility[index] = true
64
+ tooltipVisible.value = newVisibility
65
+ }
66
+
67
+ const hideTooltip = (index) => {
68
+ const newVisibility = [...tooltipVisible.value]
69
+ newVisibility[index] = false
70
+ tooltipVisible.value = newVisibility
71
+ }
72
+
73
+ const tooltipStyle = (index) => {
74
+ const sliderElement = document.querySelector('.sw-slider')
75
+ if (sliderElement) {
76
+ return calculateTooltipPosition(sliderElement, props.vertical)
77
+ }
78
+ // Fallback positions
79
+ if (props.vertical) {
80
+ return { right: '100%', marginRight: '10px' }
81
+ } else {
82
+ return { bottom: '100%', marginBottom: '10px' }
83
+ }
84
+ }
85
+
86
+ // Determine ifwe're dealing with an array (multiple thumbs)
87
+ const isArray = computed(() => Array.isArray(model.value))
88
+
89
+ // Get values as array for uniform treatment
90
+ const values = computed({
91
+ get() {
92
+ return isArray.value ? [...model.value] : [model.value]
93
+ },
94
+ set(newValues) {
95
+ if (isArray.value) {
96
+ // Sort values for display purposes
97
+ model.value = [...newValues].sort((a, b) => a - b)
98
+ } else {
99
+ model.value = newValues[0]
100
+ }
101
+ }
102
+ })
103
+
104
+ // Calculate percentages for each thumb
105
+ const percentages = computed(() => {
106
+ const range= props.max - props.min
107
+ return values.value.map(value => {
108
+ return range === 0 ? 0 : ((value - props.min) / range) * 100
109
+ })
110
+ })
111
+
112
+ // Style for the fill between first and last thumbs
113
+ const fillStyle = computed(() => {
114
+ if (props.vertical) {
115
+ if(isArray.value) {
116
+ const firstPercent = percentages.value[0] || 0
117
+ const lastPercent = percentages.value[percentages.value.length - 1] || 0
118
+ const minPercent = Math.min(firstPercent, lastPercent)
119
+ const maxPercent = Math.max(firstPercent, lastPercent)
120
+ return {
121
+ height: `${maxPercent - minPercent}%`,
122
+ top: `${minPercent}%`,
123
+ left: '0',
124
+ width: '100%'
125
+ }
126
+ } else {
127
+ // For single value, start from 0
128
+ const percent = percentages.value[0] || 0
129
+ return {
130
+ height: `${percent}%`,
131
+ top: '0',
132
+ left: '0',
133
+ width: '100%'
134
+ }
135
+ }
136
+ } else {
137
+ if (isArray.value) {
138
+ const firstPercent = percentages.value[0] || 0
139
+ const lastPercent= percentages.value[percentages.value.length - 1] || 0
140
+ const minPercent = Math.min(firstPercent, lastPercent)
141
+ const maxPercent = Math.max(firstPercent, lastPercent)
142
+
143
+ return {
144
+ width: `${maxPercent - minPercent}%`,
145
+ left: `${minPercent}%`,
146
+ top: '0',
147
+ height: '100%'
148
+ }
149
+ } else {
150
+ // For single value, start from 0
151
+ const percent = percentages.value[0] || 0
152
+ return {
153
+ width: `${percent}%`,
154
+ left: '0',
155
+ top: '0',
156
+ height: '100%'
157
+ }
158
+ }
159
+ }
160
+ })
161
+
162
+ // Stylesfor each thumb
163
+ const thumbStyles = computed(() => {
164
+ return percentages.value.map((percent, index) => {
165
+ return props.vertical
166
+ ? { top: `${percent}%`, zIndex: values.value.length - index }
167
+ : { left: `${percent}%`, zIndex: values.value.length - index }
168
+ })
169
+ })
170
+
171
+ // === Track click handler ===
172
+ function onTrackClick(e) {
173
+ // Only for single thumb sliders
174
+ if (isArray.value) return
175
+
176
+ const slider = e.currentTarget.closest('.sw-slider')
177
+ if (!slider) return
178
+
179
+ const rect = slider.getBoundingClientRect()
180
+ const ratio = props.vertical
181
+ ? (e.clientY - rect.top) / rect.height
182
+ : (e.clientX - rect.left) / rect.width
183
+
184
+ const clampedRatio = Math.max(0, Math.min(1, ratio))
185
+ const range = props.max - props.min
186
+ let value = props.min + clampedRatio * range
187
+
188
+ // Apply step
189
+ const steps = Math.round((value - props.min) / props.step)
190
+ value = props.min + steps * props.step
191
+ value = Math.max(props.min, Math.min(props.max, value))
192
+
193
+ // Update the value
194
+ values.value = [value]
195
+
196
+ // Update tooltip position
197
+ setTimeout(() => {
198
+ const newPositions = [...tooltipPositions.value]
199
+ newPositions[0] = tooltipStyle(0)
200
+ tooltipPositions.value = newPositions
201
+ }, 0)
202
+ }
203
+
204
+ // === Dragging ===
205
+ const isDragging = ref(false)
206
+ const draggingIndex = ref(null)
207
+ const sliderRect = ref(null)
208
+
209
+ function onStartDrag(e, index) {
210
+ // For single thumb sliders, also handle track clicks
211
+ // if (!isArray.value && index !== 0) return
212
+
213
+ const slider = e.target.closest('.sw-slider')
214
+ if (!slider) return
215
+ sliderRect.value = slider.getBoundingClientRect()
216
+ draggingIndex.value = index
217
+ isDragging.value = true
218
+ updateValueFromClient(e.clientX || e.touches?.[0]?.clientX, e.clientY|| e.touches?.[0]?.clientY)
219
+ }
220
+
221
+ function onDrag(e) {
222
+ if (!isDragging.value) return
223
+ e.preventDefault?.()
224
+ const clientX = e.clientX || e.touches?.[0]?.clientX
225
+ const clientY = e.clientY ||e.touches?.[0]?.clientY
226
+ updateValueFromClient(clientX, clientY)
227
+ }
228
+
229
+ function stopDragging() {
230
+ isDragging.value = false
231
+ draggingIndex.value = null
232
+ }
233
+
234
+ function updateValueFromClient(clientX, clientY) {
235
+ if (!sliderRect.value || draggingIndex.value === null) return
236
+
237
+ const rect = sliderRect.value
238
+ const ratio = props.vertical
239
+ ? (clientY - rect.top) / rect.height
240
+ : (clientX - rect.left) / rect.width
241
+
242
+ const clampedRatio = Math.max(0, Math.min(1, ratio))
243
+ const range= props.max - props.min
244
+ let value = props.min + clampedRatio * range
245
+
246
+ // Apply step
247
+ const steps = Math.round((value - props.min) / props.step)
248
+ value = props.min + steps * props.step
249
+ value = Math.max(props.min, Math.min(props.max,value))
250
+
251
+ // Update the specific thumb value
252
+ const newValues = [...values.value]
253
+ newValues[draggingIndex.value] = value
254
+ values.value = newValues
255
+
256
+ // Update tooltip position
257
+ setTimeout(() => {
258
+ const newPositions = [...tooltipPositions.value]
259
+ newPositions[draggingIndex.value] = tooltipStyle(draggingIndex.value)
260
+ tooltipPositions.value = newPositions
261
+ }, 0)
262
+ }
263
+
264
+ // Global listeners
265
+ onMounted(() => {
266
+ window.addEventListener('mousemove', onDrag)
267
+ window.addEventListener('mouseup', stopDragging)
268
+ window.addEventListener('touchmove', onDrag, { passive: false })
269
+ window.addEventListener('touchend', stopDragging)
270
+
271
+ // Initialize tooltip visibility array
272
+ tooltipVisible.value = new Array(values.value.length).fill(false)
273
+ })
274
+
275
+ onUnmounted(() => {
276
+ window.removeEventListener('mousemove', onDrag)
277
+ window.removeEventListener('mouseup', stopDragging)
278
+ window.removeEventListener('touchmove', onDrag)
279
+ window.removeEventListener('touchend', stopDragging)
280
+ })
281
+ </script>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <div :class="style">
3
+ <!--input type="checkbox" :name="props.name" class="sw-control" :id="props.id" v-model="model"-->
4
+ <input
5
+ type="checkbox"
6
+ :name="props.name"
7
+ class="sw-control"
8
+ :id="props.id"
9
+ :checked="model"
10
+ @change="handleChange"
11
+ >
12
+ <label :for="props.id" class="sw-label" :data-onlabel="props.on" :data-offlabel="props.off" />
13
+ <slot></slot>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { computed } from "vue"
19
+
20
+ // Define prop types
21
+ interface Props {
22
+ id?: string
23
+ class?: string
24
+ name?: string
25
+ on?: string
26
+ off?: string
27
+ size?: string
28
+ checkbox?: boolean
29
+ }
30
+
31
+ const model = defineModel<boolean>()
32
+ const props = withDefaults(defineProps<Props>(), {
33
+ id: 'checkbox',
34
+ class: '',
35
+ name: '',
36
+ on: '',
37
+ off: '',
38
+ size: '',
39
+ checkbox: false
40
+ })
41
+ const style = computed(() =>{
42
+ let s = ['sw-switch']
43
+ if(props.size.length > 0) s.push('sw-' + props.size)
44
+ if(props.checkbox) s.push('sw-checkbox')
45
+ if(props.class.length > 0) s.push(props.class)
46
+ return s
47
+ })
48
+ const handleChange = (event) => {
49
+ model.value = event.target.checked
50
+ }
51
+ </script>
@@ -0,0 +1,239 @@
1
+ <template>
2
+ <table class="sw-table">
3
+ <thead>
4
+ <tr v-for="(row, rowIndex) in headerRows" :key="rowIndex">
5
+ <th
6
+ v-for="cell in row"
7
+ :key="cell.key"
8
+ :colspan="cell.colspan"
9
+ :rowspan="cell.rowspan"
10
+ :style="{ minWidth: cell.width, minHeight: cell.height }"
11
+ >
12
+ {{ cell.label }}
13
+ </th>
14
+ </tr>
15
+ </thead>
16
+ <!--tbody>
17
+ <tr v-for="(row, index) in data" :key="index">
18
+ <td v-for="column in flatColumns" :key="column.prop">
19
+ <slot
20
+ :name="column.prop"
21
+ :row="row"
22
+ :$index="index"
23
+ >
24
+ {{ row[column.prop] }}
25
+ </slot>
26
+ </td>
27
+ </tr>
28
+ </tbody-->
29
+ <tbody>
30
+ <tr v-for="(row, rowIndex) in processedTableData" :key="rowIndex">
31
+ <td
32
+ v-for="cell in row"
33
+ :key="cell.key"
34
+ :colspan="cell.colspan"
35
+ :rowspan="cell.rowspan"
36
+ :style="cell.style"
37
+ >
38
+ <slot
39
+ :name="cell.prop"
40
+ :row="cell.originalRow"
41
+ :$index="cell.rowIndex"
42
+ >
43
+ {{ cell.label }}
44
+ </slot>
45
+ </td>
46
+ </tr>
47
+ </tbody>
48
+ </table>
49
+ </template>
50
+
51
+ <script setup>
52
+ import { ref, useSlots, onMounted, computed } from 'vue'
53
+ import SwTableColumn from './SwTableColumn.vue'
54
+
55
+ const props = defineProps({
56
+ data: {
57
+ type: Array,
58
+ required: true
59
+ }
60
+ })
61
+
62
+ const slots = useSlots()
63
+ const columnTree = ref([])
64
+ // Функция для построения дерева колонок
65
+ function buildColumnTree(vnodes) {
66
+ const result = []
67
+
68
+ for (const vnode of vnodes) {
69
+ if (vnode.type === SwTableColumn) {
70
+ const column = {
71
+ ...vnode.props,
72
+ children: []
73
+ }
74
+ const children = vnode.children?.default?.()
75
+ if (children && children.length > 0) {
76
+ column.children = buildColumnTree(children)
77
+ }
78
+ result.push(column)
79
+ }
80
+ }
81
+ return result
82
+ }
83
+
84
+ // Получение максимальной глубины дерева
85
+ function getMaxDepth(columns) {
86
+ if (!columns || columns.length === 0) return 1
87
+ let maxDepth = 1
88
+ for (const column of columns) {
89
+ if (column.children && column.children.length > 0) {
90
+ const childDepth = getMaxDepth(column.children)
91
+ maxDepth = Math.max(maxDepth, 1 + childDepth)
92
+ }
93
+ }
94
+ return maxDepth
95
+ }
96
+
97
+ // Преобразование дерева в строки для thead
98
+ function buildHeaderRows(columns, depth) {
99
+ const rows = Array.from({ length: depth }, () => [])
100
+
101
+ function processColumns(cols, rowIndex, parentKey = '') {
102
+ for (let i = 0; i < cols.length; i++) {
103
+ const col = cols[i]
104
+ const key = parentKey ? `${parentKey}-${col.prop || i}` : (col.prop || i)
105
+
106
+ if (col.children && col.children.length > 0) {
107
+ // Это родительская колонка
108
+ const leafCount = countLeafColumns(col.children)
109
+ rows[rowIndex].push({
110
+ key,
111
+ label: col.label,
112
+ colspan: leafCount,
113
+ rowspan: 1,
114
+ width: col.width,
115
+ height: col.height
116
+ })
117
+ processColumns(col.children, rowIndex + 1, key)
118
+ } else {
119
+ // Это конечная колонка
120
+ rows[rowIndex].push({
121
+ key: col.prop,
122
+ label: col.label,
123
+ colspan: 1,
124
+ rowspan: depth - rowIndex,
125
+ width: col.width,
126
+ height: col.height,
127
+ prop: col.prop
128
+ })
129
+ }
130
+ }
131
+ }
132
+ processColumns(columns, 0)
133
+ return rows
134
+ }
135
+
136
+ // Подсчет количества листовых колонок
137
+ function countLeafColumns(columns) {
138
+ let count = 0
139
+
140
+ function traverse(cols) {
141
+ for (const col of cols) {
142
+ if (col.children && col.children.length > 0) traverse(col.children)
143
+ else count++
144
+ }
145
+ }
146
+ traverse(columns)
147
+ return count
148
+ }
149
+
150
+ // Извлечение плоского списка конечных колонок
151
+ function extractLeafColumns(columns) {
152
+ const result = []
153
+
154
+ function traverse(cols) {
155
+ for (const col of cols) {
156
+ if (col.children && col.children.length > 0) traverse(col.children)
157
+ else result.push(col)
158
+ }
159
+ }
160
+ traverse(columns)
161
+ return result
162
+ }
163
+
164
+ // Обработка данных для tbody с поддержкой colspan
165
+ function processBodyData(data, columns) {
166
+ const processedRows = []
167
+ const cellMap = new Map() // Для отслеживания объединенных ячеек
168
+ data.forEach((row, rowIndex) => {
169
+ const processedRow = []
170
+
171
+ columns.forEach((column, colIndex) => {
172
+ const key = `${rowIndex}-${column.prop}`
173
+
174
+ // Проверяем, не занята ли эта ячейка из-за rowspan/colspan
175
+ if (cellMap.has(key)) {
176
+ return
177
+ }
178
+
179
+ const cellData = row[column.prop]
180
+ let cellValue = cellData
181
+ let colspan = 1
182
+ let rowspan = 1
183
+ let style = {}
184
+
185
+ // Проверяем, есть ли у значения свойства rowspan/colspan
186
+ if (cellData && typeof cellData === 'object' && cellData !== null) {
187
+ cellValue = cellData.label || cellData.value || ''
188
+ colspan = cellData.colspan || 1
189
+ rowspan = cellData.rowspan || 1
190
+ style = cellData.style || {}
191
+ }
192
+
193
+ const cell = {
194
+ key,
195
+ label: cellValue,
196
+ prop: column.prop,
197
+ colspan,
198
+ rowspan,
199
+ style,
200
+ originalRow: row,
201
+ rowIndex
202
+ }
203
+
204
+ processedRow.push(cell)
205
+
206
+
207
+ // Помечаем объединенные ячейки
208
+ for (let r = 0; r < rowspan; r++) {
209
+ for (let c = 0; c < colspan; c++) {
210
+ if (r === 0 && c === 0) continue // Основная ячейка
211
+ const cellKey = `${rowIndex + r}-${columns[colIndex + c]?.prop}`
212
+ if (cellKey) {
213
+ cellMap.set(cellKey, true)
214
+ }
215
+ }
216
+ }
217
+ })
218
+
219
+ // Добавляем строку только если в ней есть ячейки
220
+ if (processedRow.length > 0) {
221
+ processedRows.push(processedRow)
222
+ }
223
+ })
224
+
225
+ return processedRows
226
+ }
227
+
228
+ // Сбор колонок из слотов
229
+ onMounted(() => {
230
+ const columnVNodes = slots.default?.() || []
231
+ // columns.value = extractLeafColumns(columnVNodes)
232
+ columnTree.value = buildColumnTree(columnVNodes)
233
+ })
234
+ // Вычисляемые свойства
235
+ const maxDepth = computed(() => getMaxDepth(columnTree.value))
236
+ const headerRows = computed(() => buildHeaderRows(columnTree.value, maxDepth.value))
237
+ const flatColumns = computed(() => extractLeafColumns(columnTree.value))
238
+ const processedTableData = computed(() => processBodyData(props.data, flatColumns.value))
239
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div style="display: none;"></div>
3
+ </template>
4
+
5
+ <script setup>
6
+
7
+ defineProps({
8
+ prop: {
9
+ type: String,
10
+ required: true
11
+ },
12
+ label: {
13
+ type: String,
14
+ default: ''
15
+ },
16
+ width: {
17
+ type: String,
18
+ default: 'auto'
19
+ },
20
+ height: {
21
+ type: String,
22
+ default: 'auto'
23
+ }
24
+ })
25
+ </script>
@@ -0,0 +1,41 @@
1
+ <script setup>
2
+ import {ref, provide} from 'vue'
3
+ const activeTab = ref(0);
4
+ const tabs = ref([]);
5
+
6
+ const props = defineProps({
7
+ class: {type:String, default: ''}
8
+ })
9
+ const model = defineModel()
10
+ const emit = defineEmits(['tab-click'])
11
+
12
+ function registerTab(tab) {
13
+ tabs.value.push(tab)
14
+ }
15
+
16
+ function unregisterTab(tab) {
17
+ tabs.value = tabs.value.filter(t => t !== tab)
18
+ }
19
+
20
+ provide('registerTab', registerTab)
21
+ provide('unregisterTab', unregisterTab)
22
+ provide('activeTab', model)
23
+ provide('tabs', tabs)
24
+ </script>
25
+
26
+ <template>
27
+ <div :class="'sw-tabs ' + props.class">
28
+ <header v-if="tabs.length !== 0">
29
+ <span
30
+ v-for="(tab, index) in tabs"
31
+ :key="index"
32
+ :class="[{ active: model === index }]"
33
+ @click="model = index; emit('tab-click', index)"
34
+ >
35
+ {{ tab.label || tab.title }}
36
+ </span>
37
+ </header>
38
+ <slot />
39
+ </div>
40
+ </template>
41
+