srcdev-nuxt-components 6.1.2 → 6.1.4

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 (50) hide show
  1. package/app/components/carousel-basic/CarouselBasic.vue +86 -62
  2. package/app/components/display-details/DisplayDetailsCore.vue +26 -207
  3. package/app/components/expanding-panel/ExpandingPanel.vue +5 -10
  4. package/app/components/marquee-scroller/MarqueeScroller.vue +122 -0
  5. package/app/components/masonry-grid-ordered/MasonryGridOrdered.vue +44 -41
  6. package/app/components/responsive-header/NavigationItems.vue +98 -48
  7. package/app/components/responsive-header/ResponsiveHeader.vue +1 -1
  8. package/nuxt.config.ts +1 -1
  9. package/package.json +3 -3
  10. package/app/assets/styles/extends-layer/srcdev-components/components/_display-prompt-core.css +0 -72
  11. package/app/assets/styles/extends-layer/srcdev-components/components/index.css +0 -1
  12. package/app/assets/styles/extends-layer/srcdev-components/index.css +0 -2
  13. package/app/assets/styles/extends-layer/srcdev-components/setup/index.css +0 -1
  14. package/app/assets/styles/extends-layer/srcdev-components/setup/themes/_error.css +0 -15
  15. package/app/assets/styles/extends-layer/srcdev-components/setup/themes/_info.css +0 -15
  16. package/app/assets/styles/extends-layer/srcdev-components/setup/themes/_secondary.css +0 -15
  17. package/app/assets/styles/extends-layer/srcdev-components/setup/themes/_success.css +0 -15
  18. package/app/assets/styles/extends-layer/srcdev-components/setup/themes/_warning.css +0 -15
  19. package/app/assets/styles/extends-layer/srcdev-components/setup/themes/index.css +0 -5
  20. package/app/assets/styles/main.css +0 -2
  21. package/app/assets/styles/setup/_head.css +0 -36
  22. package/app/assets/styles/setup/a11y/_utils.css +0 -20
  23. package/app/assets/styles/setup/a11y/_variables.css +0 -8
  24. package/app/assets/styles/setup/a11y/index.css +0 -2
  25. package/app/assets/styles/setup/index.css +0 -5
  26. package/app/assets/styles/setup/typography/index.css +0 -2
  27. package/app/assets/styles/setup/typography/utility-classes/_generic-font-classes.css +0 -217
  28. package/app/assets/styles/setup/typography/utility-classes/_generic-font-variation-settings.css +0 -29
  29. package/app/assets/styles/setup/typography/utility-classes/_generic-font-weights.css +0 -39
  30. package/app/assets/styles/setup/typography/utility-classes/index.css +0 -3
  31. package/app/assets/styles/setup/typography/vars/_colors.css +0 -14
  32. package/app/assets/styles/setup/typography/vars/_reponsive-font-sizes.css +0 -12
  33. package/app/assets/styles/setup/typography/vars/index.css +0 -2
  34. package/app/assets/styles/setup/utility-classes/_fluid-spacing.css +0 -13
  35. package/app/assets/styles/setup/utility-classes/_margin.css +0 -334
  36. package/app/assets/styles/setup/utility-classes/_padding.css +0 -308
  37. package/app/assets/styles/setup/utility-classes/animations/_auto-rotate.css +0 -13
  38. package/app/assets/styles/setup/utility-classes/animations/_entry-exit-blur.css +0 -16
  39. package/app/assets/styles/setup/utility-classes/animations/_entry-slide-in.css +0 -15
  40. package/app/assets/styles/setup/utility-classes/animations/_entry-zoom-reveal.css +0 -15
  41. package/app/assets/styles/setup/utility-classes/animations/index.css +0 -4
  42. package/app/assets/styles/setup/utility-classes/index.css +0 -4
  43. package/app/assets/styles/setup/variables/colors/_blue.css +0 -15
  44. package/app/assets/styles/setup/variables/colors/_gray.css +0 -16
  45. package/app/assets/styles/setup/variables/colors/_green.css +0 -15
  46. package/app/assets/styles/setup/variables/colors/_orange.css +0 -15
  47. package/app/assets/styles/setup/variables/colors/_red.css +0 -15
  48. package/app/assets/styles/setup/variables/colors/_yellow.css +0 -15
  49. package/app/assets/styles/setup/variables/colors/index.css +0 -6
  50. package/app/assets/styles/setup/variables/index.css +0 -1
