@ndla/ui 35.1.2 → 36.0.1
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/es/Embed/RelatedContentEmbed.js +2 -2
- package/es/Messages/MessageBox.js +9 -9
- package/es/NDLAFilm/FilmContentCard.js +21 -11
- package/es/NDLAFilm/FilmMovieList.js +2 -2
- package/es/NDLAFilm/FilmSlideshow.js +169 -335
- package/es/RelatedArticleList/RelatedArticleV2.js +27 -6
- package/es/Search/ToggleSearchButton.js +7 -2
- package/es/locale/messages-en.js +1 -0
- package/es/locale/messages-nb.js +1 -0
- package/es/locale/messages-nn.js +1 -0
- package/es/locale/messages-se.js +1 -0
- package/es/locale/messages-sma.js +1 -0
- package/lib/Embed/RelatedContentEmbed.js +2 -2
- package/lib/Messages/MessageBox.js +9 -9
- package/lib/NDLAFilm/FilmContentCard.d.ts +4 -2
- package/lib/NDLAFilm/FilmContentCard.js +21 -12
- package/lib/NDLAFilm/FilmMovieList.d.ts +1 -1
- package/lib/NDLAFilm/FilmMovieList.js +2 -2
- package/lib/NDLAFilm/FilmSlideshow.d.ts +11 -5
- package/lib/NDLAFilm/FilmSlideshow.js +169 -333
- package/lib/RelatedArticleList/RelatedArticleV2.d.ts +4 -3
- package/lib/RelatedArticleList/RelatedArticleV2.js +35 -14
- package/lib/Search/ToggleSearchButton.js +7 -2
- package/lib/locale/messages-en.d.ts +1 -0
- package/lib/locale/messages-en.js +1 -0
- package/lib/locale/messages-nb.d.ts +1 -0
- package/lib/locale/messages-nb.js +1 -0
- package/lib/locale/messages-nn.d.ts +1 -0
- package/lib/locale/messages-nn.js +1 -0
- package/lib/locale/messages-se.d.ts +1 -0
- package/lib/locale/messages-se.js +1 -0
- package/lib/locale/messages-sma.d.ts +1 -0
- package/lib/locale/messages-sma.js +1 -0
- package/package.json +5 -5
- package/src/Embed/AudioEmbed.stories.tsx +226 -0
- package/src/Embed/BrightcoveEmbed.stories.tsx +209 -0
- package/src/Embed/ConceptEmbed.stories.tsx +190 -0
- package/src/Embed/ImageEmbed.stories.tsx +106 -0
- package/src/Embed/RelatedContentEmbed.tsx +1 -1
- package/src/Messages/MessageBox.tsx +1 -1
- package/src/NDLAFilm/FilmContentCard.tsx +11 -9
- package/src/NDLAFilm/FilmMovieList.tsx +2 -2
- package/src/NDLAFilm/FilmSlideshow.tsx +178 -387
- package/src/RelatedArticleList/RelatedArticleV2.tsx +24 -7
- package/src/Search/ToggleSearchButton.tsx +5 -1
- package/src/locale/messages-en.ts +1 -0
- package/src/locale/messages-nb.ts +1 -0
- package/src/locale/messages-nn.ts +1 -0
- package/src/locale/messages-se.ts +1 -0
- package/src/locale/messages-sma.ts +1 -0
|
@@ -1,436 +1,227 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) 2023-present, NDLA.
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the GPLv3 license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import React, { useCallback,
|
|
10
|
-
import { SwipeDirections, SwipeEventData, useSwipeable } from 'react-swipeable';
|
|
9
|
+
import React, { useCallback, useState } from 'react';
|
|
11
10
|
import styled from '@emotion/styled';
|
|
12
|
-
import {
|
|
13
|
-
import { breakpoints,
|
|
11
|
+
import { Carousel, CarouselAutosize } from '@ndla/carousel';
|
|
12
|
+
import { breakpoints, colors, misc, mq, spacing, spacingUnit } from '@ndla/core';
|
|
14
13
|
import SafeLink from '@ndla/safelink';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import SlideshowIndicator from './SlideshowIndicator';
|
|
14
|
+
import { IconButtonV2 } from '@ndla/button';
|
|
15
|
+
import { ChevronLeft, ChevronRight } from '@ndla/icons/common';
|
|
16
|
+
import FilmContentCard from './FilmContentCard';
|
|
19
17
|
import { MovieType } from './types';
|
|
20
18
|
|
|
19
|
+
export const slideshowBreakpoints: {
|
|
20
|
+
until?: keyof typeof breakpoints;
|
|
21
|
+
columnsPrSlide: number;
|
|
22
|
+
distanceBetweenItems: number;
|
|
23
|
+
arrowOffset: number;
|
|
24
|
+
margin?: number;
|
|
25
|
+
maxColumnWidth?: number;
|
|
26
|
+
}[] = [
|
|
27
|
+
{
|
|
28
|
+
until: 'mobileWide',
|
|
29
|
+
columnsPrSlide: 2,
|
|
30
|
+
distanceBetweenItems: spacingUnit / 2,
|
|
31
|
+
margin: spacingUnit,
|
|
32
|
+
arrowOffset: 13,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
until: 'tabletWide',
|
|
36
|
+
columnsPrSlide: 3,
|
|
37
|
+
distanceBetweenItems: spacingUnit / 2,
|
|
38
|
+
margin: spacingUnit,
|
|
39
|
+
arrowOffset: 13,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
until: 'desktop',
|
|
43
|
+
columnsPrSlide: 3,
|
|
44
|
+
distanceBetweenItems: spacingUnit,
|
|
45
|
+
margin: spacingUnit * 2,
|
|
46
|
+
arrowOffset: 0,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
until: 'wide',
|
|
50
|
+
columnsPrSlide: 3,
|
|
51
|
+
distanceBetweenItems: spacingUnit,
|
|
52
|
+
margin: spacingUnit * 2,
|
|
53
|
+
arrowOffset: 0,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
until: 'ultraWide',
|
|
57
|
+
columnsPrSlide: 3,
|
|
58
|
+
distanceBetweenItems: spacingUnit,
|
|
59
|
+
margin: spacingUnit * 3.5,
|
|
60
|
+
arrowOffset: 0,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
columnsPrSlide: 3,
|
|
64
|
+
distanceBetweenItems: spacingUnit,
|
|
65
|
+
margin: spacingUnit * 3.5,
|
|
66
|
+
arrowOffset: 0,
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
21
70
|
interface Props {
|
|
22
|
-
autoSlide?: boolean;
|
|
23
|
-
randomStart?: boolean;
|
|
24
71
|
slideshow: MovieType[];
|
|
25
|
-
slideInterval?: number;
|
|
26
72
|
}
|
|
27
73
|
|
|
28
|
-
const
|
|
29
|
-
margin: 0 auto;
|
|
30
|
-
display: flex;
|
|
31
|
-
justify-content: flex-start;
|
|
32
|
-
align-items: flex-end;
|
|
74
|
+
const SlideInfoWrapper = styled.div`
|
|
33
75
|
position: absolute;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
width:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
${mq.range({ from: breakpoints.ultraWide })} {
|
|
50
|
-
height: 36vw;
|
|
76
|
+
color: ${colors.white};
|
|
77
|
+
max-width: 40%;
|
|
78
|
+
min-width: 40%;
|
|
79
|
+
top: 40%;
|
|
80
|
+
right: 5%;
|
|
81
|
+
${mq.range({ until: breakpoints.desktop })} {
|
|
82
|
+
top: 30%;
|
|
83
|
+
max-width: 60%;
|
|
84
|
+
min-width: 60%;
|
|
85
|
+
}
|
|
86
|
+
${mq.range({ until: breakpoints.tablet })} {
|
|
87
|
+
max-width: 90%;
|
|
88
|
+
min-width: 90%;
|
|
89
|
+
left: 5%;
|
|
51
90
|
}
|
|
52
91
|
`;
|
|
53
92
|
|
|
54
|
-
const
|
|
55
|
-
|
|
93
|
+
const StyledSafeLink = styled(SafeLink)`
|
|
94
|
+
position: relative;
|
|
95
|
+
display: block;
|
|
56
96
|
box-shadow: none;
|
|
57
97
|
`;
|
|
58
98
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/* aspect ratios */
|
|
67
|
-
${mq.range({ from: breakpoints.mobileWide })} {
|
|
68
|
-
height: 100vw;
|
|
69
|
-
}
|
|
70
|
-
${mq.range({ from: breakpoints.tablet })} {
|
|
71
|
-
height: 75vw;
|
|
72
|
-
}
|
|
73
|
-
${mq.range({ from: breakpoints.desktop })} {
|
|
74
|
-
height: 55vw;
|
|
75
|
-
}
|
|
76
|
-
${mq.range({ from: breakpoints.wide })} {
|
|
77
|
-
height: 40vw;
|
|
78
|
-
}
|
|
79
|
-
${mq.range({ from: breakpoints.ultraWide })} {
|
|
80
|
-
height: 36vw;
|
|
81
|
-
}
|
|
82
|
-
background-color: '#222';
|
|
83
|
-
background-size: cover;
|
|
84
|
-
background-position-x: center;
|
|
85
|
-
background-position-y: center;
|
|
86
|
-
border: 0;
|
|
87
|
-
position: ${(props) => (props.fadeOver ? 'absolute' : 'relative')};
|
|
88
|
-
animation: ${(props) => props.fadeOver && 'fadeIn 400ms ease'};
|
|
89
|
-
z-index: ${(props) => props.fadeOver && 1};
|
|
90
|
-
&:before {
|
|
91
|
-
content: '';
|
|
92
|
-
opacity: 0.4;
|
|
93
|
-
background: #091a2a;
|
|
94
|
-
top: 0;
|
|
95
|
-
left: 0;
|
|
96
|
-
bottom: 0;
|
|
97
|
-
right: 0;
|
|
98
|
-
position: absolute;
|
|
99
|
-
z-index: 1;
|
|
99
|
+
const InfoWrapper = styled.div`
|
|
100
|
+
padding: ${spacing.normal};
|
|
101
|
+
border-radius: ${misc.borderRadius};
|
|
102
|
+
border: 0.5px solid ${colors.brand.primary};
|
|
103
|
+
background-color: rgba(11, 29, 45, 0.8);
|
|
104
|
+
h3 {
|
|
105
|
+
margin: 0px;
|
|
100
106
|
}
|
|
101
107
|
`;
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const shouldForwardProp = (p: string) => p !== 'out';
|
|
108
|
-
|
|
109
|
-
const SlideshowLink = styled(SafeLink, { shouldForwardProp })<SlideshowLinkProps>`
|
|
110
|
-
display: flex;
|
|
111
|
-
box-shadow: none;
|
|
112
|
-
transition: all 400ms ease;
|
|
113
|
-
opacity: ${(props) => props.out && 0};
|
|
114
|
-
animation: ${(props) => !props.out && 'fadeInBottomFixed 600ms ease'};
|
|
115
|
-
${mq.range({ from: breakpoints.mobileWide })} {
|
|
116
|
-
padding-bottom: ${spacing.medium};
|
|
117
|
-
}
|
|
118
|
-
${mq.range({ from: breakpoints.tablet })} {
|
|
119
|
-
padding-bottom: ${spacing.large};
|
|
120
|
-
}
|
|
121
|
-
${mq.range({ from: breakpoints.desktop })} {
|
|
122
|
-
padding-bottom: ${spacingUnit * 3}px;
|
|
123
|
-
}
|
|
124
|
-
&:hover {
|
|
125
|
-
${() => SlideshowName} {
|
|
126
|
-
text-decoration: underline;
|
|
127
|
-
text-decoration-color: white;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
`;
|
|
131
|
-
|
|
132
|
-
const SlideshowWrapper = styled.section`
|
|
109
|
+
const StyledImg = styled.img`
|
|
110
|
+
max-height: 600px;
|
|
111
|
+
object-position: top;
|
|
133
112
|
width: 100%;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
transform: translate(0, 0);
|
|
139
|
-
}
|
|
113
|
+
aspect-ratio: 16/9;
|
|
114
|
+
${mq.range({ until: breakpoints.tablet })} {
|
|
115
|
+
min-height: 440px;
|
|
116
|
+
max-height: 440px;
|
|
140
117
|
}
|
|
118
|
+
object-fit: cover;
|
|
141
119
|
`;
|
|
142
120
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
flex-direction: column;
|
|
146
|
-
gap: ${spacing.small};
|
|
147
|
-
background-color: rgba(3, 23, 43, 0.7);
|
|
148
|
-
border-radius: 4px;
|
|
149
|
-
padding: ${spacing.medium} ${spacing.medium} ${spacing.medium} ${spacing.normal};
|
|
150
|
-
margin: 0 -20px;
|
|
151
|
-
width: 100vw;
|
|
152
|
-
${mq.range({ from: breakpoints.mobileWide })} {
|
|
153
|
-
margin: 0;
|
|
154
|
-
width: 100%;
|
|
155
|
-
padding: ${spacing.medium} ${spacingUnit * 2}px ${spacing.medium} ${spacing.normal};
|
|
156
|
-
}
|
|
157
|
-
`;
|
|
158
|
-
|
|
159
|
-
const SlideshowName = styled.p`
|
|
160
|
-
${fonts.sizes('22px', '30px')};
|
|
161
|
-
color: ${colors.white};
|
|
162
|
-
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
|
|
163
|
-
margin: 0;
|
|
164
|
-
font-weight: ${fonts.weight.semibold};
|
|
165
|
-
${mq.range({ from: breakpoints.mobileWide })} {
|
|
166
|
-
${fonts.sizes('26px', '30px')};
|
|
167
|
-
}
|
|
121
|
+
const CarouselContainer = styled.div`
|
|
122
|
+
margin-top: -50px;
|
|
168
123
|
${mq.range({ from: breakpoints.tablet })} {
|
|
169
|
-
|
|
124
|
+
margin-top: -70px;
|
|
170
125
|
}
|
|
171
126
|
${mq.range({ from: breakpoints.desktop })} {
|
|
172
|
-
|
|
127
|
+
margin-top: -150px;
|
|
173
128
|
}
|
|
174
129
|
`;
|
|
175
130
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
padding: 0;
|
|
180
|
-
${fonts.sizes('12px', '18px')};
|
|
181
|
-
${mq.range({ from: breakpoints.mobileWide })} {
|
|
182
|
-
${fonts.sizes('15px', '20px')};
|
|
183
|
-
}
|
|
184
|
-
${mq.range({ from: breakpoints.tablet })} {
|
|
185
|
-
${fonts.sizes('18px', '24px')};
|
|
186
|
-
}
|
|
187
|
-
${mq.range({ from: breakpoints.wide })} {
|
|
188
|
-
${fonts.sizes('20px', '32px')};
|
|
189
|
-
}
|
|
190
|
-
`;
|
|
131
|
+
interface StyledFilmContentCardProps {
|
|
132
|
+
current?: boolean;
|
|
133
|
+
}
|
|
191
134
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
margin-bottom: $spacing--large * 4;
|
|
195
|
-
display: flex;
|
|
196
|
-
align-items: center;
|
|
197
|
-
justify-content: center;
|
|
198
|
-
height: 40vw;
|
|
135
|
+
const SlideshowButton = styled(IconButtonV2)`
|
|
136
|
+
margin-top: ${spacing.normal};
|
|
199
137
|
`;
|
|
200
138
|
|
|
201
|
-
const
|
|
202
|
-
const defaultTransitionText = 'opacity 600ms ease';
|
|
203
|
-
|
|
204
|
-
const renderSlideItem = (slide: MovieType) => (
|
|
205
|
-
<SlideshowItem
|
|
206
|
-
key={slide.id}
|
|
207
|
-
role="img"
|
|
208
|
-
aria-label={(slide.metaImage && slide.metaImage.alt) || ''}
|
|
209
|
-
style={{
|
|
210
|
-
backgroundImage: `url(${(slide.metaImage && slide.metaImage.url) || ''})`,
|
|
211
|
-
}}
|
|
212
|
-
/>
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
const FilmSlideshow = ({ autoSlide = false, slideshow = [], slideInterval = 5000 }: Props) => {
|
|
216
|
-
const [swipeDistance, setSwipeDistance] = useState(0);
|
|
217
|
-
const [slideIndex, setSlideIndex] = useState(0);
|
|
218
|
-
const [slideIndexTarget, setSlideIndexTarget] = useState(0);
|
|
219
|
-
const [animationComplete, setAnimationComplete] = useState(true);
|
|
220
|
-
const [swipeDirection, setSwipeDirection] = useState<SwipeDirections | undefined>(undefined);
|
|
221
|
-
const slideRef = useRef<HTMLDivElement>(null);
|
|
222
|
-
const slideText = useRef<HTMLDivElement>(null);
|
|
223
|
-
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
224
|
-
|
|
225
|
-
const gotoSlide = useCallback((indexTarget: number, useAnimation = false) => {
|
|
226
|
-
setSwipeDistance(0);
|
|
227
|
-
if (timer.current) {
|
|
228
|
-
clearTimeout(timer.current);
|
|
229
|
-
}
|
|
230
|
-
setSlideIndexTarget(indexTarget);
|
|
231
|
-
setAnimationComplete(!useAnimation);
|
|
232
|
-
}, []);
|
|
233
|
-
|
|
234
|
-
const onChangedSlide = () => {
|
|
235
|
-
if (!animationComplete) {
|
|
236
|
-
if (slideRef.current) {
|
|
237
|
-
slideRef.current.style.transition = 'none';
|
|
238
|
-
slideRef.current.style.transform = `translateX(${slideIndexTarget * 100}vw))`;
|
|
239
|
-
}
|
|
139
|
+
const shouldForwardProp = (p: string) => p !== 'current';
|
|
240
140
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
slideRef.current.style.transition = 'none';
|
|
247
|
-
slideRef.current.style.transform = `translateX(${slideshow.length * 100}vw))`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
setSlideIndex(slideshow.length - 1);
|
|
251
|
-
setSlideIndexTarget(slideshow.length - 1);
|
|
252
|
-
setAnimationComplete(true);
|
|
253
|
-
} else if (slideIndexTarget === slideshow.length) {
|
|
254
|
-
if (slideRef.current) {
|
|
255
|
-
// Go to first slide for continuous loop
|
|
256
|
-
slideRef.current.style.transition = 'none';
|
|
257
|
-
slideRef.current.style.transform = `translateX(100vw))`;
|
|
258
|
-
}
|
|
259
|
-
setSlideIndex(0);
|
|
260
|
-
setSlideIndexTarget(0);
|
|
261
|
-
setAnimationComplete(true);
|
|
262
|
-
} else {
|
|
263
|
-
setAnimationComplete(true);
|
|
264
|
-
setSlideIndex(slideIndexTarget);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
const onSwipeEnd = () => {
|
|
269
|
-
setSwipeDirection(undefined);
|
|
270
|
-
let slide;
|
|
271
|
-
if (swipeDistance > 40) {
|
|
272
|
-
slide = -1;
|
|
273
|
-
} else if (swipeDistance < -40) {
|
|
274
|
-
slide = 1;
|
|
275
|
-
} else {
|
|
276
|
-
slide = 0;
|
|
277
|
-
}
|
|
278
|
-
if (slideRef.current && slideText.current) {
|
|
279
|
-
slideRef.current.style.transition = defaultTransitionSwipeEnd;
|
|
280
|
-
slideText.current.style.transition = defaultTransitionText;
|
|
281
|
-
slideText.current.style.opacity = '1';
|
|
282
|
-
}
|
|
283
|
-
setSwipeDistance(0);
|
|
284
|
-
|
|
285
|
-
initTimer();
|
|
286
|
-
|
|
287
|
-
if (slide !== 0) {
|
|
288
|
-
setSlideIndex(slideIndex + slide);
|
|
289
|
-
setSlideIndexTarget(slideIndex + slide);
|
|
290
|
-
} else {
|
|
291
|
-
// Reset transfrom
|
|
292
|
-
if (slideRef.current) {
|
|
293
|
-
slideRef.current.style.transform = getSlidePosition(slideIndex + slide);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
const onSwipe = (eventData: SwipeEventData) => {
|
|
299
|
-
if (eventData.initial) {
|
|
300
|
-
setSwipeDirection(eventData.dir);
|
|
301
|
-
}
|
|
302
|
-
const dir = eventData.initial ? eventData.dir : swipeDirection;
|
|
303
|
-
if (dir === 'Up' || dir === 'Down') {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
if (timer.current) {
|
|
307
|
-
clearTimeout(timer.current);
|
|
308
|
-
}
|
|
309
|
-
setSwipeDistance(eventData.deltaX);
|
|
310
|
-
if (slideRef && slideRef.current) {
|
|
311
|
-
slideRef.current.style.transition = 'none';
|
|
312
|
-
slideRef.current.style.transform = getSlidePosition(slideIndexTarget);
|
|
313
|
-
}
|
|
314
|
-
const opacityText = 1 - Math.min(100, Math.abs(swipeDistance)) / 100;
|
|
315
|
-
if (slideText && slideText.current) {
|
|
316
|
-
slideText.current.style.transition = 'none';
|
|
317
|
-
slideText.current.style.opacity = opacityText.toString();
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
const onTransitionEnd = () => {
|
|
322
|
-
const slideshowLength = slideshow.length;
|
|
323
|
-
if (slideIndex === -1) {
|
|
324
|
-
if (slideRef.current) {
|
|
325
|
-
slideRef.current.style.transition = 'none';
|
|
326
|
-
slideRef.current.style.transform = getSlidePosition(slideshowLength - 1);
|
|
327
|
-
}
|
|
328
|
-
setSlideIndex(slideshowLength - 1);
|
|
329
|
-
setSlideIndexTarget(slideshowLength - 1);
|
|
330
|
-
} else if (slideIndex >= slideshowLength) {
|
|
331
|
-
if (slideRef.current) {
|
|
332
|
-
slideRef.current.style.transition = 'none';
|
|
333
|
-
slideRef.current.style.transform = getSlidePosition(0);
|
|
334
|
-
}
|
|
335
|
-
setSlideIndex(0);
|
|
336
|
-
setSlideIndexTarget(0);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const getSlidePosition = (target: number) => {
|
|
341
|
-
if (swipeDistance !== 0) {
|
|
342
|
-
return `translateX(calc(${swipeDistance}px -
|
|
343
|
-
${(target + 1) * 100}vw))`;
|
|
344
|
-
}
|
|
345
|
-
return `translateX(-${(target + 1) * 100}vw)`;
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
const initTimer = useCallback(() => {
|
|
349
|
-
if (autoSlide) {
|
|
350
|
-
timer.current = setTimeout(() => {
|
|
351
|
-
gotoSlide(slideIndex + 1);
|
|
352
|
-
}, slideInterval);
|
|
353
|
-
}
|
|
354
|
-
}, [autoSlide, gotoSlide, slideInterval, slideIndex]);
|
|
141
|
+
const StyledFilmContentCard = styled(FilmContentCard, { shouldForwardProp })<StyledFilmContentCardProps>`
|
|
142
|
+
margin-bottom: 2%;
|
|
143
|
+
transform: ${(p) => (p.current ? 'translateY(0%)' : 'translateY(10%)')};
|
|
144
|
+
transition: all 200ms;
|
|
145
|
+
`;
|
|
355
146
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}, [initTimer]);
|
|
147
|
+
const FilmSlideshow = ({ slideshow }: Props) => {
|
|
148
|
+
const [currentSlide, setCurrentSlide] = useState(slideshow[0]);
|
|
359
149
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
150
|
+
return (
|
|
151
|
+
<CarouselAutosize breakpoints={slideshowBreakpoints} itemsLength={slideshow.length}>
|
|
152
|
+
{(autoSizedProps) => (
|
|
153
|
+
<section>
|
|
154
|
+
<StyledSafeLink to={currentSlide.path} tabIndex={-1} aria-hidden>
|
|
155
|
+
<StyledImg src={currentSlide.metaImage?.url ?? ''} alt={currentSlide.metaImage?.alt ?? ''} />
|
|
156
|
+
<SlideInfoWrapper>
|
|
157
|
+
<InfoWrapper>
|
|
158
|
+
<h3>{currentSlide.title}</h3>
|
|
159
|
+
<span id="currentMovieDescription">{currentSlide.metaDescription}</span>
|
|
160
|
+
</InfoWrapper>
|
|
161
|
+
</SlideInfoWrapper>
|
|
162
|
+
</StyledSafeLink>
|
|
163
|
+
<CarouselContainer>
|
|
164
|
+
<Carousel
|
|
165
|
+
leftButton={
|
|
166
|
+
<SlideshowButton aria-label={''}>
|
|
167
|
+
<ChevronLeft />
|
|
168
|
+
</SlideshowButton>
|
|
169
|
+
}
|
|
170
|
+
rightButton={
|
|
171
|
+
<SlideshowButton aria-label={''}>
|
|
172
|
+
<ChevronRight />
|
|
173
|
+
</SlideshowButton>
|
|
174
|
+
}
|
|
175
|
+
items={slideshow.map((movie) => (
|
|
176
|
+
<FilmCard
|
|
177
|
+
key={movie.id}
|
|
178
|
+
current={movie.id === currentSlide.id}
|
|
179
|
+
movie={movie}
|
|
180
|
+
columnWidth={autoSizedProps.columnWidth}
|
|
181
|
+
setCurrentSlide={() => setCurrentSlide(movie)}
|
|
182
|
+
/>
|
|
183
|
+
))}
|
|
184
|
+
{...autoSizedProps}
|
|
185
|
+
/>
|
|
186
|
+
</CarouselContainer>
|
|
187
|
+
</section>
|
|
188
|
+
)}
|
|
189
|
+
</CarouselAutosize>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
365
192
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
</div>
|
|
373
|
-
);
|
|
374
|
-
}
|
|
193
|
+
interface FilmCardProps {
|
|
194
|
+
setCurrentSlide: () => void;
|
|
195
|
+
movie: MovieType;
|
|
196
|
+
current: boolean;
|
|
197
|
+
columnWidth: number;
|
|
198
|
+
}
|
|
375
199
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (activeSlide < 0) {
|
|
379
|
-
activeSlide = slideshow.length - 1;
|
|
380
|
-
} else if (activeSlide >= slideshow.length) {
|
|
381
|
-
activeSlide = 0;
|
|
382
|
-
}
|
|
200
|
+
const FilmCard = ({ setCurrentSlide, movie, current, columnWidth }: FilmCardProps) => {
|
|
201
|
+
const [hoverCallback, setHoverCallback] = useState<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
383
202
|
|
|
384
|
-
const
|
|
203
|
+
const onHover = useCallback(() => {
|
|
204
|
+
const timeout = setTimeout(() => setCurrentSlide(), 500);
|
|
205
|
+
setHoverCallback(timeout);
|
|
206
|
+
}, [setCurrentSlide]);
|
|
385
207
|
|
|
386
208
|
return (
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
<NavigationArrow
|
|
404
|
-
slideIndexTarget={slideIndexTarget < slideshow.length - 1 ? slideIndexTarget + 1 : 0}
|
|
405
|
-
gotoSlide={gotoSlide}
|
|
406
|
-
rightArrow
|
|
407
|
-
/>
|
|
408
|
-
{!animationComplete && (
|
|
409
|
-
<SlideshowItem
|
|
410
|
-
fadeOver
|
|
411
|
-
role="img"
|
|
412
|
-
onAnimationEnd={onChangedSlide}
|
|
413
|
-
style={{
|
|
414
|
-
backgroundImage: `url(${(backgroundImage && backgroundImage.url) || ''})`,
|
|
415
|
-
}}
|
|
416
|
-
/>
|
|
417
|
-
)}
|
|
418
|
-
<div
|
|
419
|
-
ref={slideRef}
|
|
420
|
-
css={itemWrapperCSS}
|
|
421
|
-
onTransitionEnd={onTransitionEnd}
|
|
422
|
-
style={{
|
|
423
|
-
width: slideshowWidth,
|
|
424
|
-
transform: getSlidePosition(slideIndex),
|
|
425
|
-
}}
|
|
426
|
-
>
|
|
427
|
-
{renderSlideItem(slideshow[slideshow.length - 1])}
|
|
428
|
-
{slideshow.map(renderSlideItem)}
|
|
429
|
-
{renderSlideItem(slideshow[0])}
|
|
430
|
-
</div>
|
|
431
|
-
<SlideshowIndicator slideshow={slideshow} activeSlide={activeSlide} gotoSlide={gotoSlide} />
|
|
432
|
-
</>
|
|
433
|
-
</SlideshowWrapper>
|
|
209
|
+
<StyledFilmContentCard
|
|
210
|
+
onMouseEnter={onHover}
|
|
211
|
+
onMouseLeave={() => {
|
|
212
|
+
if (hoverCallback) {
|
|
213
|
+
clearTimeout(hoverCallback);
|
|
214
|
+
setHoverCallback(undefined);
|
|
215
|
+
}
|
|
216
|
+
}}
|
|
217
|
+
onFocus={() => setCurrentSlide()}
|
|
218
|
+
current={current}
|
|
219
|
+
aria-describedby={'currentMovieDescription'}
|
|
220
|
+
key={movie.id}
|
|
221
|
+
movie={movie}
|
|
222
|
+
columnWidth={columnWidth}
|
|
223
|
+
resourceTypes={[]}
|
|
224
|
+
/>
|
|
434
225
|
);
|
|
435
226
|
};
|
|
436
227
|
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { Children, useMemo, useState } from 'react';
|
|
9
|
+
import { Children, HTMLProps, ReactNode, useMemo, useState } from 'react';
|
|
10
10
|
import BEMHelper from 'react-bem-helper';
|
|
11
11
|
import { useTranslation } from 'react-i18next';
|
|
12
|
+
import styled from '@emotion/styled';
|
|
12
13
|
import { ButtonV2 } from '@ndla/button';
|
|
13
14
|
import SafeLink from '@ndla/safelink';
|
|
14
15
|
import SectionHeading from '../SectionHeading';
|
|
@@ -54,12 +55,25 @@ export const RelatedArticleV2 = ({
|
|
|
54
55
|
);
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
const HeadingWrapper = styled.div`
|
|
59
|
+
display: flex;
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
align-items: center;
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
interface Props extends HTMLProps<HTMLElement> {
|
|
58
65
|
children?: JSX.Element[];
|
|
59
66
|
articleCount?: number;
|
|
60
67
|
headingLevel?: HeadingLevel;
|
|
68
|
+
headingButtons?: ReactNode;
|
|
61
69
|
}
|
|
62
|
-
export const RelatedArticleListV2 = ({
|
|
70
|
+
export const RelatedArticleListV2 = ({
|
|
71
|
+
children = [],
|
|
72
|
+
articleCount,
|
|
73
|
+
headingLevel = 'h3',
|
|
74
|
+
headingButtons,
|
|
75
|
+
...rest
|
|
76
|
+
}: Props) => {
|
|
63
77
|
const [expanded, setExpanded] = useState(false);
|
|
64
78
|
const { t } = useTranslation();
|
|
65
79
|
const childCount = useMemo(() => articleCount ?? Children.count(children), [children, articleCount]);
|
|
@@ -69,10 +83,13 @@ export const RelatedArticleListV2 = ({ children = [], articleCount, headingLevel
|
|
|
69
83
|
);
|
|
70
84
|
|
|
71
85
|
return (
|
|
72
|
-
<section {...classes('')}>
|
|
73
|
-
<
|
|
74
|
-
{
|
|
75
|
-
|
|
86
|
+
<section {...classes('')} {...rest}>
|
|
87
|
+
<HeadingWrapper>
|
|
88
|
+
<SectionHeading headingLevel={headingLevel} className={classes('component-title').className}>
|
|
89
|
+
{t('related.title')}
|
|
90
|
+
</SectionHeading>
|
|
91
|
+
{headingButtons}
|
|
92
|
+
</HeadingWrapper>
|
|
76
93
|
<div {...classes('articles')}>{childrenToShow}</div>
|
|
77
94
|
{childCount > 2 ? (
|
|
78
95
|
<ButtonV2 onClick={() => setExpanded((p) => !p)} variant="outline">
|
|
@@ -24,7 +24,11 @@ interface StyledButtonProps {
|
|
|
24
24
|
ndlaFilm?: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const props = ['hideOnNarrowScreen', 'hideOnWideScreen', 'ndlaFilm'];
|
|
28
|
+
|
|
29
|
+
const shouldForwardProp = (p: string) => !props.includes(p);
|
|
30
|
+
|
|
31
|
+
const StyledButton = styled(ButtonV2, { shouldForwardProp })<StyledButtonProps>`
|
|
28
32
|
background: ${(p) => (p.ndlaFilm ? colors.ndlaFilm.filmColorBright : colors.brand.greyLighter)};
|
|
29
33
|
border-radius: ${misc.borderRadius};
|
|
30
34
|
border: 0;
|
|
@@ -1185,6 +1185,7 @@ const messages = {
|
|
|
1185
1185
|
drawerButton: 'Show folders and resources',
|
|
1186
1186
|
drawerTitle: 'Folders and resources',
|
|
1187
1187
|
description: {
|
|
1188
|
+
all: 'In this folder you find articles and tasks from NDLA. The articles have been collected and placed in order by a teacher.\n\nYou can use the menu to navigate through the articles.\n\nIf you want to come back to the folder later, you can use the link the teacher gave you, or you can bookmark the page.',
|
|
1188
1189
|
info1:
|
|
1189
1190
|
'In this folder you find articles and tasks from NDLA. The articles have been collected and placed in order by a teacher.',
|
|
1190
1191
|
info2: 'You can use the menu to navigate through the articles.',
|