Skip to content

Commit dfeae32

Browse files
authored
Add a "News Ticker" to homepage (#2766)
* Add configurable NewsTicker component at the top of the page * Change to sliding animations The direction will depend on whether we selected a "next" or "previous" dot. When it reaches the last item, it will fade away and slide back to the first item * Make the layout responsive * Improve the links hover effect * Update colors to match style in both modes * Pause when hovering * Adjust vertical padding * Tweak news ticker content * Disable announcement bar * Update release notes text (third item) for news ticker * Fix text color * Test adding background color * Update content for Oct. 29th launch * Fix icon position * Update news ticker content
1 parent 9530593 commit dfeae32

File tree

6 files changed

+222
-0
lines changed

6 files changed

+222
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { useState, useEffect } from 'react';
2+
import styles from './styles.module.scss';
3+
import clsx from 'clsx';
4+
import Icon from '@site/src/components/Icon';
5+
6+
const NewsTicker = ({ newsItems, interval = 5000 }) => {
7+
const [currentIndex, setCurrentIndex] = useState(0);
8+
const [isFading, setIsFading] = useState(false);
9+
const [isPaused, setIsPaused] = useState(false);
10+
11+
const handleNavigation = (newIndex) => {
12+
const isWrapping =
13+
(currentIndex === newsItems.length - 1 && newIndex === 0) ||
14+
(currentIndex === 0 && newIndex === newsItems.length - 1);
15+
16+
if (isWrapping) {
17+
setIsFading(true);
18+
setTimeout(() => {
19+
setCurrentIndex(newIndex);
20+
setIsFading(false);
21+
}, 250); // Half of the fade animation duration
22+
} else {
23+
setCurrentIndex(newIndex);
24+
}
25+
};
26+
27+
// Auto-rotation effect
28+
useEffect(() => {
29+
if (isPaused || newsItems.length <= 1) {
30+
return; // Do nothing if paused or not enough items
31+
}
32+
33+
const timer = setInterval(() => {
34+
handleNavigation((currentIndex + 1) % newsItems.length);
35+
}, interval);
36+
37+
return () => clearInterval(timer);
38+
}, [isPaused, currentIndex, newsItems, interval]);
39+
40+
if (!newsItems || newsItems.length === 0) {
41+
return null;
42+
}
43+
44+
return (
45+
<div
46+
className={clsx(styles.newsTicker, 'news-ticker-container', isFading && styles.isFading)}
47+
onMouseEnter={() => setIsPaused(true)}
48+
onMouseLeave={() => setIsPaused(false)}
49+
>
50+
<div className={styles.contentViewport}>
51+
<div className={styles.filmStrip} style={{ transform: `translateX(-${currentIndex * 100}%)` }}>
52+
{newsItems.map((item, index) => (
53+
<div key={index} className={styles.newsItem}>
54+
<a href={item.link} target="_blank" rel="noopener noreferrer">
55+
{item.icon && <Icon name={item.icon} classes="ph-fill" />}
56+
<span>{item.text}</span>
57+
</a>
58+
</div>
59+
))}
60+
</div>
61+
</div>
62+
63+
{newsItems.length > 1 && (
64+
<div className={styles.dotsContainer}>
65+
{newsItems.map((_, index) => (
66+
<button
67+
key={index}
68+
className={clsx(styles.dot, { [styles.active]: currentIndex === index })}
69+
onClick={() => handleNavigation(index)}
70+
aria-label={`Go to news item ${index + 1}`}
71+
/>
72+
))}
73+
</div>
74+
)}
75+
</div>
76+
);
77+
};
78+
79+
export default NewsTicker;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
.newsTicker {
2+
background-color: var(--ifm-background-color);
3+
padding: 1.25rem 2rem;
4+
display: flex;
5+
align-items: center;
6+
width: 100%;
7+
gap: 1.5rem;
8+
transition: opacity 0.25s ease-in-out;
9+
10+
&.isFading {
11+
opacity: 0;
12+
}
13+
}
14+
15+
.contentViewport {
16+
flex-grow: 1;
17+
min-width: 0;
18+
overflow: hidden;
19+
}
20+
21+
.filmStrip {
22+
display: flex;
23+
transition: transform 0.5s ease-in-out;
24+
}
25+
26+
.newsItem {
27+
flex: 0 0 100%;
28+
display: flex;
29+
justify-content: center;
30+
align-items: center;
31+
32+
a {
33+
display: flex;
34+
align-items: center;
35+
gap: 0.75rem;
36+
font-weight: 500;
37+
color: var(--ifm-font-color-base);
38+
text-decoration: none;
39+
white-space: normal;
40+
text-align: left;
41+
transition: color 0.3s ease;
42+
43+
i {
44+
font-size: 1.25rem;
45+
color: var(--ifm-font-color-base);
46+
flex-shrink: 0;
47+
transition: color 0.3s ease;
48+
}
49+
50+
&:hover,
51+
&:hover i {
52+
color: var(--news-ticker-hover-color);
53+
}
54+
}
55+
}
56+
57+
.dotsContainer {
58+
display: flex;
59+
gap: 0.5rem;
60+
flex-shrink: 0;
61+
}
62+
63+
.dot {
64+
width: 8px;
65+
height: 8px;
66+
border-radius: 50%;
67+
background-color: var(--ifm-color-emphasis-300);
68+
border: none;
69+
padding: 0;
70+
cursor: pointer;
71+
transition: background-color 0.3s ease;
72+
73+
&.active {
74+
background-color: var(--news-ticker-active-dot-color);
75+
}
76+
}
77+
78+
// Mobile-specific adjustments
79+
@media (max-width: 996px) {
80+
.newsTicker {
81+
padding-left: 1rem;
82+
padding-right: 1rem;
83+
gap: 1rem;
84+
}
85+
86+
.newsItem {
87+
justify-content: flex-start;
88+
}
89+
}

docusaurus/src/pages/home/Home.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
HeroTitle,
1919
HomepageAIButton,
2020
} from '../../components';
21+
import NewsTicker from '@site/src/components/NewsTicker';
2122
import Icon from '../../components/Icon';
2223
import content from './_home.content';
2324