@@ -1,11 +1,30 @@
1
1
  <template>
2
- <section class="carousel-basic" :class="[elementClasses]" ref="carouselWrapperRef" role="region" aria-label="Image carousel">
2
+ <section
3
+ class="carousel-basic"
4
+ :class="[elementClasses]"
5
+ ref="carouselWrapperRef"
6
+ role="region"
7
+ aria-label="Image carousel"
8
+ >
3
9
  <!-- Screen reader announcement for current item -->
4
10
  <div aria-live="polite" aria-atomic="true" class="sr-only">Item {{ currentIndex + 1 }} of {{ itemCount }}</div>
5
11
 
6
12
  <LayoutRow tag="div" variant="full-width" :style-class-passthrough="['mbe-20']">
7
- <div tabindex="0" class="item-container" :class="{ 'allow-overflow': allowCarouselOverflow }" ref="carouselContainerRef" role="group" aria-label="Carousel items">
8
- <div v-for="(item, index) in carouselDataIds" :key="index" class="item" ref="carouselItems" :aria-current="currentIndex === index ? 'true' : 'false'">
13
+ <div
14
+ tabindex="0"
15
+ class="item-container"
16
+ :class="{ 'allow-overflow': allowCarouselOverflow }"
17
+ ref="carouselContainerRef"
18
+ role="group"
19
+ aria-label="Carousel items"
20
+ >
21
+ <div
22
+ v-for="(item, index) in carouselDataIds"
23
+ :key="index"
24
+ class="item"
25
+ ref="carouselItems"
26
+ :aria-current="currentIndex === index ? 'true' : 'false'"
27
+ >
9
28
  <slot :name="item"></slot>
10
29
  </div>
11
30
  </div>
@@ -24,7 +43,12 @@
24
43
  <div class="markers-container">
25
44
  <ul class="markers-list">
26
45
  <li v-for="index in itemCount" :key="index" class="markers-item">
27
- <button @click.prevent="jumpToFrame(index - 1)" class="btn-marker" :class="[{ active: currentIndex === index - 1 }]" :aria-label="`Jump to item ${Math.floor(index + 1)}`"></button>
46
+ <button
47
+ @click.prevent="jumpToFrame(index - 1)"
48
+ class="btn-marker"
49
+ :class="[{ active: currentIndex === index - 1 }]"
50
+ :aria-label="`Jump to item ${Math.floor(index + 1)}`"
51
+ ></button>
28
52
  </li>
29
53
  </ul>
30
54
  </div>
@@ -42,7 +66,7 @@
42
66
  </template>
43
67
 
44
68
  <script setup lang="ts">
45
- import { useEventListener, useResizeObserver, useSwipe } from '@vueuse/core';
69
+ import { useEventListener, useResizeObserver, useSwipe } from "@vueuse/core"
46
70
 
47
71
  const props = defineProps({
48
72
  carouselDataIds: {
@@ -65,108 +89,108 @@ const props = defineProps({
65
89
  type: Boolean,
66
90
  default: false,
67
91
  },
68
- });
92
+ })
69
93
 
70
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
94
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
71
95
 
72
- const carouselWrapperRef = ref<HTMLDivElement | null>(null);
73
- const carouselContainerRef = ref<HTMLDivElement | null>(null);
74
- const carouselItemsRef = useTemplateRef<HTMLDivElement[]>('carouselItems');
75
- const controlsContainerRef = ref<HTMLDivElement | null>(null);
76
- const carouselInitComplete = ref(false);
96
+ const carouselWrapperRef = ref<HTMLDivElement | null>(null)
97
+ const carouselContainerRef = ref<HTMLDivElement | null>(null)
98
+ const carouselItemsRef = useTemplateRef<HTMLDivElement[]>("carouselItems")
99
+ const controlsContainerRef = ref<HTMLDivElement | null>(null)
100
+ const carouselInitComplete = ref(false)
77
101
 
