Skip to content

Commit 64bdba6

Browse files
Feat/logo carousel (#24)
Co-authored-by: maxnoelp2 <max@surfgreen.dev>
1 parent d486bec commit 64bdba6

File tree

7 files changed

+302
-12
lines changed

7 files changed

+302
-12
lines changed

backend/static/js/logo_carousel.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
*
3+
* Swiper config
4+
*/
5+
6+
(function() {
7+
'use strict';
8+
9+
function initSwiper() {
10+
11+
if (typeof Swiper === 'undefined') {
12+
console.error('Swiper library not loaded!');
13+
return;
14+
}
15+
16+
try {
17+
const swiperElements = document.querySelectorAll('.logo-swiper-carousel');
18+
19+
if (swiperElements.length === 0) {
20+
return;
21+
}
22+
23+
swiperElements.forEach(elem => {
24+
const loop = (elem.dataset.loop) === 'true';
25+
const spaceBetweenSlides = parseInt(elem.dataset.spaceBetweenSlides, 10) || 20;
26+
const delay = parseInt(elem.dataset.delay) || 3000;
27+
const autoplay = (elem.dataset.autoplay === 'true')
28+
? {delay: delay, disableOnInteraction: false}
29+
: false;
30+
31+
new Swiper(`.logoSwiper-${elem.dataset.instanceId}`, {
32+
slidesPerView: 1, // Mobile
33+
spaceBetween: spaceBetweenSlides,
34+
loop: loop,
35+
keyboard: {
36+
enabled: true,
37+
onlyInViewport: true,
38+
},
39+
autoplay: autoplay,
40+
navigation: {
41+
nextEl: `.btn-next-${elem.dataset.instanceId}`,
42+
prevEl: `.btn-prev-${elem.dataset.instanceId}`,
43+
},
44+
breakpoints: {
45+
768: {
46+
slidesPerView: 3, // Tablet
47+
spaceBetween: spaceBetweenSlides
48+
},
49+
1024: {
50+
slidesPerView: 4, // Desktop
51+
spaceBetween: spaceBetweenSlides
52+
}
53+
}
54+
});
55+
});
56+
} catch (e) {
57+
console.error("Error initializing Swiper instance", e);
58+
}
59+
}
60+
61+
if (document.readyState === 'loading') {
62+
document.addEventListener('DOMContentLoaded', initSwiper);
63+
} else {
64+
initSwiper();
65+
}
66+
67+
})();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
.carousel-container {
2+
position: relative;
3+
margin: 0 auto;
4+
padding: 0 60px;
5+
min-height: 0;
6+
display: flex;
7+
align-items: center;
8+
}
9+
.logo-swiper-carousel {
10+
width: 100%;
11+
height: auto;
12+
}
13+
.swiper-slide.logo-item {
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
transition: all 0.4s ease;
18+
height: 100px;
19+
img {
20+
max-height: 100px;
21+
width: 178px;
22+
max-width: 100%;
23+
}
24+
a {
25+
cursor: pointer;
26+
}
27+
@media (max-width: 768px) {
28+
margin-top: 0;
29+
}
30+
}
31+
.nav-btn {
32+
position: absolute;
33+
top: 50%;
34+
transform: translateY(-50%);
35+
width: 32px;
36+
height: 32px;
37+
border-radius: 50%;
38+
display: flex;
39+
align-items: center;
40+
justify-content: center;
41+
cursor: pointer;
42+
z-index: 10;
43+
transition: all 0.3s ease;
44+
padding: 0;
45+
svg {
46+
width: 20px;
47+
height: 20px;
48+
stroke: currentColor;
49+
stroke-width: 2;
50+
fill: none;
51+
}
52+
&.btn-outline-dark:hover,
53+
.btn-outline-dark:hover & {
54+
color: $white;
55+
}
56+
&:hover {
57+
color: $dark;
58+
}
59+
&:focus,
60+
&:focus-visible {
61+
outline: 2px solid $dark;
62+
outline-offset: 2px;
63+
}
64+
}
65+
66+
.btn-prev {
67+
left: 0;
68+
}
69+
.btn-next {
70+
right: 0;
71+
}
72+
.swiper-button-next {
73+
&:after {
74+
display: none;
75+
}
76+
}
77+
.swiper-button-prev {
78+
&:after {
79+
display: none;
80+
}
81+
}
82+
.grayscale {
83+
filter: grayscale(100%);
84+
opacity: 0.6;
85+
transition: all 0.4s ease;
86+
&:hover {
87+
filter: grayscale(0%);
88+
opacity: 1;
89+
}
90+
}

backend/static/scss/main.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@import "./background-grid";
1313
@import "./hero";
1414
@import "./cta_panel";
15+
@import "./logo_carousel";
1516
@import "./timeline";
1617
@import "./features";
1718
@import "./footer";

cms_theme/cms_components.py

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from django import forms
22
from django.conf import settings
3+
from django.core.validators import MinValueValidator
34
from django.utils.translation import gettext_lazy as _
45
from djangocms_frontend.component_base import CMSFrontendComponent
56
from djangocms_frontend.component_pool import components
6-
from djangocms_frontend.fields import HTMLFormField
7-
8-
from djangocms_frontend.fields import ColoredButtonGroup
7+
from djangocms_frontend.fields import ColoredButtonGroup, HTMLFormField
98

109

1110
@components.register
@@ -75,7 +74,7 @@ class Meta:
7574
required=False,
7675
initial="default",
7776
)
78-
77+
7978

