@ndla/ui 3.3.6 → 3.3.11

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.
Files changed (110) hide show
  1. package/es/Article/Article.js +23 -1
  2. package/es/Article/ArticleNotions.js +80 -32
  3. package/es/AudioPlayer/Controls.js +30 -30
  4. package/es/Breadcrumb/Breadcrumb.js +2 -1
  5. package/es/Breadcrumblist/Breadcrumblist.js +14 -10
  6. package/es/CloseButton/CloseButton.js +62 -0
  7. package/es/CloseButton/index.js +9 -0
  8. package/es/ContentTypeBadge/ContentTypeBadge.js +30 -10
  9. package/es/Frontpage/FrontpageProgramMenu.js +10 -10
  10. package/es/MediaList/MediaList.js +22 -73
  11. package/es/MessageBox/MessageBox.js +146 -78
  12. package/es/MessageBox/MessageBoxTag.js +37 -0
  13. package/es/MessageBox/index.js +3 -1
  14. package/es/NDLAFilm/FilmSlideshow.js +214 -247
  15. package/es/NDLAFilm/NavigationArrow.js +13 -60
  16. package/es/NDLAFilm/SlideshowIndicator.js +16 -63
  17. package/es/NDLAFilm/interfaces.js +0 -0
  18. package/es/Programme/Programme.js +18 -36
  19. package/es/Programme/ProgrammeSubjects.js +15 -42
  20. package/es/SearchTypeResult/SearchNotionItem.js +12 -12
  21. package/es/SearchTypeResult/SearchNotionsResult.js +9 -14
  22. package/es/SearchTypeResult/SearchTypeHeader.js +7 -6
  23. package/es/Spinner/Spinner.js +3 -3
  24. package/es/index.js +2 -1
  25. package/es/locale/messages-en.js +10 -0
  26. package/es/locale/messages-nb.js +10 -0
  27. package/es/locale/messages-nn.js +10 -0
  28. package/lib/Article/Article.d.ts +3 -1
  29. package/lib/Article/Article.js +25 -3
  30. package/lib/Article/ArticleNotions.js +79 -30
  31. package/lib/AudioPlayer/Controls.js +30 -30
  32. package/lib/Breadcrumb/Breadcrumb.js +2 -1
  33. package/lib/Breadcrumblist/Breadcrumblist.d.ts +2 -1
  34. package/lib/Breadcrumblist/Breadcrumblist.js +15 -10
  35. package/lib/CloseButton/CloseButton.d.ts +6 -0
  36. package/lib/CloseButton/CloseButton.js +69 -0
  37. package/lib/CloseButton/index.d.ts +9 -0
  38. package/lib/CloseButton/index.js +15 -0
  39. package/lib/ContentTypeBadge/ContentTypeBadge.d.ts +3 -1
  40. package/lib/ContentTypeBadge/ContentTypeBadge.js +30 -10
  41. package/lib/Frontpage/FrontpageProgramMenu.js +10 -10
  42. package/lib/MediaList/MediaList.d.ts +48 -0
  43. package/lib/MediaList/MediaList.js +24 -78
  44. package/lib/MediaList/index.d.ts +8 -0
  45. package/lib/MessageBox/MessageBox.d.ts +22 -6
  46. package/lib/MessageBox/MessageBox.js +146 -77
  47. package/lib/MessageBox/MessageBoxTag.d.ts +12 -0
  48. package/lib/MessageBox/MessageBoxTag.js +44 -0
  49. package/lib/MessageBox/index.d.ts +3 -1
  50. package/lib/MessageBox/index.js +22 -2
  51. package/lib/NDLAFilm/FilmSlideshow.d.ts +16 -0
  52. package/lib/NDLAFilm/FilmSlideshow.js +214 -248
  53. package/lib/NDLAFilm/NavigationArrow.d.ts +15 -0
  54. package/lib/NDLAFilm/NavigationArrow.js +20 -65
  55. package/lib/NDLAFilm/SlideshowIndicator.d.ts +15 -0
  56. package/lib/NDLAFilm/SlideshowIndicator.js +16 -69
  57. package/lib/NDLAFilm/interfaces.d.ts +10 -0
  58. package/lib/NDLAFilm/interfaces.js +1 -0
  59. package/lib/NDLAFilm/shapes.d.ts +15 -0
  60. package/lib/Programme/Programme.d.ts +2 -1
  61. package/lib/Programme/Programme.js +23 -39
  62. package/lib/Programme/ProgrammeSubjects.d.ts +3 -2
  63. package/lib/Programme/ProgrammeSubjects.js +23 -48
  64. package/lib/SearchTypeResult/SearchNotionItem.js +12 -12
  65. package/lib/SearchTypeResult/SearchNotionsResult.js +8 -13
  66. package/lib/SearchTypeResult/SearchTypeHeader.js +7 -6
  67. package/lib/Spinner/Spinner.d.ts +3 -3
  68. package/lib/Spinner/Spinner.js +2 -2
  69. package/lib/index.d.ts +2 -1
  70. package/lib/index.js +23 -0
  71. package/lib/locale/messages-en.d.ts +10 -0
  72. package/lib/locale/messages-en.js +10 -0
  73. package/lib/locale/messages-nb.d.ts +10 -0
  74. package/lib/locale/messages-nb.js +10 -0
  75. package/lib/locale/messages-nn.d.ts +10 -0
  76. package/lib/locale/messages-nn.js +10 -0
  77. package/package.json +10 -10
  78. package/src/Article/Article.tsx +17 -0
  79. package/src/Article/ArticleNotions.tsx +10 -7
  80. package/src/AudioPlayer/Controls.tsx +2 -2
  81. package/src/Breadcrumb/Breadcrumb.tsx +1 -1
  82. package/src/Breadcrumblist/Breadcrumblist.tsx +5 -3
  83. package/src/CloseButton/CloseButton.tsx +40 -0
  84. package/src/CloseButton/index.ts +11 -0
  85. package/src/ContentTypeBadge/ContentTypeBadge.tsx +11 -9
  86. package/src/Frontpage/FrontpageProgramMenu.tsx +1 -0
  87. package/src/MediaList/MediaList.tsx +158 -0
  88. package/src/MediaList/{index.js → index.ts} +0 -0
  89. package/src/MessageBox/MessageBox.tsx +117 -96
  90. package/src/MessageBox/MessageBoxTag.tsx +35 -0
  91. package/src/MessageBox/index.ts +3 -1
  92. package/src/NDLAFilm/FilmSlideshow.tsx +265 -0
  93. package/src/NDLAFilm/NavigationArrow.tsx +42 -0
  94. package/src/NDLAFilm/SlideshowIndicator.tsx +40 -0
  95. package/src/NDLAFilm/interfaces.ts +10 -0
  96. package/src/NDLAFilm/{shapes.js → shapes.ts} +0 -0
  97. package/src/Programme/Programme.tsx +9 -17
  98. package/src/Programme/ProgrammeSubjects.tsx +10 -28
  99. package/src/SearchTypeResult/SearchNotionItem.tsx +0 -1
  100. package/src/SearchTypeResult/SearchNotionsResult.tsx +3 -8
  101. package/src/SearchTypeResult/SearchTypeHeader.tsx +1 -1
  102. package/src/Spinner/Spinner.tsx +9 -5
  103. package/src/index.ts +2 -1
  104. package/src/locale/messages-en.ts +14 -0
  105. package/src/locale/messages-nb.ts +14 -0
  106. package/src/locale/messages-nn.ts +14 -0
  107. package/src/MediaList/MediaList.jsx +0 -182
  108. package/src/NDLAFilm/FilmSlideshow.jsx +0 -277
  109. package/src/NDLAFilm/NavigationArrow.jsx +0 -46
  110. package/src/NDLAFilm/SlideshowIndicator.jsx +0 -43