78
- const currentIndex = ref(0);
79
- const itemCount = ref(props.carouselDataIds.length);
80
- const offset = ref(0);
81
- const transitionSpeedStr = props.transitionSpeed + 'ms';
102
+ const currentIndex = ref(0)
103
+ const itemCount = ref(props.carouselDataIds.length)
104
+ const offset = ref(0)
105
+ const transitionSpeedStr = props.transitionSpeed + "ms"
82
106
  const itemTransform = computed(() => {
83
- return `translateX(calc(${offset.value} * (${itemWidth.value} + var(--_carousel-item-track-gap))))`;
84
- });
107
+ return `translateX(calc(${offset.value} * (${itemWidth.value} + var(--_carousel-item-track-gap))))`
108
+ })
85
109
 
86
- const itemWidth = ref('0px');
110
+ const itemWidth = ref("0px")
87
111
 
88
112
  const actionPrevious = () => {
89
113
  if (props.returnToStart && currentIndex.value === 0) {
90
- offset.value = -itemCount.value;
91
- doAction();
114
+ offset.value = -itemCount.value
115
+ doAction()
92
116
  }
93
117
 
94
118
  if (offset.value >= 0) {
95
- return;
119
+ return
96
120
  }
97
121
 
98
- offset.value = Math.min(offset.value + 1);
99
- doAction();
100
- };
122
+ offset.value = Math.min(offset.value + 1)
123
+ doAction()
124
+ }
101
125
 
102
126
  const actionNext = () => {
103
127
  if (props.returnToStart && offset.value <= -1 * (itemCount.value - 1)) {
104
- offset.value = 0;
105
- doAction();
106
- return;
128
+ offset.value = 0
129
+ doAction()
130
+ return
107
131
  }
108
132
 
109
133
  if (offset.value <= -1 * (itemCount.value - 1)) {
110
- return;
134
+ return
111
135
  }
112
136
 
113
- offset.value = Math.min(offset.value - 1);
114
- doAction();
115
- };
137
+ offset.value = Math.min(offset.value - 1)
138
+ doAction()
139
+ }
116
140
 
117
141
  const doAction = () => {
118
- currentIndex.value = Math.abs(offset.value);
119
- };
142
+ currentIndex.value = Math.abs(offset.value)
143
+ }
120
144
 
121
145
  const jumpToFrame = (index: number) => {
122
146
  if (index >= 0 && index < itemCount.value) {
123
- offset.value = -index;
124
- doAction();
147
+ offset.value = -index
148
+ doAction()
125
149
  }
126
- };
150
+ }
127
151
 
128
152
  const initialSetup = () => {
129
153
  if (carouselItemsRef?.value && carouselItemsRef.value.length > 0 && carouselItemsRef.value[0]) {
130
- itemWidth.value = carouselItemsRef.value[0].offsetWidth + 'px';
154
+ itemWidth.value = carouselItemsRef.value[0].offsetWidth + "px"
131
155
  }
132
156
 
133
- carouselInitComplete.value = true;
134
- };
157
+ carouselInitComplete.value = true
158
+ }
135
159
 
136
160
  const { direction } = useSwipe(carouselContainerRef, {
137
161
  passive: false,
138
162
  onSwipeEnd() {
139
- if (direction.value === 'left') {
140
- actionNext();
141
- } else if (direction.value === 'right') {
142
- actionPrevious();
163
+ if (direction.value === "left") {
164
+ actionNext()
165
+ } else if (direction.value === "right") {
166
+ actionPrevious()
143
167
  }
144
168
  },
145
- });
169
+ })
146
170
 
