srcdev-nuxt-components 1.0.4 → 1.1.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.
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tabs">
|
|
2
|
+
<div class="tabs-core" :class="`axis-${axis}`">
|
|
3
3
|
<ul role="tablist" aria-labelledby="channel-name" ref="tabsNavRef" @mouseleave="resetHoverToActivePosition()" class="tabs-list" :class="[elementClasses]">
|
|
4
4
|
<li v-for="(index, key) in navItems" :key="key">
|
|
5
5
|
<button
|
|
6
6
|
@click.prevent="navItemClicked($event)"
|
|
7
7
|
@mouseover="navItemHovered($event)"
|
|
8
8
|
:id="`tab-${key}-trigger`"
|
|
9
|
-
:data-tab-index="
|
|
9
|
+
:data-tab-index="key"
|
|
10
10
|
data-nav-item
|
|
11
11
|
role="tab"
|
|
12
12
|
aria-selected="false"
|
|
@@ -32,6 +32,14 @@ const props = defineProps({
|
|
|
32
32
|
type: String as PropType<string>,
|
|
33
33
|
default: 'button',
|
|
34
34
|
},
|
|
35
|
+
axis: {
|
|
36
|
+
type: String as PropType<'x' | 'y'>,
|
|
37
|
+
default: 'x',
|
|
38
|
+
},
|
|
39
|
+
transitionDuration: {
|
|
40
|
+
type: Number as PropType<number>,
|
|
41
|
+
default: 200,
|
|
42
|
+
},
|
|
35
43
|
navItems: {
|
|
36
44
|
type: Array as PropType<ITabNav[]>,
|
|
37
45
|
required: true,
|
|
@@ -59,7 +67,7 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
|
|
|
59
67
|
const tabsNavRef = ref<HTMLElement | null>(null);
|
|
60
68
|
const tabsContentRefs = ref<HTMLElement[] | null>(null);
|
|
61
69
|
|
|
62
|
-
const { initNavDecorators, navItemClicked, navItemHovered, resetHoverToActivePosition } = useTabs(tabsNavRef, tabsContentRefs);
|
|
70
|
+
const { initNavDecorators, navItemClicked, navItemHovered, resetHoverToActivePosition } = useTabs(props.axis, tabsNavRef, tabsContentRefs, props.transitionDuration);
|
|
63
71
|
|
|
64
72
|
onMounted(() => {
|
|
65
73
|
initNavDecorators();
|
|
@@ -67,7 +75,7 @@ onMounted(() => {
|
|
|
67
75
|
</script>
|
|
68
76
|
|
|
69
77
|
<style lang="css">
|
|
70
|
-
.tabs {
|
|
78
|
+
.tabs-core {
|
|
71
79
|
/*
|
|
72
80
|
* CSS var within /assets/styles/components/tabs.css
|
|
73
81
|
*/
|
|
@@ -104,7 +112,7 @@ onMounted(() => {
|
|
|
104
112
|
bottom: 0;
|
|
105
113
|
top: 0;
|
|
106
114
|
scale: var(--_width-hovered, 0.125) 1;
|
|
107
|
-
translate: var(--
|
|
115
|
+
translate: var(--_x-hovered, 0) 0;
|
|
108
116
|
transform-origin: left;
|
|
109
117
|
transition: scale var(--_transition-duration), translate var(--_transition-duration);
|
|
110
118
|
z-index: 1;
|
|
@@ -118,7 +126,7 @@ onMounted(() => {
|
|
|
118
126
|
bottom: 0;
|
|
119
127
|
top: 0;
|
|
120
128
|
scale: var(--_width-active, 0.125) 1;
|
|
121
|
-
translate: var(--
|
|
129
|
+
translate: var(--_x-active, 0) 0;
|
|
122
130
|
transform-origin: left;
|
|
123
131
|
transition: scale var(--_transition-duration), translate var(--_transition-duration);
|
|
124
132
|
z-index: 2;
|
|
@@ -131,7 +139,7 @@ onMounted(() => {
|
|
|
131
139
|
right: 0;
|
|
132
140
|
bottom: 0;
|
|
133
141
|
scale: var(--_width-active, 0.125) 1;
|
|
134
|
-
translate: var(--
|
|
142
|
+
translate: var(--_x-active, 0) 0;
|
|
135
143
|
transform-origin: left;
|
|
136
144
|
transition: scale var(--_transition-duration), translate var(--_transition-duration);
|
|
137
145
|
z-index: 3;
|
|
@@ -140,7 +148,7 @@ onMounted(() => {
|
|
|
140
148
|
.tabs-list-item {
|
|
141
149
|
opacity: 0.7;
|
|
142
150
|
position: relative;
|
|
143
|
-
transition: color
|
|
151
|
+
transition: color 100ms;
|
|
144
152
|
z-index: 4;
|
|
145
153
|
|
|
146
154
|
&:hover {
|
|
@@ -181,12 +189,17 @@ onMounted(() => {
|
|
|
181
189
|
padding: 1em 2em;
|
|
182
190
|
|
|
183
191
|
&:hover {
|
|
192
|
+
/* background: var(--_tabs-hovered-bg); */
|
|
184
193
|
color: var(--_tabs-hovered-text);
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
&[aria-selected='true'] {
|
|
188
197
|
color: var(--_tabs-active-text);
|
|
189
198
|
}
|
|
199
|
+
|
|
200
|
+
&.transitioning {
|
|
201
|
+
color: var(--_tabs-hovered-text);
|
|
202
|
+
}
|
|
190
203
|
}
|
|
191
204
|
}
|
|
192
205
|
|
|
@@ -218,4 +231,78 @@ onMounted(() => {
|
|
|
218
231
|
} */
|
|
219
232
|
}
|
|
220
233
|
}
|
|
234
|
+
|
|
235
|
+
/*
|
|
236
|
+
* Deal with axis-y
|
|
237
|
+
**/
|
|
238
|
+
|
|
239
|
+
.tabs-core {
|
|
240
|
+
&.axis-y {
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-direction: row;
|
|
243
|
+
gap: 2em;
|
|
244
|
+
|
|
245
|
+
.tabs-list {
|
|
246
|
+
flex-direction: column;
|
|
247
|
+
|
|
248
|
+
border-bottom: initial;
|
|
249
|
+
border-left: var(--_tabs-border-bottom);
|
|
250
|
+
position: relative;
|
|
251
|
+
|
|
252
|
+
.tabs-list-item {
|
|
253
|
+
text-align: left;
|
|
254
|
+
width: 100%;
|
|
255
|
+
|
|
256
|
+
/* &:hover {
|
|
257
|
+
color: var(--_tabs-hovered-text);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
&[aria-selected='true'] {
|
|
261
|
+
color: var(--_tabs-active-text);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
&.transitioning {
|
|
265
|
+
color: var(--_tabs-hovered-text);
|
|
266
|
+
} */
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.nav__hovered {
|
|
270
|
+
left: 0;
|
|
271
|
+
right: initial;
|
|
272
|
+
bottom: initial;
|
|
273
|
+
top: 0;
|
|
274
|
+
height: var(--_y-height);
|
|
275
|
+
translate: 0 var(--_y-hovered, 0);
|
|
276
|
+
transform-origin: top;
|
|
277
|
+
width: var(--_y-width);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.nav__active {
|
|
281
|
+
left: 0;
|
|
282
|
+
right: initial;
|
|
283
|
+
bottom: initial;
|
|
284
|
+
top: 0;
|
|
285
|
+
height: var(--_y-height);
|
|
286
|
+
translate: 0 var(--_y-active, 0);
|
|
287
|
+
transform-origin: top;
|
|
288
|
+
width: var(--_y-width);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.nav__active-indicator {
|
|
292
|
+
left: 0;
|
|
293
|
+
right: initial;
|
|
294
|
+
bottom: initial;
|
|
295
|
+
top: 0;
|
|
296
|
+
height: var(--_y-height);
|
|
297
|
+
scale: var(--_width-active, 0.125) 1;
|
|
298
|
+
translate: 0 var(--_y-active, 0);
|
|
299
|
+
transform-origin: top;
|
|
300
|
+
width: 0.4em;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
.tab-content-wrapper {
|
|
304
|
+
flex-grow: 1;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
221
308
|
</style>
|
package/composables/useTabs.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useResizeObserver } from '@vueuse/core';
|
|
2
2
|
|
|
3
|
-
const useTabs = (tabsNavRef: Ref<HTMLElement | null>, tabsContentRefs: Ref<HTMLElement[] | null>, duration: number
|
|
3
|
+
const useTabs = (axis: string, tabsNavRef: Ref<HTMLElement | null>, tabsContentRefs: Ref<HTMLElement[] | null>, duration: number) => {
|
|
4
4
|
const navItems = ref<HTMLElement[] | null>(null);
|
|
5
5
|
const previousActiveTab = useState<HTMLElement | null>('previousActiveTab', () => null);
|
|
6
6
|
const currentActiveTab = ref<HTMLElement>();
|
|
@@ -79,20 +79,59 @@ const useTabs = (tabsNavRef: Ref<HTMLElement | null>, tabsContentRefs: Ref<HTMLE
|
|
|
79
79
|
setActiveTabContent();
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
const handleTransitioningClass = (transitionDuration: number = 200) => {
|
|
83
|
+
if (previousHoveredTab.value && currentHoveredTab.value) {
|
|
84
|
+
const newTabPosition = previousHoveredTab.value.compareDocumentPosition(currentHoveredTab.value);
|
|
85
|
+
const navItemsArray = navItems.value || [];
|
|
86
|
+
|
|
87
|
+
const timeout = Math.floor(transitionDuration / Math.abs(navItemsArray.indexOf(currentHoveredTab.value) - navItemsArray.indexOf(previousHoveredTab.value)));
|
|
88
|
+
|
|
89
|
+
if (newTabPosition === 4) {
|
|
90
|
+
for (let i = navItemsArray.indexOf(previousHoveredTab.value); i < navItemsArray.indexOf(currentHoveredTab.value); i++) {
|
|
91
|
+
navItemsArray[i].classList.add('transitioning');
|
|
92
|
+
if (i >= navItemsArray.indexOf(previousHoveredTab.value) && i < navItemsArray.indexOf(currentHoveredTab.value)) {
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
navItemsArray[i].classList.remove('transitioning');
|
|
95
|
+
// }, timeout * (i - navItemsArray.indexOf(previousHoveredTab.value) - 1));
|
|
96
|
+
}, duration * 1.5);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
for (let i = navItemsArray.indexOf(previousHoveredTab.value); i > navItemsArray.indexOf(currentHoveredTab.value); i--) {
|
|
101
|
+
navItemsArray[i].classList.add('transitioning');
|
|
102
|
+
if (i <= navItemsArray.indexOf(previousHoveredTab.value) && i > navItemsArray.indexOf(currentHoveredTab.value)) {
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
navItemsArray[i].classList.remove('transitioning');
|
|
105
|
+
// }, timeout * (i - navItemsArray.indexOf(previousHoveredTab.value) - 1));
|
|
106
|
+
}, duration * 1.5);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
82
113
|
const setFinalHoveredPositions = (resized: boolean = false) => {
|
|
83
114
|
const setDuration = resized ? 0 : duration;
|
|
115
|
+
// const tabsNavRefYPosition = tabsNavRef.value?.getBoundingClientRect().top || 0;
|
|
84
116
|
const newTabWidth = currentHoveredTab.value && tabsNavRef.value ? currentHoveredTab.value.offsetWidth / tabsNavRef.value.offsetWidth : 0;
|
|
85
117
|
tabsNavRef.value?.style.setProperty('--_transition-duration', setDuration + 'ms');
|
|
86
|
-
tabsNavRef.value?.style.setProperty('--
|
|
118
|
+
tabsNavRef.value?.style.setProperty('--_x-hovered', currentHoveredTab.value?.offsetLeft + 'px');
|
|
119
|
+
|
|
87
120
|
tabsNavRef.value?.style.setProperty('--_width-hovered', newTabWidth?.toString());
|
|
121
|
+
tabsNavRef.value?.style.setProperty('--_y-hovered', currentHoveredTab.value?.offsetTop + 'px');
|
|
122
|
+
tabsNavRef.value?.style.setProperty('--_y-height', currentHoveredTab.value?.offsetHeight + 'px');
|
|
123
|
+
tabsNavRef.value?.style.setProperty('--_y-width', currentHoveredTab.value?.offsetWidth + 'px');
|
|
88
124
|
};
|
|
89
125
|
|
|
90
126
|
const setFinalActivePositions = (resized: boolean = false) => {
|
|
91
127
|
const setDuration = resized ? 0 : duration;
|
|
92
128
|
const newTabWidth = currentActiveTab.value && tabsNavRef.value ? currentActiveTab.value.offsetWidth / tabsNavRef.value.offsetWidth : 0;
|
|
93
129
|
tabsNavRef.value?.style.setProperty('--_transition-duration', setDuration + 'ms');
|
|
94
|
-
tabsNavRef.value?.style.setProperty('--
|
|
130
|
+
tabsNavRef.value?.style.setProperty('--_x-active', currentActiveTab.value?.offsetLeft + 'px');
|
|
95
131
|
tabsNavRef.value?.style.setProperty('--_width-active', newTabWidth?.toString());
|
|
132
|
+
tabsNavRef.value?.style.setProperty('--_y-active', currentActiveTab.value?.offsetTop + 'px');
|
|
133
|
+
tabsNavRef.value?.style.setProperty('--_y-height', currentActiveTab.value?.offsetHeight + 'px');
|
|
134
|
+
tabsNavRef.value?.style.setProperty('--_y-width', currentActiveTab.value?.offsetWidth + 'px');
|
|
96
135
|
};
|
|
97
136
|
|
|
98
137
|
const moveActiveIndicator = () => {
|
|
@@ -105,11 +144,13 @@ const useTabs = (tabsNavRef: Ref<HTMLElement | null>, tabsContentRefs: Ref<HTMLE
|
|
|
105
144
|
transitionWidth = currentActiveTab.value && previousActiveTab.value ? currentActiveTab.value.offsetLeft + currentActiveTab.value.offsetWidth - previousActiveTab.value.offsetLeft : 0;
|
|
106
145
|
} else {
|
|
107
146
|
transitionWidth = previousActiveTab.value && currentActiveTab.value ? previousActiveTab.value.offsetLeft + previousActiveTab.value.offsetWidth - currentActiveTab.value.offsetLeft : 0;
|
|
108
|
-
tabsNavRef.value?.style.setProperty('--
|
|
147
|
+
tabsNavRef.value?.style.setProperty('--_x-active', currentActiveTab.value ? currentActiveTab.value.offsetLeft + 'px' : '0');
|
|
109
148
|
}
|
|
110
149
|
|
|
111
150
|
tabsNavRef.value?.style.setProperty('--_width-active', String(transitionWidth / tabsNavRef.value.offsetWidth));
|
|
112
151
|
|
|
152
|
+
handleTransitioningClass(duration);
|
|
153
|
+
|
|
113
154
|
setTimeout(() => {
|
|
114
155
|
setFinalActivePositions();
|
|
115
156
|
}, Math.floor(duration + 20));
|
|
@@ -125,11 +166,13 @@ const useTabs = (tabsNavRef: Ref<HTMLElement | null>, tabsContentRefs: Ref<HTMLE
|
|
|
125
166
|
transitionWidth = currentHoveredTab.value && previousHoveredTab.value ? currentHoveredTab.value.offsetLeft + currentHoveredTab.value.offsetWidth - previousHoveredTab.value.offsetLeft : 0;
|
|
126
167
|
} else {
|
|
127
168
|
transitionWidth = previousHoveredTab.value && currentHoveredTab.value ? previousHoveredTab.value.offsetLeft + previousHoveredTab.value.offsetWidth - currentHoveredTab.value.offsetLeft : 0;
|
|
128
|
-
tabsNavRef.value?.style.setProperty('--
|
|
169
|
+
tabsNavRef.value?.style.setProperty('--_x-hovered', currentHoveredTab.value ? currentHoveredTab.value.offsetLeft + 'px' : '0');
|
|
129
170
|
}
|
|
130
171
|
|
|
131
172
|
tabsNavRef.value?.style.setProperty('--_width-hovered', String(transitionWidth / tabsNavRef.value.offsetWidth));
|
|
132
173
|
|
|
174
|
+
handleTransitioningClass(duration);
|
|
175
|
+
|
|
133
176
|
setTimeout(() => {
|
|
134
177
|
setFinalHoveredPositions();
|
|
135
178
|
}, Math.floor(duration + 20));
|
|
@@ -142,10 +185,6 @@ const useTabs = (tabsNavRef: Ref<HTMLElement | null>, tabsContentRefs: Ref<HTMLE
|
|
|
142
185
|
});
|
|
143
186
|
};
|
|
144
187
|
|
|
145
|
-
const animationRunning = (running: boolean) => {
|
|
146
|
-
console.log('animationRunning', running);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
188
|
useResizeObserver(tabsNavRef, () => {
|
|
150
189
|
setFinalActivePositions(true);
|
|
151
190
|
setFinalHoveredPositions(true);
|
package/package.json
CHANGED