@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_reusable_components",
3
- "version": "9.31.4",
3
+ "version": "9.34.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -4,83 +4,103 @@
4
4
  data-test-id="tabs-scroll-area"
5
5
  :scroll-overflow="scrollOverflow"
6
6
  >
7
- <TabsContainer
7
+ <Draggable
8
+ class="rc-tabs-header__tabs"
9
+ :class="{ 'rc-tabs-header__tabs--scroll': scrollOverflow }"
8
10
  data-test-id="tabs-container"
9
- :scroll-overflow="scrollOverflow"
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
- <TabItem
12
- v-for="item in tabsData"
13
- :key="item.id"
14
- :data-id="item.dataId"
15
- :data-qa-id="item.dataId"
16
- :data-test-active="activeTab === item.id"
17
- data-test-id="tab-item"
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
- >{{ item.text }}</TabText
38
+ :is-draggable="dragEnabled"
39
+ :scroll-overflow="scrollOverflow"
40
+ @click="onTabClick({ id: item.id, isDisabled: item.isDisabled })"
45
41
  >
46
- <TabMenuSlot
47
- v-if="item.showTabMenu && $slots['tab-menu']"
48
- class="rc-tabs-header__menu-slot"
49
- data-test-id="tab-menu-slot"
50
- :is-active="activeTab === item.id"
51
- >
52
- <slot
53
- name="tab-menu"
54
- :item="item"
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
- </TabMenuSlot>
58
- <DotIcon
59
- v-if="item.subText"
60
- data-test-id="dot-icon"
61
- :is-active="activeTab === item.id"
62
- />
63
- <SubText v-if="item.subText" data-test-id="tab-subtext">{{
64
- item.subText
65
- }}</SubText>
66
- <RCIcon
67
- v-if="item.hasError"
68
- data-test-id="warning-icon"
69
- name="warning"
70
- size="14px"
71
- />
72
- <ConversionTag
73
- v-if="item.showUpgradeTag"
74
- :data-id="
75
- item.isTrialEnded || item.isTrialActive
76
- ? `${item.upgradeName}_upgrade_only`
77
- : `conversion_tag_import_${item.upgradeName}_available_trial_${item.isTrialAvailable}`
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
- cursor: ${(props) => (props.isDisabled ? 'not-allowed' : 'pointer')};
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 = { isActive: Boolean, isDisabled: Boolean }
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) => (props.isDisabled ? 'not-allowed' : 'pointer')};
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>