147
- useEventListener(carouselContainerRef, 'keydown', (event: KeyboardEvent) => {
148
- if (event.key === 'ArrowLeft') {
149
- actionPrevious();
150
- } else if (event.key === 'ArrowRight') {
151
- actionNext();
171
+ useEventListener(carouselContainerRef, "keydown", (event: KeyboardEvent) => {
172
+ if (event.key === "ArrowLeft") {
173
+ actionPrevious()
174
+ } else if (event.key === "ArrowRight") {
175
+ actionNext()
152
176
  }
153
- });
177
+ })
154
178
 
155
- useEventListener(controlsContainerRef, 'keydown', (event: KeyboardEvent) => {
156
- if (event.key === 'ArrowLeft') {
157
- actionPrevious();
158
- } else if (event.key === 'ArrowRight') {
159
- actionNext();
179
+ useEventListener(controlsContainerRef, "keydown", (event: KeyboardEvent) => {
180
+ if (event.key === "ArrowLeft") {
181
+ actionPrevious()
182
+ } else if (event.key === "ArrowRight") {
183
+ actionNext()
160
184
  }
161
- });
185
+ })
162
186
 
163
187
  useResizeObserver(carouselWrapperRef, async () => {
164
- initialSetup();
165
- });
188
+ initialSetup()
189
+ })
166
190
 
167
191
  onMounted(() => {
168
- initialSetup();
169
- });
192
+ initialSetup()
193
+ })
170
194
  </script>
171
195
 
172
196
  <style lang="css">