@@ -1,63 +1,92 @@
1
- import React from 'react';
1
+ /**
2
+ * Copyright (c) 2022-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, { useState } from 'react';
2
10
  import styled from '@emotion/styled';
3
11
  import Sticky from 'react-sticky-el';
4
12
  import { breakpoints, fonts, mq, spacing } from '@ndla/core';
5
- // @ts-ignore
6
- import Button from '@ndla/button';
7
- import { Cross } from '@ndla/icons/action';
8
- import { InformationOutline } from '@ndla/icons/common';
13
+ import { InformationOutline, HumanMaleBoard } from '@ndla/icons/common';
9
14
  import { WithTranslation, withTranslation } from 'react-i18next';
15
+ import { CloseButton } from '../CloseButton';
10
16
 
17
+ export enum MessageBoxType {
18
+ ghost = 'ghost',
19
+ fullpage = 'fullpage',
20
+ medium = 'medium',
21
+ }
11
22
  type WrapperProps = {
12
- boxType?: 'info';
23
+ boxType?: MessageBoxType;
13
24
  };
14
25
 
15
- const colorsByType = (type: WrapperProps['boxType']) => {
16
- const colors = {
17
- color: '#551700',
18
- backgroundColor: '#FEEFB3',
19
- };
26
+ const StyleByType = (type: WrapperProps['boxType']) => {
27
+ const styles = {
28
+ margin: '1px',
29
+ color: '#444444',
30
+ backgroundColor: '#f9f4c8',
31
+ border: 'none',
32
+ display: 'flex',
33
+ width: 'auto',
34
+ position: 'relative',
35
+ transform: 'auto',
36
+ left: 'auto',
37
+ }; //Different CSS properties for different types of message-boxes
20
38
  switch (type) {
21
- case 'info':
22
- default:
39
+ case 'fullpage':
40
+ styles.margin = '0 auto';
41
+ styles.display = 'none';
42
+ styles.width = '100vw';
43
+ styles.position = 'relative';
44
+ styles.left = '50%';
45
+ styles.transform = 'translateX(-50%)';
46
+ break;
47
+ case 'medium':
48
+ styles.margin = '0px';
49
+ break;
50
+ case 'ghost':
51
+ styles.backgroundColor = 'transparent';
52
+ styles.border = '1px solid #D1D6DB';
53
+ styles.color = '#444444';
54
+
23
55
  break;
24
56
  }
25
- return colors;
57
+ return styles;
26
58
  };
27
59
 
28
60
  const Wrapper = styled.div<WrapperProps>`
29
- background: ${(props) => colorsByType(props.boxType).backgroundColor};
30
- color: ${(props) => colorsByType(props.boxType).color};
61
+ background: ${(props) => StyleByType(props.boxType).backgroundColor};
62
+ color: ${(props) => StyleByType(props.boxType).color};
31
63
  font-size: 18px;
32
64
  line-height: 32px;
33
- box-shadow: 0 4px 4px rgba(0, 0, 0, 0.12);
65
+ font-family: ${fonts.sans};
34
66
  display: flex;
35
67
  padding: ${spacing.small};
36
- position: relative;
68
+ position: ${(props) => StyleByType(props.boxType).position};
69
+ border: ${(props) => StyleByType(props.boxType).border};
70
+ border-radius: 5px;
71
+ transform: ${(props) => StyleByType(props.boxType).transform};
72
+ left: ${(props) => StyleByType(props.boxType).left};
73
+ z-index: 10;
74
+ width: ${(props) => StyleByType(props.boxType).width};
37
75
  `;
38
76
 
39
- const Label = styled.label`
40
- font-weight: ${fonts.weight.bold};
41
- display: inline-block;
42
- margin-right: ${spacing.small};
43
- ${mq.range({ until: breakpoints.tabletWide })} {
44
- font-size: 18px;
45
- font-weight: ${fonts.weight.semibold};
46
- }
47
- `;
48
-
49
- const InfoWrapper = styled.div`
50
- margin: 0 auto;
51
- padding: 0 100px;
77
+ const InfoWrapper = styled.div<WrapperProps>`
78
+ margin: ${(props) => StyleByType(props.boxType).margin};
79
+ padding: 10px;
52
80
  display: flex;
53
81
  ${mq.range({ until: breakpoints.tabletWide })} {
54
82
  padding: 0 90px 0 0;
55
83
  }
56
84
  ${mq.range({ until: breakpoints.mobileWide })} {
57
- padding: 0 50px 0 0;
85
+ padding: 0 5px 0 0;
86
+ font-size: 16px;
58
87
  }
59
88
  `;
60
- const TextWrapper = styled.div`
89
+ const TextWrapper = styled.div<WrapperProps>`
61
90
  display: flex;
62
91
  align-items: center;
63
92
  flex-wrap: wrap;
@@ -67,88 +96,80 @@ const TextWrapper = styled.div`
67
96
  }
68
97
  `;
69
98
 
70
- const IconWrapper = styled.div`
99
+ const IconWrapper = styled.div<WrapperProps>`
71
100
  padding-right: ${spacing.small};
72
- display: flex;
101
+ display: ${(props) => StyleByType(props.boxType).display};
73
102
  align-items: flex-start;
74
103
  ${mq.range({ from: breakpoints.tabletWide })} {
75
104
  padding-top: 4px;
76
105
  }
77
106
  `;
78
107
 
79
- const CloseButtonWrapper = styled.div`
80
- position: absolute;
81
- top: 13px;
82
- right: ${spacing.small};
83
- display: flex;
84
- justify-content: flex-end;
85
- ${mq.range({ from: breakpoints.tabletWide })} {
86
- top: 16px;
87
- }
88
- `;
89
-
90
- const CloseButton = styled(Button)`
91
- display: flex;
92
- justify-content: center;
93
- align-items: center;
94
- font-weight: ${fonts.weight.semibold};
95
- font-size: 16px;
96
- color: ${(props) => colorsByType(props.boxType).color};
97
- &:hover {
98
- color: ${(props) => colorsByType(props.boxType).color};
99
- }
108
+ const LinkWrapper = styled.div`
109
+ display: block;
110
+ width: 100%;
111
+ background-color: #f9f4c8;
112
+ padding-bottom: 20px;
113
+ margin-top: -2px;
114
+ padding-left: 58px;
115
+ border-radius: 0px 0px 5px 5px;
100
116
  ${mq.range({ until: breakpoints.mobileWide })} {
101
- flex-direction: column-reverse;
117
+ padding-left: 45px;
102
118
  }
103
119
  `;
104
-
105
- const CloseButtonText = styled.span`
106
- margin-right: ${spacing.small};
107
- line-height: 1;
120
+ const Link = styled.a`
121
+ color: #20588f;
122
+ font-size: 16px;
123
+ padding-bottom: 1px;
124
+ margin: 0px 40px 1px 2px;
125
+ font-family: ${fonts.sans};
108
126
  ${mq.range({ until: breakpoints.mobileWide })} {
109
- margin-right: 0;
110
- ${fonts.sizes('14px', '18px')};
111
- font-weight: ${fonts.weight.normal};
127
+ margin: 0px 15px 1px 5px;
128
+ box-shadow: none;
112
129
  }
113
130
  `;
114
131
 
132
+ type LinkProps = {
133
+ href?: string;
134
+ text?: string;
135
+ };
115
136
  type Props = {
116
137
  type?: WrapperProps['boxType'];
117
- heading?: string;
118
138
  sticky?: boolean;
119
- onClose?: () => void;
120
- children: React.ReactNode;
139
+ onClose?: boolean;
140
+ children?: string;
141
+ links?: LinkProps[];
121
142
  };
122
143
 
123
- export const MessageBox = ({
124
- heading,
125
- type = 'info',
126
- sticky = true,
127
- onClose,
128
- children,
129
- t,
130
- }: Props & WithTranslation) => (
131
- <Sticky disabled={!sticky} stickyStyle={{ zIndex: 9999 }}>
132
- <Wrapper boxType={type}>
133
- <InfoWrapper>
134
- <IconWrapper>
135
- <InformationOutline style={{ width: '24px', height: '24px' }} />
136
- </IconWrapper>
137
- <TextWrapper>
138
- {heading && <Label>{heading}</Label>}
139
- {children}
140
- </TextWrapper>
141
- </InfoWrapper>
142
- {onClose && (
143
- <CloseButtonWrapper>
144
- <CloseButton link onClick={onClose}>
145
- <CloseButtonText>{t('close')}</CloseButtonText>
146
- <Cross style={{ width: '24px', height: '24px' }} />
147
- </CloseButton>
148
- </CloseButtonWrapper>
144
+ export const MessageBox = ({ type, sticky = false, onClose, children, links, t }: Props & WithTranslation) => {
145
+ const [hideMessageBox, setHideMessageBox] = useState(false);
146
+ const onCloseMessageBox = () => {
147
+ setHideMessageBox(true);
148
+ };
149
+ const Icon = type === 'ghost' ? HumanMaleBoard : InformationOutline;
150
+ return (
151
+ //StickyStyle top:84 makes sure that the messagebox sits beneath the masthead (header ) and the topOffsett sets it so that it applies when reaching the top of the messagebox
152
+ <Sticky disabled={!sticky} stickyStyle={{ zIndex: 9998, top: 84 }} topOffset={-84}>
153
+ <Wrapper boxType={type} style={{ display: hideMessageBox ? 'none' : 'flex' }}>
154
+ <InfoWrapper boxType={type}>
155
+ <IconWrapper boxType={type}>
156
+ <Icon style={{ width: '24px', height: '24px' }} />
157
+ </IconWrapper>
158
+ <TextWrapper>{children}</TextWrapper>
159
+ </InfoWrapper>
160
+ {onClose && <CloseButton onClick={onCloseMessageBox} color="#444444" />}
161
+ </Wrapper>
162
+
163
+ {links && (
164
+ //loops through the links passed in as properties if there are any and creates a working link for each of them
165
+ <LinkWrapper style={{ display: hideMessageBox ? 'none' : 'flex' }}>
166
+ {links.map((x) => (
167
+ <Link href={x.href}>{x.text}</Link>
168
+ ))}
169
+ </LinkWrapper>
149
170
  )}
150
- </Wrapper>
151
- </Sticky>
152
- );
171
+ </Sticky>
172
+ );
173
+ };
153
174
 
154
175
  export default withTranslation()(MessageBox);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Copyright (c) 2022-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 { useTranslation } from 'react-i18next';
11
+
12
+ import styled from '@emotion/styled';
13
+
14
+ const MiniMesssage = styled.div`
15
+ background-color: #f9f4c8;
16
+ text-align: center;
17
+ display: inline-block;
18
+ padding: 5px;
19
+ text-transform: uppercase;
20
+ font-weight: 600;
21
+ font-size: 14px;
22
+ margin-left: 10px;
23
+ margin-bottom: 10px;
24
+ border-radius: 5px;
25
+ `;
26
+
27
+ type Props = {
28
+ tagMessage: string;
29
+ };
30
+ export const MessageBoxTag = ({ tagMessage }: Props) => {
31
+ const { t } = useTranslation();
32
+ return <MiniMesssage>{t(tagMessage)}</MiniMesssage>;
33
+ };
34
+
35
+ export default MessageBoxTag;
@@ -7,5 +7,7 @@
7
7
  */
