@eturnity/eturnity_reusable_components 9.31.4 → 9.34.0
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/package.json +1 -1
- package/src/components/tabsHeader/index.vue +191 -84
package/package.json
CHANGED
|
@@ -4,83 +4,103 @@
|
|
|
4
4
|
data-test-id="tabs-scroll-area"
|
|
5
5
|
:scroll-overflow="scrollOverflow"
|
|
6
6
|
>
|
|
7
|
-
<
|
|
7
|
+
<Draggable
|
|
8
|
+
class="rc-tabs-header__tabs"
|
|
9
|
+
:class="{ 'rc-tabs-header__tabs--scroll': scrollOverflow }"
|
|
8
10
|
data-test-id="tabs-container"
|
|
9
|
-
:
|
|
11
|
+
:model-value="localTabs"
|
|
12
|
+
item-key="id"
|
|
13
|
+
:disabled="!dragEnabled"
|
|
14
|
+
:animation="150"
|
|
15
|
+
direction="horizontal"
|
|
16
|
+
:force-fallback="true"
|
|
17
|
+
:fallback-tolerance="5"
|
|
18
|
+
:scroll="true"
|
|
19
|
+
:scroll-sensitivity="60"
|
|
20
|
+
filter=".rc-tabs-header__menu-slot"
|
|
21
|
+
:prevent-on-filter="false"
|
|
22
|
+
ghost-class="rc-tabs-header__tab--ghost"
|
|
23
|
+
chosen-class="rc-tabs-header__tab--chosen"
|
|
24
|
+
drag-class="rc-tabs-header__tab--drag"
|
|
25
|
+
@start="onDragStart"
|
|
26
|
+
@end="onDragEnd"
|
|
27
|
+
@update:model-value="onDragOrderUpdate"
|
|
10
28
|
>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
:full-size="fullSize"
|
|
19
|
-
:is-active="activeTab === item.id"
|
|
20
|
-
:is-disabled="item.isDisabled"
|
|
21
|
-
:scroll-overflow="scrollOverflow"
|
|
22
|
-
@click="onTabClick({ id: item.id, isDisabled: item.isDisabled })"
|
|
23
|
-
>
|
|
24
|
-
<RCIcon
|
|
25
|
-
v-if="item.icon"
|
|
26
|
-
:color="
|
|
27
|
-
activeTab === item.id
|
|
28
|
-
? theme.semanticColors.purple[500]
|
|
29
|
-
: theme.semanticColors.teal[800]
|
|
30
|
-
"
|
|
31
|
-
data-test-id="tab-icon"
|
|
32
|
-
:hovered-color="
|
|
33
|
-
activeTab === item.id
|
|
34
|
-
? theme.semanticColors.purple[500]
|
|
35
|
-
: theme.semanticColors.teal[800]
|
|
36
|
-
"
|
|
37
|
-
:name="item.icon"
|
|
38
|
-
size="14px"
|
|
39
|
-
/>
|
|
40
|
-
<TabText
|
|
41
|
-
data-test-id="tab-text"
|
|
29
|
+
<template #item="{ element: item }">
|
|
30
|
+
<TabItem
|
|
31
|
+
:data-id="item.dataId"
|
|
32
|
+
:data-qa-id="item.dataId"
|
|
33
|
+
:data-test-active="activeTab === item.id"
|
|
34
|
+
data-test-id="tab-item"
|
|
35
|
+
:full-size="fullSize"
|
|
42
36
|
:is-active="activeTab === item.id"
|
|
43
37
|
:is-disabled="item.isDisabled"
|
|
44
|
-
|
|
38
|
+
:is-draggable="dragEnabled"
|
|
39
|
+
:scroll-overflow="scrollOverflow"
|
|
40
|
+
@click="onTabClick({ id: item.id, isDisabled: item.isDisabled })"
|
|
45
41
|
>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
:
|
|
42
|
+
<RCIcon
|
|
43
|
+
v-if="item.icon"
|
|
44
|
+
:color="
|
|
45
|
+
activeTab === item.id
|
|
46
|
+
? theme.semanticColors.purple[500]
|
|
47
|
+
: theme.semanticColors.teal[800]
|
|
48
|
+
"
|
|
49
|
+
data-test-id="tab-icon"
|
|
50
|
+
:hovered-color="
|
|
51
|
+
activeTab === item.id
|
|
52
|
+
? theme.semanticColors.purple[500]
|
|
53
|
+
: theme.semanticColors.teal[800]
|
|
54
|
+
"
|
|
55
|
+
:name="item.icon"
|
|
56
|
+
size="14px"
|
|
57
|
+
/>
|
|
58
|
+
<TabText
|
|
59
|
+
data-test-id="tab-text"
|
|
60
|
+
:is-active="activeTab === item.id"
|
|
61
|
+
:is-disabled="item.isDisabled"
|
|
62
|
+
:is-draggable="dragEnabled"
|
|
63
|
+
>{{ item.text }}</TabText
|
|
64
|
+
>
|
|
65
|
+
<TabMenuSlot
|
|
66
|
+
v-if="item.showTabMenu && $slots['tab-menu']"
|
|
67
|
+
class="rc-tabs-header__menu-slot"
|
|
68
|
+
data-test-id="tab-menu-slot"
|
|
69
|
+
:is-active="activeTab === item.id"
|
|
70
|
+
>
|
|
71
|
+
<slot
|
|
72
|
+
name="tab-menu"
|
|
73
|
+
:item="item"
|
|
74
|
+
:is-active="activeTab === item.id"
|
|
75
|
+
/>
|
|
76
|
+
</TabMenuSlot>
|
|
77
|
+
<DotIcon
|
|
78
|
+
v-if="item.subText"
|
|
79
|
+
data-test-id="dot-icon"
|
|
55
80
|
:is-active="activeTab === item.id"
|
|
56
81
|
/>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
:is-capitalized="true"
|
|
80
|
-
:text="item.upgradeTagText"
|
|
81
|
-
/>
|
|
82
|
-
</TabItem>
|
|
83
|
-
</TabsContainer>
|
|
82
|
+
<SubText v-if="item.subText" data-test-id="tab-subtext">{{
|
|
83
|
+
item.subText
|
|
84
|
+
}}</SubText>
|
|
85
|
+
<RCIcon
|
|
86
|
+
v-if="item.hasError"
|
|
87
|
+
data-test-id="warning-icon"
|
|
88
|
+
name="warning"
|
|
89
|
+
size="14px"
|
|
90
|
+
/>
|
|
91
|
+
<ConversionTag
|
|
92
|
+
v-if="item.showUpgradeTag"
|
|
93
|
+
:data-id="
|
|
94
|
+
item.isTrialEnded || item.isTrialActive
|
|
95
|
+
? `${item.upgradeName}_upgrade_only`
|
|
96
|
+
: `conversion_tag_import_${item.upgradeName}_available_trial_${item.isTrialAvailable}`
|
|
97
|
+
"
|
|
98
|
+
:is-capitalized="true"
|
|
99
|
+
:text="item.upgradeTagText"
|
|
100
|
+
/>
|
|
101
|
+
</TabItem>
|
|
102
|
+
</template>
|
|
103
|
+
</Draggable>
|
|
84
104
|
</TabsScrollArea>
|
|
85
105
|
<AfterTabsSlot v-if="$slots['after-tabs']" data-test-id="after-tabs-slot">
|
|
86
106
|
<slot name="after-tabs" />
|
|
@@ -90,6 +110,7 @@
|
|
|
90
110
|
|
|
91
111
|
<script>
|
|
92
112
|
import styled from 'vue3-styled-components'
|
|
113
|
+
import Draggable from 'vuedraggable'
|
|
93
114
|
import RCIcon from '../icon'
|
|
94
115
|
import theme from '@/assets/theme'
|
|
95
116
|
import ConversionTag from '../tag/conversionTag'
|
|
@@ -131,15 +152,6 @@
|
|
|
131
152
|
: ''}
|
|
132
153
|
`
|
|
133
154
|
|
|
134
|
-
const TabsContainerAttrs = { scrollOverflow: Boolean }
|
|
135
|
-
const TabsContainer = styled('div', TabsContainerAttrs)`
|
|
136
|
-
display: flex;
|
|
137
|
-
flex-wrap: ${(props) => (props.scrollOverflow ? 'nowrap' : 'wrap')};
|
|
138
|
-
align-items: stretch;
|
|
139
|
-
cursor: pointer;
|
|
140
|
-
width: ${(props) => (props.scrollOverflow ? 'max-content' : '100%')};
|
|
141
|
-
`
|
|
142
|
-
|
|
143
155
|
const AfterTabsSlot = styled.div`
|
|
144
156
|
flex-shrink: 0;
|
|
145
157
|
display: flex;
|
|
@@ -160,6 +172,7 @@
|
|
|
160
172
|
isActive: Boolean,
|
|
161
173
|
fullSize: Boolean,
|
|
162
174
|
isDisabled: Boolean,
|
|
175
|
+
isDraggable: Boolean,
|
|
163
176
|
scrollOverflow: Boolean,
|
|
164
177
|
}
|
|
165
178
|
const TabItem = styled('div', TabAttrs)`
|
|
@@ -169,7 +182,10 @@
|
|
|
169
182
|
gap: 8px;
|
|
170
183
|
padding: 10px 25px;
|
|
171
184
|
font-size: 14px;
|
|
172
|
-
|
|
185
|
+
user-select: none;
|
|
186
|
+
-webkit-user-select: none;
|
|
187
|
+
cursor: ${(props) =>
|
|
188
|
+
props.isDisabled ? 'not-allowed' : props.isDraggable ? 'grab' : 'pointer'};
|
|
173
189
|
font-weight: ${(props) => (props.isActive ? '500' : '400')};
|
|
174
190
|
color: ${(props) =>
|
|
175
191
|
props.isActive
|
|
@@ -199,7 +215,11 @@
|
|
|
199
215
|
}
|
|
200
216
|
`
|
|
201
217
|
|
|
202
|
-
const TabTextAttrs = {
|
|
218
|
+
const TabTextAttrs = {
|
|
219
|
+
isActive: Boolean,
|
|
220
|
+
isDisabled: Boolean,
|
|
221
|
+
isDraggable: Boolean,
|
|
222
|
+
}
|
|
203
223
|
const TabText = styled('div', TabTextAttrs)`
|
|
204
224
|
font-size: 14px;
|
|
205
225
|
font-weight: 400;
|
|
@@ -209,15 +229,16 @@
|
|
|
209
229
|
: props.isDisabled
|
|
210
230
|
? props.theme.semanticColors.grey[600]
|
|
211
231
|
: props.theme.semanticColors.teal[700]};
|
|
212
|
-
cursor: ${(props) =>
|
|
232
|
+
cursor: ${(props) =>
|
|
233
|
+
props.isDisabled ? 'not-allowed' : props.isDraggable ? 'grab' : 'pointer'};
|
|
213
234
|
`
|
|
214
235
|
|
|
215
236
|
export default {
|
|
216
237
|
name: 'RCTabsHeader',
|
|
217
238
|
components: {
|
|
239
|
+
Draggable,
|
|
218
240
|
RootContainer,
|
|
219
241
|
TabsScrollArea,
|
|
220
|
-
TabsContainer,
|
|
221
242
|
AfterTabsSlot,
|
|
222
243
|
TabItem,
|
|
223
244
|
TabMenuSlot,
|
|
@@ -250,13 +271,41 @@
|
|
|
250
271
|
type: Boolean,
|
|
251
272
|
default: false,
|
|
252
273
|
},
|
|
274
|
+
/**
|
|
275
|
+
* Opt-in drag-and-drop reordering of the tabs. Off by default so existing
|
|
276
|
+
* usages are unaffected. When on, dragging a tab emits `on-tabs-reorder`
|
|
277
|
+
* with the new ordered list of tab ids. No affordance is shown for a
|
|
278
|
+
* single tab.
|
|
279
|
+
*/
|
|
280
|
+
draggable: {
|
|
281
|
+
type: Boolean,
|
|
282
|
+
default: false,
|
|
283
|
+
},
|
|
253
284
|
},
|
|
254
|
-
emits: ['on-tab-change', 'on-tab-close'],
|
|
285
|
+
emits: ['on-tab-change', 'on-tab-close', 'on-tabs-reorder'],
|
|
255
286
|
data() {
|
|
256
287
|
return {
|
|
257
288
|
theme,
|
|
289
|
+
localTabs: [],
|
|
258
290
|
}
|
|
259
291
|
},
|
|
292
|
+
computed: {
|
|
293
|
+
dragEnabled() {
|
|
294
|
+
return (
|
|
295
|
+
this.draggable &&
|
|
296
|
+
Array.isArray(this.tabsData) &&
|
|
297
|
+
this.tabsData.length > 1
|
|
298
|
+
)
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
watch: {
|
|
302
|
+
tabsData: {
|
|
303
|
+
immediate: true,
|
|
304
|
+
handler(next) {
|
|
305
|
+
this.localTabs = Array.isArray(next) ? [...next] : []
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
260
309
|
methods: {
|
|
261
310
|
onTabClick({ id, isDisabled }) {
|
|
262
311
|
if (isDisabled) {
|
|
@@ -267,6 +316,64 @@
|
|
|
267
316
|
}
|
|
268
317
|
this.$emit('on-tab-change', id)
|
|
269
318
|
},
|
|
319
|
+
onDragOrderUpdate(nextTabs) {
|
|
320
|
+
this.localTabs = nextTabs
|
|
321
|
+
this.$emit(
|
|
322
|
+
'on-tabs-reorder',
|
|
323
|
+
nextTabs.map((tab) => tab.id)
|
|
324
|
+
)
|
|
325
|
+
},
|
|
326
|
+
onDragStart() {
|
|
327
|
+
// Force a consistent grabbing cursor for the whole drag: the fallback
|
|
328
|
+
// clone has pointer-events none, so without this the cursor flickers to
|
|
329
|
+
// whatever sits under the pointer (a tab, a gap, the body).
|
|
330
|
+
document.body.classList.add('rc-tabs-header--dragging')
|
|
331
|
+
},
|
|
332
|
+
onDragEnd() {
|
|
333
|
+
document.body.classList.remove('rc-tabs-header--dragging')
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
beforeUnmount() {
|
|
337
|
+
// Guard against a drag interrupted by an unmount leaving the class stuck.
|
|
338
|
+
document.body.classList.remove('rc-tabs-header--dragging')
|
|
270
339
|
},
|
|
271
340
|
}
|
|
272
341
|
</script>
|
|
342
|
+
|
|
343
|
+
<style scoped>
|
|
344
|
+
.rc-tabs-header__tabs {
|
|
345
|
+
display: flex;
|
|
346
|
+
flex-wrap: wrap;
|
|
347
|
+
align-items: stretch;
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
width: 100%;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.rc-tabs-header__tabs--scroll {
|
|
353
|
+
flex-wrap: nowrap;
|
|
354
|
+
width: max-content;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Drop position: a slim gap rather than a swap highlight. */
|
|
358
|
+
.rc-tabs-header__tab--ghost {
|
|
359
|
+
opacity: 0.25;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.rc-tabs-header__tab--chosen {
|
|
363
|
+
cursor: grabbing;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.rc-tabs-header__tab--drag {
|
|
367
|
+
opacity: 0.9;
|
|
368
|
+
}
|
|
369
|
+
</style>
|
|
370
|
+
|
|
371
|
+
<style>
|
|
372
|
+
/* While a tab is being dragged, keep the grabbing cursor everywhere — the
|
|
373
|
+
forceFallback clone lets the pointer fall through to elements underneath,
|
|
374
|
+
which would otherwise flip the cursor between grab / default / grabbing. */
|
|
375
|
+
body.rc-tabs-header--dragging,
|
|
376
|
+
body.rc-tabs-header--dragging * {
|
|
377
|
+
cursor: grabbing !important;
|
|
378
|
+
}
|
|
379
|
+
</style>
|