@@ -136,6 +137,7 @@ export default function PageHome() {
136137
}}
137138
/>
138139
<main className={clsx(styles.home, isDarkTheme ? styles.homeDark : '')}>
140+
<NewsTicker newsItems={content.newsTicker} />
139141
<Hero id="homeHero" className={isDarkTheme ? styles.heroDark : ''}>
140142
<Container>
141143
<HeroTitle className="heroTitle">

docusaurus/src/pages/home/_home.content.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ export default {
55
title: 'Strapi 5 Docs',
66
description: 'Get set up in minutes to build any project in hours instead of weeks.',
77
},
8+
newsTicker: [
9+
{
10+
icon: 'heart',
11+
text: 'Docs Contribution Program: Improve the docs and get rewarded!',
12+
link: 'https://strapi.notion.site/Documentation-Contribution-Program-1d08f359807480d480fdde68bb7a5a71?pvs=74'
13+
},
14+
// {
15+
// icon: 'hand-heart',
16+
// text: 'Feedback wanted: Take our documentation survey',
17+
// link: 'https://docs.google.com/forms/d/e/1FAIpQLSceA85j2C5iGcAhDUPkezy408JI4jjUQgS5SfEm8obnqTY2Hw/viewform',
18+
// },
19+
{
20+
icon: 'newspaper-clipping',
21+
text: 'New Docs release - See what\'s new this week',
22+
link: '/release-notes#6100',
23+
},
24+
],
825
carousel: [
926
{
1027
title: "Can't wait to use Strapi?",

docusaurus/src/scss/__index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
@use 'markdown.scss';
4444
@use 'medium-zoom.scss';
4545
@use 'navbar.scss';
46+
@use 'news-ticker.scss';
4647
@use 'pagination-nav.scss';
4748
@use 'release-notes.scss';
4849
@use 'search.scss';
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.news-ticker-container {
2+
--news-ticker-hover-color: var(--strapi-primary-600);
3+
--news-ticker-active-dot-color: var(--strapi-primary-600);
4+
5+
background-color: var(--strapi-neutral-100) !important;
6+
7+
a {
8+
color: var(--strapi-neutral-600) !important;
9+
10+
&:hover {
11+
color: var(--news-ticker-hover-color) !important;
12+
}
13+
14+
i.strapi-icons {
15+
position: relative;
16+
top: -2px;
17+
}
18+
}
19+
}
20+
21+
[data-theme='dark'] .news-ticker-container {
22+
--news-ticker-hover-color: var(--strapi-secondary-500);
23+
--news-ticker-active-dot-color: var(--strapi-secondary-500);
24+
25+
background-color: var(--strapi-neutral-100) !important;
26+
27+
a {
28+
color: var(--strapi-neutral-400) !important;
29+
30+
&:hover {
31+
color: var(--news-ticker-hover-color) !important;
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)