about : extract team carousel into TeamCarousel component (swiper)
Replace inline carousel logic with a dedicated TeamCarousel.svelte component. Uses SwiperJS for mobile peek (1.4 slides), tablet (2) and desktop (4) layouts, with touch swipe, pagination dots and prev/next buttons. related to #53 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6ec32dd82a
commit
4ccbad9663
4 changed files with 238 additions and 238 deletions
214
src/components/ui/TeamCarousel.svelte
Normal file
214
src/components/ui/TeamCarousel.svelte
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { t } from '@i18n'
|
||||
import Swiper from 'swiper'
|
||||
import { Navigation, Pagination } from 'swiper/modules'
|
||||
import 'swiper/css'
|
||||
import 'swiper/css/pagination'
|
||||
|
||||
let { members = [] } = $props()
|
||||
|
||||
let swiperContainerEl = $state(null)
|
||||
let swiper = null
|
||||
|
||||
onMount(() => {
|
||||
// Petit délai pour laisser le layout se stabiliser
|
||||
// (le composant est monté même quand le slide est hors écran)
|
||||
const timer = setTimeout(() => {
|
||||
swiper = new Swiper(swiperContainerEl, {
|
||||
modules: [Navigation, Pagination],
|
||||
|
||||
slidesPerView: 1.4,
|
||||
centeredSlides: true,
|
||||
spaceBetween: 16,
|
||||
|
||||
pagination: {
|
||||
el: swiperContainerEl.parentElement.querySelector('.team-pagination'),
|
||||
clickable: true,
|
||||
},
|
||||
navigation: {
|
||||
prevEl: swiperContainerEl.parentElement.querySelector('.team-prev'),
|
||||
nextEl: swiperContainerEl.parentElement.querySelector('.team-next'),
|
||||
},
|
||||
|
||||
breakpoints: {
|
||||
701: {
|
||||
slidesPerView: 2,
|
||||
centeredSlides: false,
|
||||
spaceBetween: 20,
|
||||
},
|
||||
913: {
|
||||
slidesPerView: 4,
|
||||
centeredSlides: false,
|
||||
spaceBetween: 30,
|
||||
},
|
||||
},
|
||||
})
|
||||
}, 100)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
swiper?.destroy(true, true)
|
||||
swiper = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="team-carousel">
|
||||
<div class="swiper" bind:this={swiperContainerEl}>
|
||||
<div class="swiper-wrapper">
|
||||
{#each members as member}
|
||||
<div class="swiper-slide">
|
||||
{#if member.link}
|
||||
<a href={member.link} target="_blank" rel="noopener noreferrer" class="team-member">
|
||||
{#if member.photo}
|
||||
<img src={member.photo} alt={member.name} class="team-member-image" draggable="false" />
|
||||
{/if}
|
||||
<h4 class="team-member-name">{member.name}</h4>
|
||||
<p class="team-member-title">{member.role}</p>
|
||||
</a>
|
||||
{:else}
|
||||
<div class="team-member">
|
||||
{#if member.photo}
|
||||
<img src={member.photo} alt={member.name} class="team-member-image" draggable="false" />
|
||||
{/if}
|
||||
<h4 class="team-member-name">{member.name}</h4>
|
||||
<p class="team-member-title">{member.role}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="team-nav">
|
||||
<button class="team-prev nav-button">{t('prev_slide')}</button>
|
||||
<div class="team-pagination"></div>
|
||||
<button class="team-next nav-button">{t('next_slide')}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.team-carousel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Swiper container */
|
||||
.swiper {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
overflow: hidden;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* --- Items --- */
|
||||
.team-member {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
a.team-member:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.team-member-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: contain;
|
||||
margin-bottom: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
a.team-member .team-member-image:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.team-member-name {
|
||||
font-family: "Danzza Bold", sans-serif;
|
||||
font-size: var(--font-size-paragraph-small);
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.team-member-title {
|
||||
font-family: "Danzza", sans-serif;
|
||||
font-size: var(--font-size-caption);
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* --- Nav buttons --- */
|
||||
.team-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
color: #04fea0;
|
||||
font-family: "Danzza", sans-serif;
|
||||
font-size: var(--font-size-paragraph-small);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
text-transform: uppercase;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-button:hover:not(:disabled) {
|
||||
transform: scale(1.05);
|
||||
background-color: rgba(4, 254, 160, 0.1);
|
||||
}
|
||||
|
||||
.nav-button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* --- Pagination (Swiper-injected, needs :global) --- */
|
||||
:global(.team-pagination) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
:global(.team-pagination .swiper-pagination-bullet) {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
:global(.team-pagination .swiper-pagination-bullet-active) {
|
||||
background: #04fea0;
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
:global(.team-pagination) {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:global(.team-pagination .swiper-pagination-bullet) {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue