@paris-ias/list 1.0.82 → 1.0.83

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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@paris-ias/list",
3
3
  "configKey": "list",
4
- "version": "1.0.82",
4
+ "version": "1.0.83",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.0",
7
7
  "unbuild": "3.5.0"
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <v-card
3
+ v-ripple
4
+ class="event-sliding-item cursor-pointer"
5
+ elevation="2"
6
+ height="280"
7
+ @click="$router.push(localePath('/activities/events/' + item.slug[locale]))"
8
+ >
9
+ <v-card-text class="pa-4 d-flex flex-column h-100">
10
+ <!-- Date section -->
11
+ <div class="text-overline font-weight-black mb-2 text-primary">
12
+ {{
13
+ new Date(item.start).toLocaleDateString(locale, {
14
+ year: "numeric",
15
+ month: "short",
16
+ day: "numeric",
17
+ })
18
+ }}
19
+ </div>
20
+
21
+ <!-- Category badge -->
22
+ <v-chip
23
+ v-if="item.category"
24
+ size="small"
25
+ variant="outlined"
26
+ color="primary"
27
+ class="mb-3 align-self-start"
28
+ >
29
+ {{ $t("list.filters.events.category." + item.category) }}
30
+ </v-chip>
31
+
32
+ <!-- Event title -->
33
+ <h3 class="text-h6 font-weight-bold mb-3 line-clamp-3">
34
+ {{ item.name }}
35
+ </h3>
36
+
37
+ <!-- Event description if available -->
38
+ <p v-if="item.description" class="text-body-2 text-grey-darken-2 line-clamp-4 flex-grow-1">
39
+ {{ item.description }}
40
+ </p>
41
+
42
+ <!-- Location if available -->
43
+ <div v-if="item.location" class="mt-auto pt-2">
44
+ <v-icon size="small" class="mr-1">mdi-map-marker</v-icon>
45
+ <span class="text-caption text-grey-darken-1">{{ item.location }}</span>
46
+ </div>
47
+ </v-card-text>
48
+
49
+ <!-- Hover overlay -->
50
+ <v-overlay
51
+ v-model="isHovered"
52
+ contained
53
+ opacity="0.1"
54
+ class="d-flex align-center justify-center"
55
+ >
56
+ <v-icon size="large" color="primary">mdi-eye</v-icon>
57
+ </v-overlay>
58
+ </v-card>
59
+ </template>
60
+
61
+ <script setup>
62
+ import { ref } from "vue";
63
+ import { useLocalePath, useI18n } from "#imports";
64
+ const { locale } = useI18n();
65
+ const localePath = useLocalePath();
66
+ const props = defineProps({
67
+ item: {
68
+ type: Object,
69
+ required: true
70
+ },
71
+ index: {
72
+ type: Number,
73
+ required: true
74
+ }
75
+ });
76
+ const { item, index } = props;
77
+ const isHovered = ref(false);
78
+ </script>
79
+
80
+ <style scoped>
81
+ .event-sliding-item{border-radius:12px;overflow:hidden;transition:all .3s cubic-bezier(.25,.8,.25,1)}.event-sliding-item:hover{box-shadow:0 8px 25px rgba(0,0,0,.15)!important;transform:translateY(-4px)}.line-clamp-3{-webkit-line-clamp:3;line-clamp:3}.line-clamp-3,.line-clamp-4{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.line-clamp-4{-webkit-line-clamp:4;line-clamp:4}
82
+ </style>
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <div class="slider-container">
3
+ <!-- Header with counter and navigation -->
4
+ <div class="slider-header">
5
+ <div class="slide-counter">
6
+ {{ activeSlideIndex + 1 }}/{{ items.length }}
7
+ </div>
8
+ <div class="navigation-controls">
9
+ <button
10
+ :disabled="!canGoBack"
11
+ class="nav-button nav-prev"
12
+ @click="goToSlide(activeSlideIndex - 1)"
13
+ >
14
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
15
+ <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
16
+ </svg>
17
+ </button>
18
+ <button
19
+ :disabled="!canGoForward"
20
+ class="nav-button nav-next"
21
+ @click="goToSlide(activeSlideIndex + 1)"
22
+ >
23
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
24
+ <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
25
+ </svg>
26
+ </button>
27
+ </div>
28
+ </div>
29
+
30
+ <!-- Slider container -->
31
+ <div ref="sliderRef" class="slider-viewport" @scroll="handleScroll">
32
+ <div
33
+ class="slider-track"
34
+ :style="{ transform: `translateX(${offsetX}px)` }"
35
+ >
36
+ <div
37
+ v-for="(item, index) in items"
38
+ :key="index"
39
+ class="slide-item"
40
+ :class="{
41
+ 'slide-visible': isSlideVisible(index),
42
+ 'slide-entering': isSlideEntering(index),
43
+ 'slide-exiting': isSlideExiting(index),
44
+ }"
45
+ :style="getSlideStyle(index)"
46
+ >
47
+ <component :is="itemComponent" :item="item" :index="index" />
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
+ <script setup>
55
+ import {
56
+ ref,
57
+ computed,
58
+ onMounted,
59
+ onBeforeUnmount,
60
+ nextTick,
61
+ watch
62
+ } from "vue";
63
+ import { useRootStore } from "../../../stores/root";
64
+ import { capitalize } from "../../../composables/useUtils";
65
+ import { useNuxtApp } from "#imports";
66
+ import { useI18n } from "vue-i18n";
67
+ defineOptions({ name: "ListOrganismsSlider" });
68
+ const { $stores } = useNuxtApp();
69
+ const { locale } = useI18n();
70
+ const rootStore = useRootStore();
71
+ const props = defineProps({
72
+ type: {
73
+ type: String,
74
+ required: true
75
+ }
76
+ });
77
+ const items = computed(() => $stores[props.type].items);
78
+ const sliderRef = ref(null);
79
+ const activeSlideIndex = ref(0);
80
+ const offsetX = ref(0);
81
+ const containerWidth = ref(0);
82
+ const slideWidth = ref(0);
83
+ const slidesPerView = ref(1);
84
+ const itemComponent = computed(() => {
85
+ return capitalize(props.type) + "SlidingItem";
86
+ });
87
+ const totalSlides = computed(() => items.value.length);
88
+ const canGoBack = computed(() => activeSlideIndex.value > 0);
89
+ const canGoForward = computed(() => {
90
+ const maxIndex = Math.max(0, totalSlides.value - slidesPerView.value);
91
+ return activeSlideIndex.value < maxIndex;
92
+ });
93
+ const visibleSlideIndices = computed(() => {
94
+ const start = activeSlideIndex.value;
95
+ const end = Math.min(start + slidesPerView.value, totalSlides.value);
96
+ return Array.from({ length: end - start }, (_, i) => start + i);
97
+ });
98
+ const calculateDimensions = () => {
99
+ if (!sliderRef.value) return;
100
+ containerWidth.value = sliderRef.value.offsetWidth;
101
+ const breakpoints = {
102
+ 320: { slidesPerView: 1.2, spacing: 20 },
103
+ 640: { slidesPerView: 2, spacing: 24 },
104
+ 960: { slidesPerView: 2.5, spacing: 28 },
105
+ 1280: { slidesPerView: 3, spacing: 32 },
106
+ 1600: { slidesPerView: 3.5, spacing: 36 }
107
+ };
108
+ const currentBreakpoint = Object.entries(breakpoints).reverse().find(([width]) => window.innerWidth >= Number.parseInt(width));
109
+ const config = currentBreakpoint ? currentBreakpoint[1] : breakpoints[320];
110
+ slidesPerView.value = Math.floor(config.slidesPerView);
111
+ slideWidth.value = (containerWidth.value - config.spacing * (config.slidesPerView - 1)) / config.slidesPerView;
112
+ };
113
+ const isSlideVisible = (index) => {
114
+ return visibleSlideIndices.value.includes(index);
115
+ };
116
+ const isSlideEntering = (index) => {
117
+ const rightmostVisible = Math.max(...visibleSlideIndices.value);
118
+ return index === rightmostVisible && index > activeSlideIndex.value;
119
+ };
120
+ const isSlideExiting = (index) => {
121
+ const leftmostVisible = Math.min(...visibleSlideIndices.value);
122
+ return index === leftmostVisible && index < activeSlideIndex.value;
123
+ };
124
+ const getSlideStyle = (index) => {
125
+ const baseStyles = {
126
+ width: `${slideWidth.value}px`,
127
+ opacity: isSlideVisible(index) ? 1 : 0,
128
+ transform: `translateX(${index * (slideWidth.value + 24)}px)`,
129
+ transition: "all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)"
130
+ };
131
+ if (isSlideEntering(index)) {
132
+ baseStyles.transform += " translateX(20px)";
133
+ baseStyles.opacity = 0.8;
134
+ }
135
+ if (isSlideExiting(index)) {
136
+ baseStyles.transform += " translateX(-20px)";
137
+ baseStyles.opacity = 0.6;
138
+ }
139
+ return baseStyles;
140
+ };
141
+ const goToSlide = (targetIndex) => {
142
+ const maxIndex = Math.max(0, totalSlides.value - slidesPerView.value);
143
+ const newIndex = Math.max(0, Math.min(targetIndex, maxIndex));
144
+ if (newIndex === activeSlideIndex.value) return;
145
+ activeSlideIndex.value = newIndex;
146
+ updateSliderPosition();
147
+ };
148
+ const updateSliderPosition = () => {
149
+ const newOffset = -(activeSlideIndex.value * (slideWidth.value + 24));
150
+ const visualOffset = activeSlideIndex.value > 0 ? -40 : 0;
151
+ offsetX.value = newOffset + visualOffset;
152
+ };
153
+ const handleScroll = () => {
154
+ };
155
+ const handleResize = () => {
156
+ calculateDimensions();
157
+ updateSliderPosition();
158
+ };
159
+ onMounted(async () => {
160
+ await nextTick();
161
+ calculateDimensions();
162
+ updateSliderPosition();
163
+ window.addEventListener("resize", handleResize);
164
+ });
165
+ onBeforeUnmount(() => {
166
+ window.removeEventListener("resize", handleResize);
167
+ rootStore.resetState(props.type);
168
+ });
169
+ watch(
170
+ () => items.value,
171
+ () => {
172
+ nextTick(() => {
173
+ calculateDimensions();
174
+ updateSliderPosition();
175
+ });
176
+ },
177
+ { deep: true }
178
+ );
179
+ try {
180
+ await rootStore.update(props.type, locale.value);
181
+ } catch (error) {
182
+ console.log("error fetching update list: ", error);
183
+ }
184
+ </script>
185
+
186
+ <style scoped>
187
+ .slider-container{overflow:hidden;position:relative;width:100%}.slider-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:24px;padding:0 20px}.slide-counter{color:#666;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-weight:500}.navigation-controls{display:flex;gap:8px}.nav-button{align-items:center;background:#fff;border:1px solid #e0e0e0;border-radius:4px;color:#333;cursor:pointer;display:flex;height:40px;justify-content:center;transition:all .2s ease;width:40px}.nav-button:hover:not(:disabled){background:#f5f5f5;border-color:#d0d0d0}.nav-button:disabled{color:#999;cursor:not-allowed;opacity:.4}.slider-viewport{overflow:hidden;padding:0 20px;position:relative;width:100%}.slider-track{display:flex;transition:transform .4s cubic-bezier(.25,.46,.45,.94);will-change:transform}.slide-item{flex-shrink:0;margin-right:24px;transform-origin:center;will-change:transform,opacity}.slide-item:last-child{margin-right:0}.slide-entering{animation:slideInFromRight .4s cubic-bezier(.25,.46,.45,.94)}.slide-exiting{animation:slideOutToLeft .4s cubic-bezier(.25,.46,.45,.94)}@keyframes slideInFromRight{0%{opacity:0;transform:translateX(40px)}to{opacity:1;transform:translateX(0)}}@keyframes slideOutToLeft{0%{opacity:1;transform:translateX(0)}to{opacity:.6;transform:translateX(-40px)}}@media (max-width:640px){.slider-header{margin-bottom:20px;padding:0 16px}.slider-viewport{padding:0 16px}.slide-item{margin-right:16px}}@media (max-width:480px){.slide-counter{font-size:12px}.nav-button{height:36px;width:36px}.nav-button svg{height:20px;width:20px}}
188
+ </style>
@@ -16,6 +16,7 @@ query listNews(
16
16
  date
17
17
  description
18
18
  featured
19
+ summary
19
20
  image {
20
21
  alt
21
22
  backgroundColor
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "license": "AGPL-3.0-only",
3
3
  "main": "./dist/module.mjs",
4
- "version": "1.0.82",
4
+ "version": "1.0.83",
5
5
  "name": "@paris-ias/list",
6
6
  "repository": {
7
7
  "url": "git+https://github.com/IEA-Paris/list.git",