@ndla/ui 3.3.4 → 3.3.8
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/AudioPlayer/Controls.js +30 -30
- package/es/Breadcrumb/Breadcrumb.js +2 -1
- package/es/ContentTypeBadge/ContentTypeBadge.js +30 -10
- package/es/NDLAFilm/FilmSlideshow.js +213 -247
- package/es/NDLAFilm/NavigationArrow.js +13 -60
- package/es/NDLAFilm/SlideshowIndicator.js +16 -63
- package/es/NDLAFilm/interfaces.js +0 -0
- package/es/NDLAFilm/shapes.js +0 -10
- package/es/Programme/Programme.js +11 -36
- package/es/Programme/ProgrammeSubjects.js +15 -42
- package/es/SearchTypeResult/SearchTypeHeader.js +7 -6
- package/es/Spinner/Spinner.js +3 -3
- package/lib/AudioPlayer/Controls.js +30 -30
- package/lib/Breadcrumb/Breadcrumb.js +2 -1
- package/lib/ContentTypeBadge/ContentTypeBadge.d.ts +3 -1
- package/lib/ContentTypeBadge/ContentTypeBadge.js +30 -10
- package/lib/NDLAFilm/FilmSlideshow.d.ts +16 -0
- package/lib/NDLAFilm/FilmSlideshow.js +214 -248
- package/lib/NDLAFilm/NavigationArrow.d.ts +15 -0
- package/lib/NDLAFilm/NavigationArrow.js +20 -65
- package/lib/NDLAFilm/SlideshowIndicator.d.ts +15 -0
- package/lib/NDLAFilm/SlideshowIndicator.js +16 -69
- package/lib/NDLAFilm/interfaces.d.ts +10 -0
- package/lib/NDLAFilm/interfaces.js +1 -0
- package/lib/NDLAFilm/shapes.d.ts +5 -0
- package/lib/NDLAFilm/shapes.js +1 -14
- package/lib/Programme/Programme.d.ts +1 -1
- package/lib/Programme/Programme.js +11 -36
- package/lib/Programme/ProgrammeSubjects.d.ts +12 -17
- package/lib/Programme/ProgrammeSubjects.js +23 -48
- package/lib/SearchTypeResult/SearchTypeHeader.js +7 -6
- package/lib/Spinner/Spinner.d.ts +3 -3
- package/lib/Spinner/Spinner.js +2 -2
- package/package.json +10 -10
- package/src/AudioPlayer/Controls.tsx +2 -2
- package/src/Breadcrumb/Breadcrumb.tsx +1 -1
- package/src/ContentTypeBadge/ContentTypeBadge.tsx +11 -9
- package/src/NDLAFilm/FilmSlideshow.tsx +264 -0
- package/src/NDLAFilm/NavigationArrow.tsx +42 -0
- package/src/NDLAFilm/SlideshowIndicator.tsx +40 -0
- package/src/NDLAFilm/interfaces.ts +10 -0
- package/src/NDLAFilm/shapes.ts +6 -0
- package/src/Programme/Programme.tsx +3 -15
- package/src/Programme/ProgrammeSubjects.tsx +19 -43
- package/src/SearchTypeResult/SearchTypeHeader.tsx +1 -1
- package/src/Spinner/Spinner.tsx +9 -5
- package/src/NDLAFilm/FilmSlideshow.jsx +0 -277
- package/src/NDLAFilm/NavigationArrow.jsx +0 -46
- package/src/NDLAFilm/SlideshowIndicator.jsx +0 -43
- package/src/NDLAFilm/shapes.js +0 -17
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present, NDLA.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the GPLv3 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
10
|
+
import { SwipeEventData, useSwipeable } from 'react-swipeable';
|
|
11
|
+
import BEMHelper from 'react-bem-helper';
|
|
12
|
+
import SafeLink from '@ndla/safelink';
|
|
13
|
+
import { OneColumn } from '../Layout';
|
|
14
|
+
import Spinner from '../Spinner';
|
|
15
|
+
import { NDLAMovie } from './interfaces';
|
|
16
|
+
import NavigationArrow from './NavigationArrow';
|
|
17
|
+
import SlideshowIndicator from './SlideshowIndicator';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
autoSlide: boolean;
|
|
21
|
+
randomStart: boolean;
|
|
22
|
+
slideshow: NDLAMovie[];
|
|
23
|
+
slideInterval: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const classes = new BEMHelper({
|
|
27
|
+
name: 'film-slideshow',
|
|
28
|
+
prefix: 'c-',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const defaultTransitionSwipeEnd = 'transform 600ms cubic-bezier(0, 0.76, 0.09, 1)';
|
|
32
|
+
const defaultTransitionText = 'opacity 600ms ease';
|
|
33
|
+
|
|
34
|
+
const renderSlideItem = (slide: NDLAMovie) => (
|
|
35
|
+
<div
|
|
36
|
+
{...classes('item')}
|
|
37
|
+
key={slide.id}
|
|
38
|
+
role="img"
|
|
39
|
+
aria-label={(slide.metaImage && slide.metaImage.alt) || ''}
|
|
40
|
+
style={{
|
|
41
|
+
backgroundImage: `url(${(slide.metaImage && slide.metaImage.url) || ''})`,
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const FilmSlideshow = ({ autoSlide = false, slideshow = [], slideInterval = 5000 }: Props) => {
|
|
47
|
+
const [swipeDistance, setSwipeDistance] = useState(0);
|
|
48
|
+
const [slideIndex, setSlideIndex] = useState(0);
|
|
49
|
+
const [slideIndexTarget, setSlideIndexTarget] = useState(0);
|
|
50
|
+
const [animationComplete, setAnimationComplete] = useState(true);
|
|
51
|
+
const slideRef = useRef<HTMLDivElement>(null);
|
|
52
|
+
const slideText = useRef<HTMLDivElement>(null);
|
|
53
|
+
let timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
54
|
+
|
|
55
|
+
const gotoSlide = useCallback((indexTarget: number, useAnimation = false) => {
|
|
56
|
+
setSwipeDistance(0);
|
|
57
|
+
if (timer.current) {
|
|
58
|
+
clearTimeout(timer.current);
|
|
59
|
+
}
|
|
60
|
+
setSlideIndexTarget(indexTarget);
|
|
61
|
+
setAnimationComplete(!useAnimation);
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const onChangedSlide = () => {
|
|
65
|
+
if (!animationComplete) {
|
|
66
|
+
if (slideRef.current) {
|
|
67
|
+
slideRef.current.style.transition = 'none';
|
|
68
|
+
slideRef.current.style.transform = `translateX(${slideIndexTarget * 100}vw))`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setAnimationComplete(true);
|
|
72
|
+
setSlideIndex(slideIndexTarget);
|
|
73
|
+
} else if (slideIndexTarget === -1) {
|
|
74
|
+
if (slideRef.current) {
|
|
75
|
+
// Go to last slide for continuous loop
|
|
76
|
+
slideRef.current.style.transition = 'none';
|
|
77
|
+
slideRef.current.style.transform = `translateX(${slideshow.length * 100}vw))`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setSlideIndex(slideshow.length - 1);
|
|
81
|
+
setSlideIndexTarget(slideshow.length - 1);
|
|
82
|
+
setAnimationComplete(true);
|
|
83
|
+
} else if (slideIndexTarget === slideshow.length) {
|
|
84
|
+
if (slideRef.current) {
|
|
85
|
+
// Go to first slide for continuous loop
|
|
86
|
+
slideRef.current.style.transition = 'none';
|
|
87
|
+
slideRef.current.style.transform = `translateX(100vw))`;
|
|
88
|
+
}
|
|
89
|
+
setSlideIndex(0);
|
|
90
|
+
setSlideIndexTarget(0);
|
|
91
|
+
setAnimationComplete(true);
|
|
92
|
+
} else {
|
|
93
|
+
setAnimationComplete(true);
|
|
94
|
+
setSlideIndex(slideIndexTarget);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const onSwipeEnd = () => {
|
|
99
|
+
let slide;
|
|
100
|
+
if (swipeDistance > 40) {
|
|
101
|
+
slide = -1;
|
|
102
|
+
} else if (swipeDistance < -40) {
|
|
103
|
+
slide = 1;
|
|
104
|
+
} else {
|
|
105
|
+
slide = 0;
|
|
106
|
+
}
|
|
107
|
+
if (slideRef.current && slideText.current) {
|
|
108
|
+
slideRef.current.style.transition = defaultTransitionSwipeEnd;
|
|
109
|
+
slideText.current.style.transition = defaultTransitionText;
|
|
110
|
+
slideText.current.style.opacity = '1';
|
|
111
|
+
}
|
|
112
|
+
setSwipeDistance(0);
|
|
113
|
+
|
|
114
|
+
initTimer();
|
|
115
|
+
|
|
116
|
+
if (slide !== 0) {
|
|
117
|
+
setSlideIndex(slideIndex + slide);
|
|
118
|
+
setSlideIndexTarget(slideIndex + slide);
|
|
119
|
+
} else {
|
|
120
|
+
// Reset transfrom
|
|
121
|
+
if (slideRef.current) {
|
|
122
|
+
slideRef.current.style.transform = getSlidePosition(slideIndex + slide);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const onSwipe = (eventData: SwipeEventData) => {
|
|
128
|
+
if (eventData.dir === 'Up' || eventData.dir === 'Down') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (timer.current) {
|
|
132
|
+
clearTimeout(timer.current);
|
|
133
|
+
}
|
|
134
|
+
setSwipeDistance(eventData.deltaX);
|
|
135
|
+
if (slideRef && slideRef.current) {
|
|
136
|
+
slideRef.current.style.transition = 'none';
|
|
137
|
+
slideRef.current.style.transform = getSlidePosition(slideIndexTarget);
|
|
138
|
+
}
|
|
139
|
+
const opacityText = 1 - Math.min(100, Math.abs(swipeDistance)) / 100;
|
|
140
|
+
if (slideText && slideText.current) {
|
|
141
|
+
slideText.current.style.transition = 'none';
|
|
142
|
+
slideText.current.style.opacity = opacityText.toString();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const onTransitionEnd = () => {
|
|
147
|
+
const slideshowLength = slideshow.length;
|
|
148
|
+
if (slideIndex === -1) {
|
|
149
|
+
if (slideRef.current) {
|
|
150
|
+
slideRef.current.style.transition = 'none';
|
|
151
|
+
slideRef.current.style.transform = getSlidePosition(slideshowLength - 1);
|
|
152
|
+
}
|
|
153
|
+
setSlideIndex(slideshowLength - 1);
|
|
154
|
+
setSlideIndexTarget(slideshowLength - 1);
|
|
155
|
+
} else if (slideIndex >= slideshowLength) {
|
|
156
|
+
if (slideRef.current) {
|
|
157
|
+
slideRef.current.style.transition = 'none';
|
|
158
|
+
slideRef.current.style.transform = getSlidePosition(0);
|
|
159
|
+
}
|
|
160
|
+
setSlideIndex(0);
|
|
161
|
+
setSlideIndexTarget(0);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const getSlidePosition = (target: number) => {
|
|
166
|
+
if (swipeDistance !== 0) {
|
|
167
|
+
return `translateX(calc(${swipeDistance}px -
|
|
168
|
+
${(target + 1) * 100}vw))`;
|
|
169
|
+
}
|
|
170
|
+
return `translateX(-${(target + 1) * 100}vw)`;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const initTimer = useCallback(() => {
|
|
174
|
+
if (autoSlide) {
|
|
175
|
+
timer.current = setTimeout(() => {
|
|
176
|
+
gotoSlide(slideIndex + 1);
|
|
177
|
+
}, slideInterval);
|
|
178
|
+
}
|
|
179
|
+
}, [autoSlide, gotoSlide, slideInterval, slideIndex]);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
initTimer();
|
|
183
|
+
}, [initTimer]);
|
|
184
|
+
|
|
185
|
+
const handlers = useSwipeable({
|
|
186
|
+
onSwiped: onSwipeEnd,
|
|
187
|
+
onSwiping: onSwipe,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (slideshow.length === 0) {
|
|
191
|
+
return (
|
|
192
|
+
<div>
|
|
193
|
+
<div {...classes('slideshow')}>
|
|
194
|
+
<Spinner inverted />
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const slideshowWidth = `${(slideshow.length + 2) * 100}vw`;
|
|
201
|
+
let activeSlide = slideIndex;
|
|
202
|
+
if (activeSlide < 0) {
|
|
203
|
+
activeSlide = slideshow.length - 1;
|
|
204
|
+
} else if (activeSlide >= slideshow.length) {
|
|
205
|
+
activeSlide = 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<section {...classes('')} {...handlers}>
|
|
210
|
+
<>
|
|
211
|
+
<div {...classes('slide-link-wrapper')}>
|
|
212
|
+
<OneColumn>
|
|
213
|
+
<SafeLink
|
|
214
|
+
to={slideshow[activeSlide].path}
|
|
215
|
+
{...classes('item-wrapper', 'text', {
|
|
216
|
+
out: !animationComplete,
|
|
217
|
+
})}>
|
|
218
|
+
<div {...classes('slide-info')} ref={slideText}>
|
|
219
|
+
<h1>{slideshow[activeSlide].title}</h1>
|
|
220
|
+
<p>{slideshow[activeSlide].metaDescription}</p>
|
|
221
|
+
</div>
|
|
222
|
+
</SafeLink>
|
|
223
|
+
</OneColumn>
|
|
224
|
+
</div>
|
|
225
|
+
<NavigationArrow
|
|
226
|
+
slideIndexTarget={slideIndexTarget > 0 ? slideIndexTarget - 1 : slideshow.length - 1}
|
|
227
|
+
gotoSlide={gotoSlide}
|
|
228
|
+
/>
|
|
229
|
+
<NavigationArrow
|
|
230
|
+
slideIndexTarget={slideIndexTarget < slideshow.length - 1 ? slideIndexTarget + 1 : 0}
|
|
231
|
+
gotoSlide={gotoSlide}
|
|
232
|
+
rightArrow
|
|
233
|
+
/>
|
|
234
|
+
{!animationComplete && (
|
|
235
|
+
<div
|
|
236
|
+
{...classes('item', 'fade-over')}
|
|
237
|
+
role="img"
|
|
238
|
+
onAnimationEnd={onChangedSlide}
|
|
239
|
+
style={{
|
|
240
|
+
backgroundImage: `url(${
|
|
241
|
+
(slideshow[activeSlide].metaImage && slideshow[activeSlide].metaImage.url) || ''
|
|
242
|
+
})`,
|
|
243
|
+
}}
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
246
|
+
<div
|
|
247
|
+
ref={slideRef}
|
|
248
|
+
{...classes('item-wrapper')}
|
|
249
|
+
onTransitionEnd={onTransitionEnd}
|
|
250
|
+
style={{
|
|
251
|
+
width: slideshowWidth,
|
|
252
|
+
transform: getSlidePosition(slideIndex),
|
|
253
|
+
}}>
|
|
254
|
+
{renderSlideItem(slideshow[slideshow.length - 1])}
|
|
255
|
+
{slideshow.map(renderSlideItem)}
|
|
256
|
+
{renderSlideItem(slideshow[0])}
|
|
257
|
+
</div>
|
|
258
|
+
<SlideshowIndicator slideshow={slideshow} activeSlide={activeSlide} gotoSlide={gotoSlide} />
|
|
259
|
+
</>
|
|
260
|
+
</section>
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export default FilmSlideshow;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present, NDLA.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the GPLv3 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import BEMHelper from 'react-bem-helper';
|
|
11
|
+
import { ChevronRight, ChevronLeft } from '@ndla/icons/common';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
slideIndexTarget: number;
|
|
15
|
+
slideshowLength?: number;
|
|
16
|
+
gotoSlide: (indexTarget: number, useAnimation: boolean) => void;
|
|
17
|
+
rightArrow?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const classes = new BEMHelper({
|
|
21
|
+
name: 'film-slideshow',
|
|
22
|
+
prefix: 'c-',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const NavigationArrow = ({ slideIndexTarget, gotoSlide, rightArrow }: Props) => {
|
|
26
|
+
const Chevron = rightArrow ? ChevronRight : ChevronLeft;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div {...classes('navigation-arrows', rightArrow ? 'right' : '')}>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
tabIndex={-1}
|
|
33
|
+
onClick={() => {
|
|
34
|
+
gotoSlide(slideIndexTarget, true);
|
|
35
|
+
}}>
|
|
36
|
+
<Chevron />
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default NavigationArrow;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2019-present, NDLA.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the GPLv3 license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import BEMHelper from 'react-bem-helper';
|
|
11
|
+
import { NDLAMovie } from './interfaces';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
slideshow: NDLAMovie[];
|
|
15
|
+
activeSlide: number;
|
|
16
|
+
gotoSlide: (indexTarget: number, useAnimation: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const classes = new BEMHelper({
|
|
20
|
+
name: 'film-slideshow',
|
|
21
|
+
prefix: 'c-',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const SlideshowIndicator = ({ slideshow, activeSlide, gotoSlide }: Props) => {
|
|
25
|
+
return (
|
|
26
|
+
<div {...classes('indicator-wrapper')}>
|
|
27
|
+
{slideshow.map((slide, index) => (
|
|
28
|
+
<button
|
|
29
|
+
key={`indicator_${index}`}
|
|
30
|
+
type="button"
|
|
31
|
+
{...classes('indicator-dot', index === activeSlide ? 'active' : '')}
|
|
32
|
+
onClick={() => gotoSlide(index, true)}>
|
|
33
|
+
<span />
|
|
34
|
+
</button>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default SlideshowIndicator;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import styled from '@emotion/styled';
|
|
3
3
|
import { breakpoints, mq, spacing } from '@ndla/core';
|
|
4
4
|
import LayoutItem, { OneColumn } from '../Layout';
|
|
@@ -65,19 +65,7 @@ type Props = GradesProps & {
|
|
|
65
65
|
image?: string;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
export const Programme = ({ heading, image, grades }: Props) => {
|
|
69
|
-
const [showGradeIndex, setShowGradeIndex] = useState(0);
|
|
70
|
-
const isWindowContext = typeof window !== 'undefined';
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
if (isWindowContext) {
|
|
74
|
-
const rememberGradeIndex = window.localStorage.getItem('programmeShowGradeIndex') || '0';
|
|
75
|
-
if (grades.length > Number(rememberGradeIndex)) {
|
|
76
|
-
setShowGradeIndex(Number(rememberGradeIndex));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}, [isWindowContext, grades]);
|
|
80
|
-
|
|
68
|
+
export const Programme = ({ heading, image, grades, selectedGrade, onChangeGrade }: Props) => {
|
|
81
69
|
return (
|
|
82
70
|
<StyledWrapper>
|
|
83
71
|
<StyledBackground image={image} />
|
|
@@ -87,7 +75,7 @@ export const Programme = ({ heading, image, grades }: Props) => {
|
|
|
87
75
|
<StyledContentWrapper>
|
|
88
76
|
<NavigationHeading>{heading}</NavigationHeading>
|
|
89
77
|
<SubjectsWrapper>
|
|
90
|
-
<ProgrammeSubjects grades={grades}
|
|
78
|
+
<ProgrammeSubjects grades={grades} selectedGrade={selectedGrade} onChangeGrade={onChangeGrade} />
|
|
91
79
|
</SubjectsWrapper>
|
|
92
80
|
</StyledContentWrapper>
|
|
93
81
|
</LayoutItem>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import React
|
|
9
|
+
import React from 'react';
|
|
10
10
|
import styled from '@emotion/styled';
|
|
11
11
|
// @ts-ignore
|
|
12
12
|
import Button from '@ndla/button';
|
|
@@ -27,66 +27,42 @@ const GradesMenu = styled.div`
|
|
|
27
27
|
`;
|
|
28
28
|
|
|
29
29
|
export type GradesProps = {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
selectedGrade?: string;
|
|
31
|
+
onChangeGrade: (newGrade: string) => void;
|
|
32
|
+
grades: {
|
|
33
|
+
name: string;
|
|
34
|
+
categories: {
|
|
32
35
|
name: string;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
url: string;
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
},
|
|
43
|
-
];
|
|
44
|
-
},
|
|
45
|
-
];
|
|
36
|
+
subjects: {
|
|
37
|
+
label: string;
|
|
38
|
+
url: string;
|
|
39
|
+
}[];
|
|
40
|
+
}[];
|
|
41
|
+
}[];
|
|
46
42
|
};
|
|
47
43
|
|
|
48
44
|
type Props = GradesProps & {
|
|
49
|
-
preSelectedGradeIndex?: number;
|
|
50
45
|
onNavigate?: () => void;
|
|
51
46
|
};
|
|
52
47
|
|
|
53
|
-
const ProgrammeSubjects = ({ grades, onNavigate,
|
|
54
|
-
const
|
|
55
|
-
const isWindowContext = typeof window !== 'undefined';
|
|
56
|
-
|
|
57
|
-
const toggleGradeIndex = (index: number) => {
|
|
58
|
-
setShowGradeIndex(index);
|
|
59
|
-
if (isWindowContext) {
|
|
60
|
-
window.localStorage.setItem('programmeShowGradeIndex', `${index}`);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const selectedGrade = grades[showGradeIndex];
|
|
48
|
+
const ProgrammeSubjects = ({ grades, onNavigate, onChangeGrade, selectedGrade = 'vg1' }: Props) => {
|
|
49
|
+
const grade = grades.find((grade) => grade.name.toLowerCase() === selectedGrade) ?? grades[0];
|
|
65
50
|
return (
|
|
66
51
|
<>
|
|
67
52
|
<GradesMenu>
|
|
68
|
-
{grades.map((item
|
|
53
|
+
{grades.map((item) => (
|
|
69
54
|
<Button
|
|
70
55
|
key={item.name}
|
|
71
|
-
onClick={() =>
|
|
72
|
-
lighter={
|
|
56
|
+
onClick={() => onChangeGrade(item.name.toLowerCase())}
|
|
57
|
+
lighter={item !== grade}
|
|
73
58
|
size="normal"
|
|
74
59
|
borderShape="rounded">
|
|
75
60
|
{item.name}
|
|
76
61
|
</Button>
|
|
77
62
|
))}
|
|
78
63
|
</GradesMenu>
|
|
79
|
-
{
|
|
80
|
-
<NavigationBox
|
|
81
|
-
key={category.name}
|
|
82
|
-
heading={category.name}
|
|
83
|
-
items={category.subjects}
|
|
84
|
-
onClick={() => {
|
|
85
|
-
if (onNavigate) {
|
|
86
|
-
onNavigate();
|
|
87
|
-
}
|
|
88
|
-
}}
|
|
89
|
-
/>
|
|
64
|
+
{grade.categories.map((category) => (
|
|
65
|
+
<NavigationBox key={category.name} heading={category.name} items={category.subjects} onClick={onNavigate} />
|
|
90
66
|
))}
|
|
91
67
|
</>
|
|
92
68
|
);
|
|
@@ -85,7 +85,7 @@ type Props = {
|
|
|
85
85
|
const SearchTypeHeader = ({ filters, onFilterClick, totalCount, type, t }: Props & WithTranslation) => (
|
|
86
86
|
<HeaderWrapper>
|
|
87
87
|
<TypeWrapper>
|
|
88
|
-
{type && <ContentTypeBadge type={type} background size="large" />}
|
|
88
|
+
{type && <ContentTypeBadge type={type} title={t(`contentTypes.${type}`)} background size="large" />}
|
|
89
89
|
<SubjectName>
|
|
90
90
|
{type && <b>{t(`contentTypes.${type}`)}</b>}{' '}
|
|
91
91
|
{totalCount && <Count>{t(`searchPage.resultType.hits`, { count: totalCount })}</Count>}
|
package/src/Spinner/Spinner.tsx
CHANGED
|
@@ -8,23 +8,27 @@
|
|
|
8
8
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import styled from '@emotion/styled';
|
|
11
|
-
import {
|
|
11
|
+
import { colors, spacing, SpacingNames } from '@ndla/core';
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
|
+
size?: SpacingNames;
|
|
15
|
+
margin?: string;
|
|
16
|
+
inverted?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface StyledProps extends Props {
|
|
14
20
|
size: SpacingNames;
|
|
15
|
-
margin: string;
|
|
16
|
-
inverted: boolean;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
const SpinnerDiv = styled('div')<
|
|
23
|
+
const SpinnerDiv = styled('div')<StyledProps>`
|
|
20
24
|
border: calc(${(props) => spacing[props.size]} / 6.5) solid rgba(0, 0, 0, 0.1);
|
|
21
25
|
border-bottom-color: ${(props) => (props.inverted ? '#fff' : colors.brand.primary)};
|
|
22
26
|
border-radius: 50%;
|
|
23
27
|
animation: spinnerAnimation 0.7s linear infinite;
|
|
24
28
|
height: ${(props) => spacing[props.size]};
|
|
25
29
|
width: ${(props) => spacing[props.size]};
|
|
26
|
-
display: block;
|
|
27
30
|
margin: ${(props) => props.margin};
|
|
31
|
+
display: block;
|
|
28
32
|
@keyframes spinnerAnimation {
|
|
29
33
|
0% {
|
|
30
34
|
transform: rotate(0deg);
|