@@ -203,7 +227,7 @@ onMounted(() => {
203
227
  position: relative;
204
228
 
205
229
  &::before {
206
- content: '';
230
+ content: "";
207
231
  position: absolute;
208
232
  height: 2px;
209
233
  background-color: #fff;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <details @click.prevent="handleClick()" :name="name" class="display-details" :class="[elementClasses]" ref="detailsRef">
2
+ <details :name="name" class="display-details" :class="[elementClasses]" ref="detailsRef">
3
3
  <summary class="display-details-summary" :id="triggerId" :aria-controls="contentId" ref="summaryRef">
4
4
  <span class="label">
5
5
  <slot name="summary"></slot>
@@ -9,175 +9,13 @@
9
9
  </slot>
10
10
  </summary>
11
11
  <div class="display-details-content" :aria-labelledby="triggerId" :id="contentId" role="region" ref="contentRef">
12
- <slot name="content"></slot>
12
+ <div class="inner">
13
+ <slot name="details-content"></slot>
14
+ </div>
13
15
  </div>
14
16
  </details>
15
17
  </template>
16
18
 
17
- <script lang="ts">
18
- // Create a global store to track open details elements by name
19
- const openDetailsByName = reactive(new Map<string, HTMLDetailsElement>());
20
-
21
- export const useDetailsTransition = (
22
- detailsRef: Ref<HTMLDetailsElement | null>,
23
- summaryRef: Ref<HTMLElement | null>,
24
- contentRef: Ref<HTMLDivElement | null>,
25
- name: string,
26
- animationDuration: number
27
- ) => {
28
- // State
29
- const animation = ref<Animation | null>(null);
30
- const isClosing = ref(false);
31
- const isExpanding = ref(false);
32
-
33
- // Check if refs are available
34
- if (!detailsRef.value || !summaryRef.value || !contentRef.value) {
35
- console.warn('Details, summary, or content ref is null');
36
- return {
37
- clickAction: () => console.warn('Component not fully initialized'),
38
- };
39
- }
40
-
41
- const closeOtherDetailsWithSameName = () => {
42
- const currentDetails = detailsRef.value;
43
- if (!currentDetails || !name) return;
44
-
45
- // Get the currently open details with the same name
46
- const openDetails = openDetailsByName.get(name);
47
-
48
- // If there's an open details with the same name and it's not the current one, close it
49
- if (openDetails && openDetails !== currentDetails && openDetails.open) {
50
- // Simulate a click on the other details to close it with animation
51
- const otherSummary = openDetails.querySelector('summary');
52
- if (otherSummary) {
53
- otherSummary.click();
54
- } else {
55
- // Fallback: close directly without animation
56
- openDetails.open = false;
57
- }
58
- }
59
-
60
- // Update the map with the current details if it's open
61
- if (currentDetails.open) {
62
- openDetailsByName.set(name, currentDetails);
63
- } else {
64
- // If it's closed and was the one in the map, remove it
65
- if (openDetailsByName.get(name) === currentDetails) {
66
- openDetailsByName.delete(name);
67
- }
68
- }
69
- };
70
-
71
- const clickAction = () => {
72
- const details = detailsRef.value;
73
- const summary = summaryRef.value;
74
- const content = contentRef.value;
75
-
76
- if (!details || !summary || !content) return;
77
-
78
- // Add overflow hidden to avoid content jumping
79
- details.style.overflow = 'hidden';
80
-
81
- if (isClosing.value || !details.open) {
82
- // Close other details with the same name first
83
- closeOtherDetailsWithSameName();
84
-
85
- // Open the details
86
- details.open = true;
87
- isExpanding.value = true;
88
- isClosing.value = false;
89
-
90
- // Get the height of the content
91
- const detailsHeight = details.offsetHeight;
92
- const contentHeight = content.offsetHeight;
93
- const summaryHeight = summary.offsetHeight;
94
-
95
- const startHeight = `${detailsHeight - contentHeight}px`;
96
- const endHeight = `${summaryHeight + contentHeight}px`;
97
-
98
- // If there's an animation running, cancel it
99
- if (animation.value) {
100
- animation.value.cancel();
101
- }
102
-
103
- // Start animation
104
- animation.value = details.animate(
105
- {
106
- height: [startHeight, endHeight],
107
- },
108
- {
109
- duration: animationDuration,
110
- easing: 'linear',
111
- }
112
- );
113
-
114
- animation.value.onfinish = () => {
115
- // Animation finished - reset everything
116
- details.style.height = 'auto';
117
- details.style.overflow = '';
118
- isExpanding.value = false;
119
- animation.value = null;
120
-
121
- // Register this as the open details for this name
122
- openDetailsByName.set(name, details);
123
- };
124
-
125
- animation.value.oncancel = () => {
126
- isExpanding.value = false;
127
- };
128
- } else if (isExpanding.value || details.open) {
129
- // Close the details
130
- isClosing.value = true;
131
- isExpanding.value = false;
132
-
133
- // Get the height of the content
134
- const startHeight = `${details.offsetHeight}px`;
135
- const endHeight = `${details.offsetHeight - content.offsetHeight}px`;
136
-
137
- // If there's an animation running, cancel it
138
- if (animation.value) {
139
- animation.value.cancel();
140
- }
141
-
142
- // Start animation
143
- animation.value = details.animate(
144
- {
145
- height: [startHeight, endHeight],
146
- },
147
- {
148
- duration: animationDuration,
149
- easing: 'linear',
150
- }
151
- );
152
-
153
- animation.value.onfinish = () => {
154
- // Animation finished - reset everything
155
- details.open = false;
156
- details.style.height = 'auto';
157
- details.style.overflow = '';
158
- isClosing.value = false;
159
- animation.value = null;
160
-
161
- // Remove this from the open details map if it's there
162
- if (openDetailsByName.get(name) === details) {
163
- openDetailsByName.delete(name);
164
- }
165
- };
166
-
167
- animation.value.oncancel = () => {
168
- isClosing.value = false;
169
- };
170
- }
171
- };
172
-
173
- return {
174
- clickAction,
175
- isClosing,
176
- isExpanding,
177
- };
178
- };
179
- </script>
180
-
181
19
  <script setup lang="ts">
182
20
  const props = defineProps({
183
21
  name: {
@@ -188,58 +26,31 @@ const props = defineProps({
188
26
  type: String,
189
27
  required: true,
190
28
  },
191
- animationDuration: {
192
- type: Number,
193
- default: 400,
194
- },
195
29
  iconSize: {
196
30
  type: String,
197
- default: 'small',
31
+ default: "small",
198
32
  validator(value: string) {
199
- return ['small', 'medium', 'large'].includes(value);
33
+ return ["small", "medium", "large"].includes(value)
200
34
  },
201
35
  },
202
36
  styleClassPassthrough: {
203
37
  type: Array as PropType<string[]>,
204
38
  default: () => [],
205
39
  },
206
- });
207
-
208
- const triggerId = computed(() => `${props.id}-trigger`);
209
- const contentId = computed(() => `${props.id}-content`);
40
+ })
210
41
 
211
- const { elementClasses, resetElementClasses, updateElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
42
+ const triggerId = computed(() => `${props.id}-trigger`)
43
+ const contentId = computed(() => `${props.id}-content`)
212
44
 
213
- updateElementClasses([props.iconSize]);
45
+ const { elementClasses, resetElementClasses, updateElementClasses } = useStyleClassPassthrough(
46
+ props.styleClassPassthrough
47
+ )
214
48
 
215
- const detailsRef = ref<HTMLDetailsElement | null>(null);
216
- const summaryRef = ref<HTMLElement | null>(null);
217
- const contentRef = ref<HTMLDivElement | null>(null);
49
+ updateElementClasses([props.iconSize])
218
50
 
219
- // Initialize with dummy function that will be replaced when refs are available
220
- let clickAction = () => console.warn('Component not fully initialized');
221
-
222
- // Handle click with the current clickAction function
223
- const handleClick = () => {
224
- clickAction();
225
- };
226
-
227
- watch(
228
- () => props.styleClassPassthrough,
229
- () => {
230
- resetElementClasses(props.styleClassPassthrough);
231
- }
232
- );
233
-
234
- onMounted(() => {
235
- // Initialize the composable once the component is mounted and refs are available
236
- if (detailsRef.value && contentRef.value && summaryRef.value) {
237
- const details = useDetailsTransition(detailsRef, summaryRef, contentRef, props.name, props.animationDuration);
238
- clickAction = details.clickAction; // Assign the real click handler
239
- } else {
240
- console.error('Refs not available after mounting');
241
- }
242
- });
51
+ const detailsRef = useTemplateRef<HTMLDetailsElement>("detailsRef")
52
+ const summaryRef = useTemplateRef<HTMLElement | null>("summaryRef")
53
+ const contentRef = useTemplateRef<HTMLDivElement | null>("contentRef")
243
54
  </script>
244
55
 
245
56
  <style lang="css">
@@ -255,6 +66,10 @@ onMounted(() => {
255
66
  transform: scaleY(-1);
256
67
  }
257
68
  }
69
+ .display-details-content {
70
+ grid-template-rows: 1fr;
71
+ overflow: hidden;
72
+ }
258
73
  }
259
74
 
260
75
  .display-details-summary {
@@ -282,7 +97,6 @@ onMounted(() => {
282
97
  display: block;
283
98
 
284
99
  transform: scaleY(1);
285
- transition: transform 200ms;
286
100
 
287
101
  font-size: 1.2rem;
288
102
  &.medium {
@@ -295,7 +109,12 @@ onMounted(() => {
295
109
  }
296
110
 
297
111
  .display-details-content {
298
- /* Use an inner element for styling */
112
+ display: grid;
113
+ grid-template-rows: 0;
114
+
115
+ .inner {
116
+ overflow: hidden;
117
+ }
299
118
  }
300
119
  }
301
120
  </style>
@@ -57,17 +57,17 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
57
57
  align-items: center;
58
58
  justify-content: space-between;
59
59
  flex-direction: row;
60
- gap: 0;
60
+ gap: 1rem;
61
61
  list-style: none;
62
62
 
63
- padding-inline: 0.5rem;
63
+ padding-block: 0.5rem;
64
64
 
65
65
  &::-webkit-details-marker,
66
66
  &::marker {
67
67
  display: none;
68
68
  }
69
69
 
70
- overflow: clip;
70
+ overflow: hidden;
71
71
 
72
72
  .label-wrapper {
73
73
  }
@@ -77,8 +77,6 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
77
77
  justify-content: space-between;
78
78
 
79
79
  aspect-ratio: 1;
80
- outline: 1px solid var(--gray-3);
81
- padding: 1rem;
82
80
  overflow: hidden;
83
81
 
84
82
  .icon {
@@ -101,7 +99,7 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
101
99
  + .expanding-panel-content {
102
100
  grid-template-rows: 1fr;
103
101
  .inner {
104
- /* transform: scaleY(1); */
102
+ transition: all v-bind(animationDurationStr) ease-in-out;
105
103
  }
106
104
  }
107
105
  }
@@ -114,10 +112,7 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
114
112
 
115
113
  .inner {
116
114
  overflow: hidden;
117
- padding: 0.5rem;
118
-
119
- /* transform: scaleY(0); */
120
- /* transition: transform v-bind(animationDurationStr) ease-in-out; */
115
+ margin-top: 0;
121
116
  }
122
117
  }
123
118
  }
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <div v-if="displayComponent" class="marquee-scroller" :reverse>
3
+ <ul class="list">
4
+ <li v-for="item in marqueeData" :key="item.id" class="item" :style="{ '--position': item.id }">
5
+ <slot :name="item.id"></slot>
6
+ </li>
7
+ </ul>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ const props = defineProps({
13
+ animationRuntime: {
14
+ type: String,
15
+ default: "40s",
16
+ },
17
+ reverse: {
18
+ type: Boolean,
19
+ default: false,
20
+ },
21
+ marqueeData: {
22
+ type: Array as PropType<Array<{ id: number; content: string }>>,
23
+ default: () => [],
24
+ },
25
+ itemConfig: {
26
+ type: Object,
27
+ default: () => ({
28
+ width: "50px",
29
+ height: "50px",
30
+ quantity: 30,
31
+ }),
32
+ required: true,
33
+ },
34
+ })
35
+
36
+ const displayComponent = ref(false)
37
+
38
+ const height = computed(() => props.itemConfig.height)
39
+ const quantity = computed(() => props.itemConfig.quantity)
40
+ const width = computed(() => props.itemConfig.width)
41
+
42
+ const animationRuntimeNumber = computed(() => {
43
+ const [seconds] = props.animationRuntime.split("s")
44
+ return parseFloat(seconds ?? "0")
45
+ })
46
+
47
+ const animationDelay = computed(() => {
48
+ return Math.floor(animationRuntimeNumber.value * 1.25) + "s"
49
+ })
50
+
51
+ onMounted(() => {
52
+ console.log(`Mounted: quantity(${quantity.value}) | animationDelay(${animationDelay.value})`)
53
+ displayComponent.value = true
54
+ })
55
+ </script>
56
+
57
+ <style lang="css">
58
+ .marquee-scroller {
59
+ width: 100%;
60
+ height: v-bind(height);
61
+ overflow: hidden;
62
+ mask-image: linear-gradient(to right, transparent, #000 10% 90%, transparent);
63
+
64
+ &:hover .item {
65
+ animation-play-state: paused !important;
66
+ filter: grayscale(1);
67
+ }
68
+
69
+ &[reverse="true"] .item {
70
+ animation: reversePlay v-bind(animationRuntime) linear infinite;
71
+ }
72
+
73
+ .list {
74
+ display: flex;
75
+ width: 100%;
76
+ height: v-bind(height);
77
+ min-width: calc(v-bind(width) * v-bind(quantity));
78
+ position: relative;
79
+
80
+ .item {
81
+ width: v-bind(width);
82
+ height: v-bind(height);
83
+ display: grid;
84
+ place-items: center;
85
+ position: absolute;
86
+ aspect-ratio: 1 / 1;
87
+ left: 100%;
88
+ animation: autoRun v-bind(animationRuntime) linear infinite;
89
+ transition: filter 0.5s;
90
+ /* animation-delay: calc((50s / v-bind(quantity)) * (var(--position) - 1) - 50s) !important; */
91
+ animation-delay: calc(
92
+ (v-bind(animationDelay) / v-bind(quantity)) * (var(--position) - 1) - v-bind(animationDelay)
93
+ ) !important;
94
+
95
+ border: 1px solid light-dark(var(--gray-12), var(--gray-0));
96
+ border-radius: 4px;
97
+
98
+ &:hover {
99
+ filter: grayscale(0);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ @keyframes autoRun {
106
+ from {
107
+ left: 100%;
108
+ }
109
+ to {
110
+ left: calc(v-bind(width) * -1);
111
+ }
112
+ }
113
+
114
+ @keyframes reversePlay {
115
+ from {
116
+ left: calc(v-bind(width) * -1);
117
+ }
118
+ to {
119
+ left: 100%;
120
+ }
121
+ }
122
+ </style>