8079
@components.register
8180
class TimelineContainer(CMSFrontendComponent):
@@ -173,8 +172,8 @@ class Meta:
173172
required=False,
174173
initial="flex-column",
175174
)
176-
177-
175+
176+
178177
@components.register
179178
class CTAPanel(CMSFrontendComponent):
180179
"""CTAPanel component with background grid option"""
@@ -209,9 +208,68 @@ class Meta:
209208
choices=[
210209
("start", _("Start")),
211210
("center", _("Center (Default)")),
212-
("end", _("End"))
211+
("end", _("End")),
213212
],
214213
initial="center",
215-
help_text=_("Controls horizontal alignment of all content")
214+
help_text=_("Controls horizontal alignment of all content"),
215+
)
216+
217+
218+
@components.register
219+
class LogoCarousel(CMSFrontendComponent):
220+
"""LogoCarousel component"""
221+
222+
class Meta:
223+
name = _("Logo Carousel")
224+
render_template = "carousel/logo_carousel.html"
225+
allow_children = True
226+
child_classes = [
227+
"HeadingPlugin",
228+
"CarouselItemPlugin",
229+
]
230+
mixins = ["Background", "Spacing", "Attributes"]
231+
232+
loop = forms.BooleanField(
233+
label=_("Loop Carousel"),
234+
required=False,
235+
initial=False,
236+
help_text=_(
237+
"Turn on to make the slides loop continuously from the last slide back to the first."
238+
),
239+
)
240+
241+
space_between_slides = forms.IntegerField(
242+
label=_("Space Between Slides"),
243+
required=False,
244+
initial=20,
245+
validators=[MinValueValidator(0)],
246+
help_text=_("Set the space (in pixels) between each slide in the carousel."),
247+
)
248+
249+
autoplay = forms.BooleanField(
250+
label=_("AutoPlay"),
251+
required=False,
252+
initial=True,
253+
help_text=_(
254+
"Turn on to make the slides move automatically without manual navigation."
255+
),
216256
)
217257

