srcdev-nuxt-components 4.0.0 → 4.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.
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="carousel-basic" :class="[elementClasses]" ref="carouselWrapperRef">
|
|
3
|
+
|
|
4
|
+
<div tabindex="0" class="item-container" ref="carouselContainerRef">
|
|
5
|
+
<div v-for="(item, index) in carouselDataIds" :key="index" class="item" ref="carouselItems">
|
|
6
|
+
<slot :name="item"></slot>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="timeline-container">
|
|
11
|
+
<div v-for="index in itemCount" :key="index" class="timeline-item">
|
|
12
|
+
<div class="count">Step {{ Math.floor(index + 1) }}</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="controls-container">
|
|
17
|
+
<div class="markers-container">
|
|
18
|
+
<ul class="markers-list">
|
|
19
|
+
<li v-for="index in itemCount" :key="index" class="markers-item">
|
|
20
|
+
<button @click.prevent="jumpToFrame(index)" class="btn-marker"
|
|
21
|
+
:class="[{ active: currentIndex === index - 1}]"><span class="sr-only">Jump to item{{
|
|
22
|
+
Math.floor(index + 1) }}</span></button>
|
|
23
|
+
</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="buttons-container">
|
|
27
|
+
<button type="submit" @click.prevent="actionPrevious()" class="btn-action">Prev</button>
|
|
28
|
+
<button type="submit" @click.prevent="actionNext()" class="btn-action">Next</button>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</section>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import type { ICarouselBasic } from "@/types/types.carousel-basic";
|
|
36
|
+
import { useEventListener, useResizeObserver, useSwipe } from "@vueuse/core";
|
|
37
|
+
|
|
38
|
+
const props = defineProps({
|
|
39
|
+
carouselDataIds: {
|
|
40
|
+
type: Array as PropType<string[]>,
|
|
41
|
+
default: () => []
|
|
42
|
+
},
|
|
43
|
+
styleClassPassthrough: {
|
|
44
|
+
type: Array as PropType<string[]>,
|
|
45
|
+
default: () => [],
|
|
46
|
+
},
|
|
47
|
+
transitionSpeed: {
|
|
48
|
+
type: Number,
|
|
49
|
+
default: 200
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
54
|
+
|
|
55
|
+
const carouselWrapperRef = ref<HTMLDivElement | null>(null);
|
|
56
|
+
const carouselContainerRef = ref<HTMLDivElement | null>(null);
|
|
57
|
+
const carouselItemsRef = useTemplateRef<HTMLDivElement[]>('carouselItems');
|
|
58
|
+
const carouselInitComplete = ref(false);
|
|
59
|
+
|
|
60
|
+
const currentIndex = ref(0);
|
|
61
|
+
const itemCount = ref(props.carouselDataIds.length);
|
|
62
|
+
const offset = ref(0);
|
|
63
|
+
const transitionSpeedStr = props.transitionSpeed + 'ms';
|
|
64
|
+
const itemTransform = computed(() => {
|
|
65
|
+
return `translateX(calc(${offset.value} * (${itemWidth.value} + var(--_item-gap))))`;
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const itemWidth = ref('0px');
|
|
69
|
+
|
|
70
|
+
const actionPrevious = () => {
|
|
71
|
+
if (offset.value >= 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
offset.value = Math.min(offset.value + 1);
|
|
76
|
+
doAction();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const actionNext = () => {
|
|
80
|
+
if (offset.value <= -1 * (itemCount.value - 1)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
offset.value = Math.min(offset.value - 1);
|
|
85
|
+
doAction();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const doAction = () => {
|
|
89
|
+
currentIndex.value = Math.abs(offset.value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const jumpToFrame = (index: number) => {
|
|
93
|
+
|
|
94
|
+
if (index >= 0 && index < itemCount.value) {
|
|
95
|
+
offset.value = -index;
|
|
96
|
+
doAction();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const initialSetup = () => {
|
|
101
|
+
if (carouselItemsRef?.value && carouselItemsRef.value.length > 0 && carouselItemsRef.value[0]) {
|
|
102
|
+
itemWidth.value = carouselItemsRef.value[0].offsetWidth + 'px';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
carouselInitComplete.value = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { direction } = useSwipe(
|
|
109
|
+
carouselContainerRef,
|
|
110
|
+
{
|
|
111
|
+
passive: false,
|
|
112
|
+
onSwipeEnd() {
|
|
113
|
+
if (direction.value === 'left') {
|
|
114
|
+
actionNext();
|
|
115
|
+
} else if (direction.value === 'right') {
|
|
116
|
+
actionPrevious();
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
useEventListener(
|
|
123
|
+
carouselContainerRef,
|
|
124
|
+
'keydown',
|
|
125
|
+
(event: KeyboardEvent) => {
|
|
126
|
+
if (event.key === 'ArrowLeft') {
|
|
127
|
+
actionPrevious();
|
|
128
|
+
} else if (event.key === 'ArrowRight') {
|
|
129
|
+
actionNext();
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
useResizeObserver(carouselWrapperRef, async () => {
|
|
135
|
+
initialSetup();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
onMounted(() => {
|
|
139
|
+
initialSetup();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<style lang="css">
|
|
145
|
+
|
|
146
|
+
.carousel-basic {
|
|
147
|
+
--_item-gap: 10px;
|
|
148
|
+
|
|
149
|
+
display: grid;
|
|
150
|
+
grid-template-columns: 1fr;
|
|
151
|
+
gap: 10px;
|
|
152
|
+
|
|
153
|
+
.timeline-container {
|
|
154
|
+
display: flex;
|
|
155
|
+
gap: var(--_item-gap);
|
|
156
|
+
overflow-x: hidden;
|
|
157
|
+
|
|
158
|
+
.timeline-item {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex: 0 0 100%;
|
|
161
|
+
max-inline-size: 800px;
|
|
162
|
+
align-items: center;
|
|
163
|
+
transform: v-bind(itemTransform);
|
|
164
|
+
position: relative;
|
|
165
|
+
|
|
166
|
+
&::before {
|
|
167
|
+
content: '';
|
|
168
|
+
position: absolute;
|
|
169
|
+
height: 2px;
|
|
170
|
+
background-color: #fff;
|
|
171
|
+
left: 70px;
|
|
172
|
+
right: 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.count {
|
|
176
|
+
width: fit-content;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.item-container {
|
|
182
|
+
display: flex;
|
|
183
|
+
gap: var(--_item-gap);
|
|
184
|
+
overflow-x: hidden;
|
|
185
|
+
position: relative;
|
|
186
|
+
|
|
187
|
+
.item {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex: 0 0 100%;
|
|
190
|
+
max-inline-size: 800px;
|
|
191
|
+
transition: transform v-bind(transitionSpeedStr) ease;
|
|
192
|
+
transform: v-bind(itemTransform);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.controls-container {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: flex-end;
|
|
200
|
+
|
|
201
|
+
.markers-container {
|
|
202
|
+
|
|
203
|
+
.markers-list {
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: row;
|
|
206
|
+
gap: 10px;
|
|
207
|
+
list-style-type: none;
|
|
208
|
+
margin: unset;
|
|
209
|
+
padding: unset;
|
|
210
|
+
|
|
211
|
+
.markers-item {
|
|
212
|
+
|
|
213
|
+
.btn-marker {
|
|
214
|
+
border: none;
|
|
215
|
+
outline: none;
|
|
216
|
+
box-shadow: none;
|
|
217
|
+
cursor: pointer;
|
|
218
|
+
transition: background-color v-bind(transitionSpeedStr) linear;
|
|
219
|
+
|
|
220
|
+
&.active {
|
|
221
|
+
background-color: red;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.buttons-container {
|
|
229
|
+
display: flex;
|
|
230
|
+
align-items: center;
|
|
231
|
+
justify-content: end;
|
|
232
|
+
gap: 20px;
|
|
233
|
+
|
|
234
|
+
.btn-action {
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
height: fit-content;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
</style>
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="carousel-flip" :class="[elementClasses, { 'controls-inside': controlsInside }]">
|
|
3
|
+
|
|
4
|
+
<div class="item-container" ref="carouselContent">
|
|
5
|
+
<div v-for="(item, index) in data?.items" :key="index" class="item" ref="carouselItems">
|
|
6
|
+
<h3>{{ index }}</h3>
|
|
7
|
+
<p>{{ item.alt }}</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="controls-container">
|
|
13
|
+
<div class="buttons-container">
|
|
14
|
+
<button type="submit" @click.prevent="actionPrevious()" class="btn-action"
|
|
15
|
+
:disabled="transitionRunning">Prev</button>
|
|
16
|
+
<button type="submit" @click.prevent="actionNext()" class="btn-action"
|
|
17
|
+
:disabled="transitionRunning">Next</button>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="thumbnail-container">
|
|
20
|
+
<ul class="thumbnail-list">
|
|
21
|
+
<li v-for="item, index in data?.items" class="thumbnail-item" ref="thumbnailItems">
|
|
22
|
+
<div class="thumbnail-item_inner">{{ index }}</div>
|
|
23
|
+
</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</section>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import type { ICarouselBasic } from "@/types/types.carousel-basic";
|
|
32
|
+
import { useElementSize, useEventListener, useResizeObserver } from "@vueuse/core";
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
propsData: {
|
|
35
|
+
type: Object as PropType<ICarouselBasic>,
|
|
36
|
+
default: <ICarouselBasic>{
|
|
37
|
+
items: [],
|
|
38
|
+
total: 0,
|
|
39
|
+
skip: 0,
|
|
40
|
+
limit: 10
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
data: {
|
|
44
|
+
type: Object,
|
|
45
|
+
default: <ICarouselBasic>{}
|
|
46
|
+
},
|
|
47
|
+
styleClassPassthrough: {
|
|
48
|
+
type: Array as PropType<string[]>,
|
|
49
|
+
default: () => [],
|
|
50
|
+
},
|
|
51
|
+
transitionSpeed: {
|
|
52
|
+
type: Number,
|
|
53
|
+
default: 1000
|
|
54
|
+
},
|
|
55
|
+
controlsInside: {
|
|
56
|
+
type: Boolean,
|
|
57
|
+
default: false
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
62
|
+
|
|
63
|
+
const carouselContentRef = useTemplateRef<HTMLDivElement>('carouselContent');
|
|
64
|
+
const carouselItems = useTemplateRef<HTMLDivElement[]>('carouselItems');
|
|
65
|
+
const thumbnailItems = useTemplateRef<HTMLLIElement[]>('thumbnailItems');
|
|
66
|
+
const carouselInitComplete = ref(false);
|
|
67
|
+
|
|
68
|
+
const currentIndex = ref(1);
|
|
69
|
+
const itemCount = ref(props.data.items.length);
|
|
70
|
+
const offset = ref(1);
|
|
71
|
+
const previousOffset = ref(1);
|
|
72
|
+
const transitionSpeedStr = props.transitionSpeed + 'ms';
|
|
73
|
+
const transitionRunning = ref(false);
|
|
74
|
+
|
|
75
|
+
const actionPrevious = () => {
|
|
76
|
+
// if (transitionRunning.value) return;
|
|
77
|
+
|
|
78
|
+
offset.value = -1;
|
|
79
|
+
onTransitionEnd();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const actionNext = () => {
|
|
83
|
+
// if (transitionRunning.value) return;
|
|
84
|
+
|
|
85
|
+
offset.value = 1;
|
|
86
|
+
onTransitionEnd();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const updateOrder = (index: number, order: number) => {
|
|
90
|
+
if (carouselItems.value !== null && thumbnailItems.value !== null) {
|
|
91
|
+
carouselItems.value[index - 1].style.order = order.toString();
|
|
92
|
+
thumbnailItems.value[index - 1].style.order = order.toString();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const initialSetup = () => {
|
|
97
|
+
const items = carouselItems.value;
|
|
98
|
+
const thumbs = thumbnailItems.value;
|
|
99
|
+
|
|
100
|
+
items?.forEach((item, index) => {
|
|
101
|
+
item.style.zIndex = index === 0 || index === itemCount.value - 1 ? '1' : '2';
|
|
102
|
+
item.style.order = String(index + 1);
|
|
103
|
+
// item.setAttribute('data-order', String(index + 1));
|
|
104
|
+
});
|
|
105
|
+
thumbs?.forEach((thumb, index) => {
|
|
106
|
+
thumb.style.zIndex = index === 0 || index === itemCount.value - 1 ? '1' : '2';
|
|
107
|
+
thumb.style.order = String(index + 1);
|
|
108
|
+
// thumb.setAttribute('data-order', String(index + 1));
|
|
109
|
+
});
|
|
110
|
+
carouselInitComplete.value = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
const onTransitionEnd = () => {
|
|
115
|
+
|
|
116
|
+
// transitionRunning.value = true;
|
|
117
|
+
const items = carouselItems.value;
|
|
118
|
+
const thumbs = thumbnailItems.value;
|
|
119
|
+
|
|
120
|
+
if (!items || !Array.isArray(items)) return;
|
|
121
|
+
if (!thumbs || !Array.isArray(thumbs)) return;
|
|
122
|
+
|
|
123
|
+
// 1. Capture initial positions for both main items and thumbnails
|
|
124
|
+
const firstRects = items.map(el => el.getBoundingClientRect());
|
|
125
|
+
const firstThumbRects = thumbs.map(el => el.getBoundingClientRect());
|
|
126
|
+
|
|
127
|
+
// 2. Update orders
|
|
128
|
+
let firstVisualElementIndex = currentIndex.value; // Track which element should be visually first
|
|
129
|
+
|
|
130
|
+
if (carouselInitComplete.value) {
|
|
131
|
+
if (offset.value === 1) {
|
|
132
|
+
const localOffset = offset.value === previousOffset.value ? offset.value : 2; // Ensure we have a valid offset
|
|
133
|
+
currentIndex.value = currentIndex.value === itemCount.value ? 1 : currentIndex.value + localOffset;
|
|
134
|
+
firstVisualElementIndex = currentIndex.value;
|
|
135
|
+
let order = 1;
|
|
136
|
+
|
|
137
|
+
for (let i = currentIndex.value; i <= itemCount.value; i++) updateOrder(i, order++);
|
|
138
|
+
for (let i = 1; i < currentIndex.value; i++) updateOrder(i, order++);
|
|
139
|
+
|
|
140
|
+
} else {
|
|
141
|
+
const localOffset = offset.value === previousOffset.value ? offset.value : -2; // Ensure we have a valid offset
|
|
142
|
+
currentIndex.value = currentIndex.value === 1 ? itemCount.value : currentIndex.value + localOffset;
|
|
143
|
+
firstVisualElementIndex = currentIndex.value;
|
|
144
|
+
let order = itemCount.value;
|
|
145
|
+
|
|
146
|
+
for (let i = currentIndex.value; i >= 1; i--) updateOrder(i, order--);
|
|
147
|
+
for (let i = itemCount.value; i > currentIndex.value; i--) updateOrder(i, order--);
|
|
148
|
+
}
|
|
149
|
+
previousOffset.value = offset.value; // Store the previous offset for next transition
|
|
150
|
+
|
|
151
|
+
// setTimeout(() => {
|
|
152
|
+
// transitionRunning.value = false;
|
|
153
|
+
// }, props.transitionSpeed);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 3. Next tick: capture new positions & animate both main items and thumbnails
|
|
157
|
+
requestAnimationFrame(() => {
|
|
158
|
+
const lastRects = items.map(el => el.getBoundingClientRect());
|
|
159
|
+
const lastThumbRects = thumbs.map(el => el.getBoundingClientRect());
|
|
160
|
+
|
|
161
|
+
// Animate main carousel items
|
|
162
|
+
items.forEach((el, i) => {
|
|
163
|
+
const dx = firstRects[i].left - lastRects[i].left;
|
|
164
|
+
const dy = firstRects[i].top - lastRects[i].top;
|
|
165
|
+
|
|
166
|
+
el.style.transition = 'none';
|
|
167
|
+
el.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
168
|
+
|
|
169
|
+
requestAnimationFrame(() => {
|
|
170
|
+
el.style.transition = `transform ${transitionSpeedStr} ease`;
|
|
171
|
+
el.style.transform = '';
|
|
172
|
+
|
|
173
|
+
// Set z-index after the transition actually completes
|
|
174
|
+
const elementIndex = i + 1; // Convert to 1-based index to match your logic
|
|
175
|
+
const isFirstVisual = elementIndex === firstVisualElementIndex;
|
|
176
|
+
|
|
177
|
+
// Listen for transition end to update z-index
|
|
178
|
+
const handleTransitionEnd = (event: TransitionEvent) => {
|
|
179
|
+
if (event.propertyName === 'transform') {
|
|
180
|
+
el.style.zIndex = isFirstVisual ? '1' : '2';
|
|
181
|
+
el.removeEventListener('transitionend', handleTransitionEnd);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
el.addEventListener('transitionend', handleTransitionEnd);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Animate thumbnail items
|
|
190
|
+
thumbs.forEach((thumb, i) => {
|
|
191
|
+
const dx = firstThumbRects[i].left - lastThumbRects[i].left;
|
|
192
|
+
const dy = firstThumbRects[i].top - lastThumbRects[i].top;
|
|
193
|
+
|
|
194
|
+
thumb.style.transition = 'none';
|
|
195
|
+
thumb.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
196
|
+
|
|
197
|
+
requestAnimationFrame(() => {
|
|
198
|
+
thumb.style.transition = `transform ${transitionSpeedStr} ease`;
|
|
199
|
+
thumb.style.transform = '';
|
|
200
|
+
|
|
201
|
+
// Set z-index after the transition actually completes
|
|
202
|
+
const thumbIndex = i + 1; // Convert to 1-based index
|
|
203
|
+
const isActiveThumbnail = thumbIndex === firstVisualElementIndex;
|
|
204
|
+
|
|
205
|
+
// Listen for transition end to update z-index
|
|
206
|
+
const handleThumbTransitionEnd = (event: TransitionEvent) => {
|
|
207
|
+
if (event.propertyName === 'transform') {
|
|
208
|
+
thumb.style.zIndex = isActiveThumbnail ? '1' : '2';
|
|
209
|
+
thumb.removeEventListener('transitionend', handleThumbTransitionEnd);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
thumb.addEventListener('transitionend', handleThumbTransitionEnd);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
carouselInitComplete.value = true;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
onMounted(() => {
|
|
222
|
+
initialSetup();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<style lang="css">
|
|
228
|
+
.carousel-flip {
|
|
229
|
+
|
|
230
|
+
display: grid;
|
|
231
|
+
grid-template-columns: 1fr;
|
|
232
|
+
gap: 10px;
|
|
233
|
+
|
|
234
|
+
&.controls-inside {
|
|
235
|
+
grid-template-areas: "carousel-content";
|
|
236
|
+
isolation: isolate;
|
|
237
|
+
|
|
238
|
+
.item-container {
|
|
239
|
+
grid-area: carousel-content;
|
|
240
|
+
z-index: 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.controls-container {
|
|
244
|
+
grid-area: carousel-content;
|
|
245
|
+
z-index: 2;
|
|
246
|
+
height: fit-content;
|
|
247
|
+
align-self: flex-end;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.item-container {
|
|
252
|
+
display: flex;
|
|
253
|
+
gap: 10px;
|
|
254
|
+
overflow-x: auto;
|
|
255
|
+
padding-block: 10px;
|
|
256
|
+
padding-inline: 10px;
|
|
257
|
+
outline: 1px solid light-dark(#00000090, #f00ff090);
|
|
258
|
+
|
|
259
|
+
/* scroll-snap-type: x mandatory; */
|
|
260
|
+
|
|
261
|
+
/* isolation: isolate; */
|
|
262
|
+
position: relative;
|
|
263
|
+
|
|
264
|
+
.item {
|
|
265
|
+
display: flex;
|
|
266
|
+
flex-direction: column;
|
|
267
|
+
align-items: center;
|
|
268
|
+
justify-content: center;
|
|
269
|
+
|
|
270
|
+
/* transition: transform v-bind(transitionSpeedStr) ease; */
|
|
271
|
+
/* For FLIP smoothness */
|
|
272
|
+
|
|
273
|
+
aspect-ratio: 4 / 3;
|
|
274
|
+
|
|
275
|
+
min-inline-size: 600px;
|
|
276
|
+
color: light-dar(#aaa, #333);
|
|
277
|
+
padding-block: 10px;
|
|
278
|
+
padding-inline: 10px;
|
|
279
|
+
border-radius: 4px;
|
|
280
|
+
outline: 1px solid light-dark(#00000090, #f00ff090);
|
|
281
|
+
|
|
282
|
+
background-color: light-dark(#f00, #00f);
|
|
283
|
+
|
|
284
|
+
/* scroll-snap-align: none center; */
|
|
285
|
+
|
|
286
|
+
&:nth-child(odd) {
|
|
287
|
+
background-color: light-dark(#00f, #f00);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
.controls-container {
|
|
294
|
+
|
|
295
|
+
display: flex;
|
|
296
|
+
gap: 20px;
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
.buttons-container {
|
|
301
|
+
display: flex;
|
|
302
|
+
flex-grow: 1;
|
|
303
|
+
align-items: center;
|
|
304
|
+
justify-content: end;
|
|
305
|
+
gap: 20px;
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
.btn-action {
|
|
309
|
+
padding: 10px 20px;
|
|
310
|
+
border-radius: 4px;
|
|
311
|
+
background-color: light-dark(#000, #fff);
|
|
312
|
+
color: light-dark(#fff, #000);
|
|
313
|
+
border: none;
|
|
314
|
+
cursor: pointer;
|
|
315
|
+
height: fit-content;
|
|
316
|
+
|
|
317
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
318
|
+
|
|
319
|
+
&:hover {
|
|
320
|
+
background-color: light-dark(#0009, #fff9);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
&:active {
|
|
324
|
+
background-color: light-dark(#0009, #fff9);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
&:disabled {
|
|
328
|
+
background-color: light-dark(#0003, #fff3);
|
|
329
|
+
cursor: not-allowed;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.thumbnail-container {
|
|
335
|
+
padding-block: 10px;
|
|
336
|
+
padding-inline: 10px;
|
|
337
|
+
outline: 1px solid light-dark(#00000090, #f00ff090);
|
|
338
|
+
max-inline-size: 40%;
|
|
339
|
+
|
|
340
|
+
.thumbnail-list {
|
|
341
|
+
display: flex;
|
|
342
|
+
gap: 10px;
|
|
343
|
+
list-style-type: none;
|
|
344
|
+
padding-block: 8px;
|
|
345
|
+
padding-inline: 8px;
|
|
346
|
+
margin-block: 0;
|
|
347
|
+
margin-inline: 0;
|
|
348
|
+
|
|
349
|
+
outline: 1px solid light-dark(#00000090, #f00ff090);
|
|
350
|
+
overflow-x: auto;
|
|
351
|
+
|
|
352
|
+
.thumbnail-item {
|
|
353
|
+
|
|
354
|
+
display: flex;
|
|
355
|
+
align-items: center;
|
|
356
|
+
justify-content: center;
|
|
357
|
+
|
|
358
|
+
aspect-ratio: 3 / 4;
|
|
359
|
+
min-inline-size: 120px;
|
|
360
|
+
outline: 1px solid light-dark(#f00, #00f);
|
|
361
|
+
border-radius: 4px;
|
|
362
|
+
|
|
363
|
+
background-color: light-dark(#f00, #00f);
|
|
364
|
+
|
|
365
|
+
&:nth-child(odd) {
|
|
366
|
+
background-color: light-dark(#00f, #f00);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
.thumbnail-item_inner {}
|
|
371
|
+
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
}
|
|
378
|
+
</style>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srcdev-nuxt-components",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.1",
|
|
5
5
|
"main": "nuxt.config.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"clean": "rm -rf .nuxt && rm -rf .output && rm -rf .playground/.nuxt && rm -rf .playground/.output",
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@iconify-json/akar-icons": "1.2.2",
|
|
29
29
|
"@iconify-json/bitcoin-icons": "1.2.2",
|
|
30
|
-
"@nuxt/eslint": "1.
|
|
31
|
-
"@nuxt/eslint-config": "1.
|
|
32
|
-
"@nuxt/icon": "1.
|
|
30
|
+
"@nuxt/eslint": "1.5.2",
|
|
31
|
+
"@nuxt/eslint-config": "1.5.2",
|
|
32
|
+
"@nuxt/icon": "1.15.0",
|
|
33
33
|
"@nuxt/image": "1.10.0",
|
|
34
|
+
"@oddbird/css-anchor-positioning": "0.6.1",
|
|
35
|
+
"@vueuse/core": "13.5.0",
|
|
34
36
|
"happy-dom": "16.8.1",
|
|
35
|
-
"nuxt": "3.17.
|
|
37
|
+
"nuxt": "3.17.6",
|
|
36
38
|
"release-it": "18.1.2",
|
|
37
39
|
"typescript": "5.8.3"
|
|
38
40
|
},
|
|
39
41
|
"dependencies": {
|
|
40
|
-
"@oddbird/css-anchor-positioning": "0.6.0",
|
|
41
|
-
"@vueuse/core": "13.1.0",
|
|
42
42
|
"focus-trap-vue": "4.0.3",
|
|
43
43
|
"modern-normalize": "3.0.1"
|
|
44
44
|
},
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CarouselBasicItem {
|
|
2
|
+
id: number | string;
|
|
3
|
+
url: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CarouselModifiedItem {
|
|
8
|
+
id: number | string;
|
|
9
|
+
url: string;
|
|
10
|
+
alt: string;
|
|
11
|
+
order: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ICarouselBasic {
|
|
15
|
+
items: CarouselBasicItem[] | CarouselModifiedItem[];
|
|
16
|
+
total: number;
|
|
17
|
+
skip: number;
|
|
18
|
+
limit: number;
|
|
19
|
+
}
|