8
8
 
9
9
  import MessageBox from './MessageBox';
10
+ import MessageBoxTag from './MessageBoxTag';
11
+ import { MessageBoxType } from './MessageBox';
10
12
 
11
- export { MessageBox };
13
+ export { MessageBox, MessageBoxTag, MessageBoxType };
@@ -0,0 +1,265 @@
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
+ // @ts-ignore
11
+ import { SwipeEventData, useSwipeable } from 'react-swipeable';
12
+ import BEMHelper from 'react-bem-helper';
13
+ import SafeLink from '@ndla/safelink';
14
+ import { OneColumn } from '../Layout';
15
+ import Spinner from '../Spinner';
16
+ import { NDLAMovie } from './interfaces';
17
+ import NavigationArrow from './NavigationArrow';
18
+ import SlideshowIndicator from './SlideshowIndicator';
19
+
20
+ interface Props {
21
+ autoSlide: boolean;
22
+ randomStart: boolean;
23
+ slideshow: NDLAMovie[];
24
+ slideInterval: number;
25
+ }
26
+
27
+ const classes = new BEMHelper({
28
+ name: 'film-slideshow',
29
+ prefix: 'c-',
30
+ });
31
+
32
+ const defaultTransitionSwipeEnd = 'transform 600ms cubic-bezier(0, 0.76, 0.09, 1)';
33
+ const defaultTransitionText = 'opacity 600ms ease';
34
+
35
+ const renderSlideItem = (slide: NDLAMovie) => (
36
+ <div
37
+ {...classes('item')}
38
+ key={slide.id}
39
+ role="img"
40
+ aria-label={(slide.metaImage && slide.metaImage.alt) || ''}
41
+ style={{
42
+ backgroundImage: `url(${(slide.metaImage && slide.metaImage.url) || ''})`,
43
+ }}
44
+ />
45
+ );
46
+
47
+ const FilmSlideshow = ({ autoSlide = false, slideshow = [], slideInterval = 5000 }: Props) => {
48
+ const [swipeDistance, setSwipeDistance] = useState(0);
49
+ const [slideIndex, setSlideIndex] = useState(0);
50
+ const [slideIndexTarget, setSlideIndexTarget] = useState(0);
51
+ const [animationComplete, setAnimationComplete] = useState(true);
52
+ const slideRef = useRef<HTMLDivElement>(null);
53
+ const slideText = useRef<HTMLDivElement>(null);
54
+ let timer = useRef<ReturnType<typeof setTimeout> | null>(null);
55
+
56
+ const gotoSlide = useCallback((indexTarget: number, useAnimation = false) => {
57
+ setSwipeDistance(0);
58
+ if (timer.current) {
59
+ clearTimeout(timer.current);
60
+ }
61
+ setSlideIndexTarget(indexTarget);
62
+ setAnimationComplete(!useAnimation);
63
+ }, []);
64
+
65
+ const onChangedSlide = () => {
66
+ if (!animationComplete) {
67
+ if (slideRef.current) {
68
+ slideRef.current.style.transition = 'none';
69
+ slideRef.current.style.transform = `translateX(${slideIndexTarget * 100}vw))`;
70
+ }
71
+
72
+ setAnimationComplete(true);
73
+ setSlideIndex(slideIndexTarget);
74
+ } else if (slideIndexTarget === -1) {
75
+ if (slideRef.current) {
76
+ // Go to last slide for continuous loop
77
+ slideRef.current.style.transition = 'none';
78
+ slideRef.current.style.transform = `translateX(${slideshow.length * 100}vw))`;
79
+ }
80
+
81
+ setSlideIndex(slideshow.length - 1);
82
+ setSlideIndexTarget(slideshow.length - 1);
83
+ setAnimationComplete(true);
84
+ } else if (slideIndexTarget === slideshow.length) {
85
+ if (slideRef.current) {
86
+ // Go to first slide for continuous loop
87
+ slideRef.current.style.transition = 'none';
88
+ slideRef.current.style.transform = `translateX(100vw))`;
89
+ }
90
+ setSlideIndex(0);
91
+ setSlideIndexTarget(0);
92
+ setAnimationComplete(true);
93
+ } else {
94
+ setAnimationComplete(true);
95
+ setSlideIndex(slideIndexTarget);
96
+ }
97
+ };
98
+
99
+ const onSwipeEnd = () => {
100
+ let slide;
101
+ if (swipeDistance > 40) {
102
+ slide = -1;
103
+ } else if (swipeDistance < -40) {
104
+ slide = 1;
105
+ } else {
106
+ slide = 0;
107
+ }
108
+ if (slideRef.current && slideText.current) {
109
+ slideRef.current.style.transition = defaultTransitionSwipeEnd;
110
+ slideText.current.style.transition = defaultTransitionText;
111
+ slideText.current.style.opacity = '1';
112
+ }
113
+ setSwipeDistance(0);
114
+
115
+ initTimer();
116
+
117
+ if (slide !== 0) {
118
+ setSlideIndex(slideIndex + slide);
119
+ setSlideIndexTarget(slideIndex + slide);
120
+ } else {
121
+ // Reset transfrom
122
+ if (slideRef.current) {
123
+ slideRef.current.style.transform = getSlidePosition(slideIndex + slide);
124
+ }
125
+ }
126
+ };
127
+
128
+ const onSwipe = (eventData: SwipeEventData) => {
129
+ if (eventData.dir === 'Up' || eventData.dir === 'Down') {
130
+ return;
131
+ }
132
+ if (timer.current) {
133
+ clearTimeout(timer.current);
134
+ }
135
+ setSwipeDistance(eventData.deltaX);
136
+ if (slideRef && slideRef.current) {
137
+ slideRef.current.style.transition = 'none';
138
+ slideRef.current.style.transform = getSlidePosition(slideIndexTarget);
139
+ }
140
+ const opacityText = 1 - Math.min(100, Math.abs(swipeDistance)) / 100;
141
+ if (slideText && slideText.current) {
142
+ slideText.current.style.transition = 'none';
143
+ slideText.current.style.opacity = opacityText.toString();
144
+ }
145
+ };
146
+
147
+ const onTransitionEnd = () => {
148
+ const slideshowLength = slideshow.length;
149
+ if (slideIndex === -1) {
150
+ if (slideRef.current) {
151
+ slideRef.current.style.transition = 'none';
152
+ slideRef.current.style.transform = getSlidePosition(slideshowLength - 1);
153
+ }
154
+ setSlideIndex(slideshowLength - 1);
155
+ setSlideIndexTarget(slideshowLength - 1);
156
+ } else if (slideIndex >= slideshowLength) {
157
+ if (slideRef.current) {
158
+ slideRef.current.style.transition = 'none';
159
+ slideRef.current.style.transform = getSlidePosition(0);
160
+ }
161
+ setSlideIndex(0);
162
+ setSlideIndexTarget(0);
163
+ }
164
+ };
165
+
166
+ const getSlidePosition = (target: number) => {
167
+ if (swipeDistance !== 0) {
168
+ return `translateX(calc(${swipeDistance}px -
169
+ ${(target + 1) * 100}vw))`;
170
+ }
171
+ return `translateX(-${(target + 1) * 100}vw)`;
172
+ };
173
+
174
+ const initTimer = useCallback(() => {
175
+ if (autoSlide) {
176
+ timer.current = setTimeout(() => {
177
+ gotoSlide(slideIndex + 1);
178
+ }, slideInterval);
179
+ }
180
+ }, [autoSlide, gotoSlide, slideInterval, slideIndex]);
181
+
182
+ useEffect(() => {
183
+ initTimer();
184
+ }, [initTimer]);
185
+
186
+ const handlers = useSwipeable({
187
+ onSwiped: onSwipeEnd,
188
+ onSwiping: onSwipe,
189
+ });
190
+
191
+ if (slideshow.length === 0) {
192
+ return (
193
+ <div>
194
+ <div {...classes('slideshow')}>
195
+ <Spinner inverted />
196
+ </div>
197
+ </div>
198
+ );
199
+ }
200
+
201
+ const slideshowWidth = `${(slideshow.length + 2) * 100}vw`;
202
+ let activeSlide = slideIndex;
203
+ if (activeSlide < 0) {
204
+ activeSlide = slideshow.length - 1;
205
+ } else if (activeSlide >= slideshow.length) {
206
+ activeSlide = 0;
207
+ }
208
+
209
+ return (
210
+ <section {...classes('')} {...handlers}>
211
+ <>
212
+ <div {...classes('slide-link-wrapper')}>
213
+ <OneColumn>
214
+ <SafeLink
215
+ to={slideshow[activeSlide].path}
216
+ {...classes('item-wrapper', 'text', {
217
+ out: !animationComplete,
218
+ })}>
219
+ <div {...classes('slide-info')} ref={slideText}>
220
+ <h1>{slideshow[activeSlide].title}</h1>
221
+ <p>{slideshow[activeSlide].metaDescription}</p>
222
+ </div>
223
+ </SafeLink>
224
+ </OneColumn>
225
+ </div>
226
+ <NavigationArrow
227
+ slideIndexTarget={slideIndexTarget > 0 ? slideIndexTarget - 1 : slideshow.length - 1}
228
+ gotoSlide={gotoSlide}
229
+ />
230
+ <NavigationArrow
231
+ slideIndexTarget={slideIndexTarget < slideshow.length - 1 ? slideIndexTarget + 1 : 0}
232
+ gotoSlide={gotoSlide}
233
+ rightArrow
234
+ />
235
+ {!animationComplete && (
236
+ <div
237
+ {...classes('item', 'fade-over')}
238
+ role="img"
239
+ onAnimationEnd={onChangedSlide}
240
+ style={{
241
+ backgroundImage: `url(${
242
+ (slideshow[activeSlide].metaImage && slideshow[activeSlide].metaImage.url) || ''
243
+ })`,
244
+ }}
245
+ />
246
+ )}
247
+ <div
248
+ ref={slideRef}
249
+ {...classes('item-wrapper')}
250
+ onTransitionEnd={onTransitionEnd}
251
+ style={{
252
+ width: slideshowWidth,
253
+ transform: getSlidePosition(slideIndex),
254
+ }}>
255
+ {renderSlideItem(slideshow[slideshow.length - 1])}
256
+ {slideshow.map(renderSlideItem)}
257
+ {renderSlideItem(slideshow[0])}
258
+ </div>
259
+ <SlideshowIndicator slideshow={slideshow} activeSlide={activeSlide} gotoSlide={gotoSlide} />
260
+ </>
261
+ </section>
262
+ );
263
+ };
264
+
265
+ 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;
@@ -0,0 +1,10 @@
1
+ export interface NDLAMovie {
2
+ id: string;
3
+ metaDescription: string;
4
+ title: string;
5
+ metaImage: {
6
+ url: string;
7
+ alt: string;
8
+ };
9
+ path: string;
10
+ }
File without changes