258+
delay = forms.IntegerField(
259+
label=_("Autoplay delay"),
260+
required=False,
261+
initial=3000,
262+
validators=[MinValueValidator(500)],
263+
help_text=_(
264+
"Set the time (in milliseconds) each slide stays visible before moving to the next one."
265+
),
266+
)
267+
268+
btn_color = forms.ChoiceField(
269+
label=_("Button Color"),
270+
choices=settings.DJANGOCMS_FRONTEND_COLOR_STYLE_CHOICES,
271+
required=False,
272+
initial="primary",
273+
widget=ColoredButtonGroup(attrs={"class": "flex-wrap"}),
274+
help_text=_("Color for the carousel button."),
275+
)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!-- cms_theme/templates/carousel/logo_carousel.html -->
2+
{% load cms_tags frontend sekizai_tags %}
3+
<section class="logo-section {{ instance.get_classes }}"
4+
{{ instance.get_attributes }}>
5+
{% with plugins=instance.child_plugin_instances %}
6+
<div class="container">
7+
{% for plugin in plugins %}
8+
{% if plugin.plugin_type == "HeadingPlugin" %}
9+
{% render_plugin plugin %}
10+
{% endif %}
11+
{% endfor %}
12+
<div class="carousel-container mx-6">
13+
<div class="swiper logo-swiper-carousel logoSwiper-{{ instance.id }}"
14+
data-instance-id="{{ instance.id }}"
15+
data-loop="{% if instance.loop %}true{% else %}false{% endif %}"
16+
data-space-between-slides="{{ instance.space_between_slides }}"
17+
data-autoplay="{% if instance.autoplay %}true{% else %}false{% endif %}"
18+
data-delay="{{ instance.delay }}">
19+
<div class="swiper-wrapper">
20+
{% for plugin in plugins %}
21+
{% if plugin.plugin_type == "CarouselItemPlugin" %}
22+
{% render_plugin plugin %}
23+
{% endif %}
24+
{% endfor %}
25+
</div>
26+
</div>
27+
<button class="nav-btn btn-prev btn-prev-{{ instance.id }} btn btn-{{ instance.background_context }} btn-outline-{{ instance.btn_color }}"
28+
id="prevBtn-{{ instance.id }}"
29+
aria-label="Previous slide">
30+
<i class="bi bi-chevron-left"></i>
31+
</button>
32+
<button class="nav-btn btn-next btn-next-{{ instance.id }} btn btn-outline-{{ instance.btn_color }}"
33+
id="nextBtn-{{ instance.id }}"
34+
aria-label="Next slide">
35+
<i class="ps-1 bi bi-chevron-right"></i>
36+
</button>
37+
</div>
38+
</div>
39+
{% endwith %}
40+
</section>

cms_theme/templates/cms_theme/base.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@
55
<meta charset="UTF-8" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<link rel="stylesheet" href="{% static 'css/main.css' %}">
8-
8+
<link href="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css"
9+
rel="stylesheet">
10+
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css"
11+
rel="stylesheet">
912
{% render_block "css" %}
1013
</head>
1114
<body>
12-
{% cms_toolbar%}
15+
{% cms_toolbar %}
1316
{% block content %}
14-
{% placeholder "content" %}
17+
{% placeholder "content" %}
1518
{% endblock %}
1619
<script src="{% static 'js/counter.js' %}"></script>
1720
<script src="{% static 'js/bootstrap.js' %}"></script>
1821
<script src="{% static 'js/features.js' %}"></script>
1922
{% render_block "js" %}
2023
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
24+
<script src="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js"></script>
25+
<script src="{% static 'js/logo_carousel.js' %}"></script>
2126
<script>hljs.highlightAll();</script>
2227
</body>
23-
</html>
28+
</html>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!-- cms_theme/templates/cms_theme/cms_components/carousel_item.html -->
2+
{% load frontend cms_component thumbnail sekizai_tags djangocms_link_tags %}
3+
{% cms_component "CarouselItem" name=_("Carousel Item") parent_classes="LogoCarouselPlugin" %}
4+
{% field "logo" ImageFormField required=True %}
5+
{% field "logo_link" LinkFormField required=False %}
6+
{% field "grayscale" forms.BooleanField required=False label=_("Grayscale") help_text=_("Apply grayscale filter to the logo") %}
7+
<div class="swiper-slide logo-item{% if grayscale %} grayscale{% endif %}">
8+
{% with image=logo|get_related_object %}
9+
{% thumbnail image "400x0" crop subject_location=image.subject_location as sm %}
10+
{% thumbnail image "800x0" crop subject_location=image.subject_location as md %}
11+
{% thumbnail image "1200x0" crop subject_location=image.subject_location as lg %}
12+
{% if logo_link %}
13+
<a href="{{ logo_link|to_url }}"
14+
target="_blank"
15+
rel="noopener noreferrer">
16+
{% endif %}
17+
<img src="{{ md.url }}"
18+
srcset="{{ sm.url }} 400w,
19+
{{ md.url }} 800w,
20+
{{ lg.url }} 1200w"
21+
sizes="(max-width: 768px) 90vw,
22+
(max-width: 1200px) 70vw,
23+
1200px"
24+
alt="{{ image.alt }}"
25+
title="{{ image.title }}"
26+
loading="lazy">
27+
{% if logo_link %}</a>{% endif %}
28+
{% endwith %}
29+
</div>

0 commit comments

Comments
 (0)