@ndla/ui 4.3.2 → 5.0.0

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 (162) hide show
  1. package/README.md +11 -11
  2. package/es/Article/Article.js +3 -7
  3. package/es/Article/ArticleByline.js +4 -4
  4. package/es/Article/ArticleNotions.js +37 -92
  5. package/es/ContentTypeBadge/ContentTypeBadge.js +10 -1
  6. package/es/FactBox/FactBox.js +36 -11
  7. package/es/Filter/FilterButtons.js +18 -17
  8. package/es/Filter/FilterCarousel.js +198 -0
  9. package/es/Masthead/Masthead.js +24 -2
  10. package/es/Notion/ConceptNotion.js +57 -0
  11. package/es/Notion/FigureNotion.js +82 -0
  12. package/es/Notion/Notion.js +122 -48
  13. package/es/Notion/NotionImage.js +47 -0
  14. package/es/Notion/NotionVisualElement.js +38 -0
  15. package/es/Notion/index.js +2 -1
  16. package/es/SearchTypeResult/ActiveFilterContent.js +17 -2
  17. package/es/SearchTypeResult/ActiveFilters.js +64 -50
  18. package/es/SearchTypeResult/PopupFilter.js +28 -125
  19. package/es/SearchTypeResult/ResultNavigation.js +41 -14
  20. package/es/SearchTypeResult/SearchFieldHeader.js +20 -41
  21. package/es/SearchTypeResult/SearchFilterContent.js +61 -0
  22. package/es/SearchTypeResult/SearchHeader.js +51 -28
  23. package/es/SearchTypeResult/SearchItem.js +64 -190
  24. package/es/SearchTypeResult/SearchItemList.js +132 -0
  25. package/es/SearchTypeResult/SearchItems.js +17 -13
  26. package/es/SearchTypeResult/SearchNotionItem.js +13 -13
  27. package/es/SearchTypeResult/SearchNotionsResult.js +16 -21
  28. package/es/SearchTypeResult/SearchTypeHeader.js +43 -26
  29. package/es/SearchTypeResult/SearchTypeResult.js +7 -5
  30. package/es/SearchTypeResult/SearchViewType.js +93 -0
  31. package/es/SearchTypeResult/components/ItemContexts.js +125 -0
  32. package/es/SearchTypeResult/components/ItemResourceHeader.js +87 -0
  33. package/es/SearchTypeResult/components/ItemTopicHeader.js +56 -0
  34. package/es/SearchTypeResult/components/SubjectFilters.js +177 -0
  35. package/es/SearchTypeResult/index.js +2 -1
  36. package/es/all.css +1 -1
  37. package/es/index.js +3 -2
  38. package/es/locale/messages-en.js +20 -5
  39. package/es/locale/messages-nb.js +21 -6
  40. package/es/locale/messages-nn.js +21 -6
  41. package/es/model/ContentType.js +2 -1
  42. package/es/shapes.js +1 -1
  43. package/lib/Article/Article.d.ts +4 -5
  44. package/lib/Article/Article.js +3 -7
  45. package/lib/Article/ArticleByline.js +4 -4
  46. package/lib/Article/ArticleNotions.d.ts +3 -8
  47. package/lib/Article/ArticleNotions.js +41 -90
  48. package/lib/ContentTypeBadge/ContentTypeBadge.d.ts +1 -0
  49. package/lib/ContentTypeBadge/ContentTypeBadge.js +14 -2
  50. package/lib/FactBox/FactBox.js +41 -8
  51. package/lib/Filter/FilterButtons.d.ts +3 -10
  52. package/lib/Filter/FilterButtons.js +19 -17
  53. package/lib/Filter/FilterCarousel.d.ts +13 -0
  54. package/lib/Filter/FilterCarousel.js +207 -0
  55. package/lib/Masthead/Masthead.js +30 -2
  56. package/lib/Notion/ConceptNotion.d.ts +25 -0
  57. package/lib/Notion/ConceptNotion.js +79 -0
  58. package/lib/Notion/FigureNotion.d.ts +23 -0
  59. package/lib/Notion/FigureNotion.js +97 -0
  60. package/lib/Notion/Notion.d.ts +24 -11
  61. package/lib/Notion/Notion.js +120 -48
  62. package/lib/Notion/NotionImage.d.ts +15 -0
  63. package/lib/Notion/NotionImage.js +63 -0
  64. package/lib/Notion/NotionVisualElement.d.ts +22 -0
  65. package/lib/Notion/NotionVisualElement.js +51 -0
  66. package/lib/Notion/index.d.ts +1 -0
  67. package/lib/Notion/index.js +8 -0
  68. package/lib/SearchTypeResult/ActiveFilterContent.js +16 -9
  69. package/lib/SearchTypeResult/ActiveFilters.d.ts +2 -1
  70. package/lib/SearchTypeResult/ActiveFilters.js +65 -50
  71. package/lib/SearchTypeResult/PopupFilter.d.ts +13 -19
  72. package/lib/SearchTypeResult/PopupFilter.js +27 -123
  73. package/lib/SearchTypeResult/ResultNavigation.d.ts +2 -2
  74. package/lib/SearchTypeResult/ResultNavigation.js +38 -14
  75. package/lib/SearchTypeResult/SearchFieldHeader.d.ts +3 -8
  76. package/lib/SearchTypeResult/SearchFieldHeader.js +18 -39
  77. package/lib/SearchTypeResult/SearchFilterContent.d.ts +16 -0
  78. package/lib/SearchTypeResult/SearchFilterContent.js +67 -0
  79. package/lib/SearchTypeResult/SearchHeader.d.ts +3 -7
  80. package/lib/SearchTypeResult/SearchHeader.js +59 -30
  81. package/lib/SearchTypeResult/SearchItem.d.ts +8 -12
  82. package/lib/SearchTypeResult/SearchItem.js +64 -187
  83. package/lib/SearchTypeResult/SearchItemList.d.ts +10 -0
  84. package/lib/SearchTypeResult/SearchItemList.js +139 -0
  85. package/lib/SearchTypeResult/SearchItems.d.ts +4 -3
  86. package/lib/SearchTypeResult/SearchItems.js +18 -13
  87. package/lib/SearchTypeResult/SearchNotionItem.js +13 -13
  88. package/lib/SearchTypeResult/SearchNotionsResult.d.ts +2 -4
  89. package/lib/SearchTypeResult/SearchNotionsResult.js +23 -23
  90. package/lib/SearchTypeResult/SearchTypeHeader.d.ts +2 -2
  91. package/lib/SearchTypeResult/SearchTypeHeader.js +40 -25
  92. package/lib/SearchTypeResult/SearchTypeResult.d.ts +7 -6
  93. package/lib/SearchTypeResult/SearchTypeResult.js +7 -5
  94. package/lib/SearchTypeResult/SearchViewType.d.ts +13 -0
  95. package/lib/SearchTypeResult/SearchViewType.js +99 -0
  96. package/lib/SearchTypeResult/components/ItemContexts.d.ts +19 -0
  97. package/lib/SearchTypeResult/components/ItemContexts.js +134 -0
  98. package/lib/SearchTypeResult/components/ItemResourceHeader.d.ts +16 -0
  99. package/lib/SearchTypeResult/components/ItemResourceHeader.js +98 -0
  100. package/lib/SearchTypeResult/components/ItemTopicHeader.d.ts +17 -0
  101. package/lib/SearchTypeResult/components/ItemTopicHeader.js +67 -0
  102. package/lib/SearchTypeResult/components/SubjectFilters.d.ts +32 -0
  103. package/lib/SearchTypeResult/components/SubjectFilters.js +192 -0
  104. package/lib/SearchTypeResult/index.d.ts +2 -1
  105. package/lib/SearchTypeResult/index.js +8 -0
  106. package/lib/all.css +1 -1
  107. package/lib/index.d.ts +2 -1
  108. package/lib/index.js +24 -1
  109. package/lib/locale/messages-en.d.ts +16 -1
  110. package/lib/locale/messages-en.js +20 -5
  111. package/lib/locale/messages-nb.d.ts +15 -0
  112. package/lib/locale/messages-nb.js +21 -6
  113. package/lib/locale/messages-nn.d.ts +16 -1
  114. package/lib/locale/messages-nn.js +21 -6
  115. package/lib/model/ContentType.d.ts +1 -0
  116. package/lib/model/ContentType.js +4 -2
  117. package/lib/shapes.js +1 -1
  118. package/lib/types.d.ts +1 -0
  119. package/package.json +14 -14
  120. package/src/Article/Article.tsx +8 -15
  121. package/src/Article/ArticleByline.tsx +1 -1
  122. package/src/Article/ArticleNotions.tsx +13 -33
  123. package/src/ContentTypeBadge/ContentTypeBadge.tsx +8 -0
  124. package/src/ContentTypeBadge/component.content-type-badge.scss +5 -0
  125. package/src/FactBox/FactBox.tsx +22 -15
  126. package/src/Figure/component.figure.scss +1 -1
  127. package/src/Filter/FilterButtons.tsx +14 -15
  128. package/src/Filter/FilterCarousel.tsx +166 -0
  129. package/src/Masthead/Masthead.tsx +42 -18
  130. package/src/Notion/ConceptNotion.tsx +80 -0
  131. package/src/Notion/FigureNotion.tsx +86 -0
  132. package/src/Notion/Notion.tsx +205 -75
  133. package/src/Notion/NotionImage.tsx +51 -0
  134. package/src/Notion/NotionVisualElement.tsx +50 -0
  135. package/src/Notion/index.ts +1 -0
  136. package/src/SearchTypeResult/ActiveFilterContent.tsx +7 -2
  137. package/src/SearchTypeResult/ActiveFilters.tsx +72 -38
  138. package/src/SearchTypeResult/PopupFilter.tsx +73 -146
  139. package/src/SearchTypeResult/ResultNavigation.tsx +54 -16
  140. package/src/SearchTypeResult/SearchFieldHeader.tsx +15 -40
  141. package/src/SearchTypeResult/SearchFilterContent.tsx +63 -0
  142. package/src/SearchTypeResult/SearchHeader.tsx +31 -31
  143. package/src/SearchTypeResult/SearchItem.tsx +145 -233
  144. package/src/SearchTypeResult/SearchItemList.tsx +167 -0
  145. package/src/SearchTypeResult/SearchItems.tsx +26 -19
  146. package/src/SearchTypeResult/SearchNotionItem.tsx +5 -12
  147. package/src/SearchTypeResult/SearchNotionsResult.tsx +29 -22
  148. package/src/SearchTypeResult/SearchTypeHeader.tsx +51 -33
  149. package/src/SearchTypeResult/SearchTypeResult.tsx +13 -12
  150. package/src/SearchTypeResult/SearchViewType.tsx +109 -0
  151. package/src/SearchTypeResult/components/ItemContexts.tsx +138 -0
  152. package/src/SearchTypeResult/components/ItemResourceHeader.tsx +133 -0
  153. package/src/SearchTypeResult/components/ItemTopicHeader.tsx +95 -0
  154. package/src/SearchTypeResult/components/SubjectFilters.tsx +152 -0
  155. package/src/SearchTypeResult/index.ts +9 -1
  156. package/src/index.ts +3 -0
  157. package/src/locale/messages-en.ts +18 -3
  158. package/src/locale/messages-nb.ts +19 -4
  159. package/src/locale/messages-nn.ts +19 -4
  160. package/src/model/ContentType.ts +1 -0
  161. package/src/shapes.js +1 -0
  162. package/src/types.ts +1 -0
@@ -0,0 +1,86 @@
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
+ import Button from '@ndla/button';
9
+ import { getGroupedContributorDescriptionList, getLicenseByAbbreviation } from '@ndla/licenses';
10
+ import React, { ReactNode } from 'react';
11
+ import { useTranslation } from 'react-i18next';
12
+ import { Figure, FigureCaption, FigureLicenseDialog } from '..';
13
+ import { Copyright } from '../types';
14
+
15
+ interface Props {
16
+ resizeIframe?: boolean;
17
+ figureId: string;
18
+ children: ReactNode | ((params: { typeClass: string }) => ReactNode);
19
+ id: string;
20
+ title: string;
21
+ copyright?: Partial<Copyright>;
22
+ licenseString: string;
23
+ type: 'video' | 'h5p' | 'image' | 'concept';
24
+ hideFigCaption?: boolean;
25
+ }
26
+
27
+ const FigureNotion = ({
28
+ resizeIframe,
29
+ figureId,
30
+ children,
31
+ id,
32
+ copyright,
33
+ licenseString,
34
+ title,
35
+ type,
36
+ hideFigCaption,
37
+ }: Props) => {
38
+ const { t, i18n } = useTranslation();
39
+ const license = getLicenseByAbbreviation(licenseString, i18n.language);
40
+ const { creators, processors, rightsholders } = copyright ?? {};
41
+ const contributors = getGroupedContributorDescriptionList(
42
+ {
43
+ creators: creators ?? [],
44
+ processors: processors ?? [],
45
+ rightsholders: rightsholders ?? [],
46
+ },
47
+ i18n.language,
48
+ ).map((i) => ({ name: i.description, type: i.label }));
49
+
50
+ return (
51
+ <Figure resizeIframe={resizeIframe} id={figureId} type={'full-column'}>
52
+ {({ typeClass }) => (
53
+ <>
54
+ {typeof children === 'function' ? children({ typeClass }) : children}
55
+ <FigureCaption
56
+ hideFigcaption={hideFigCaption}
57
+ figureId={figureId}
58
+ id={id}
59
+ caption={title}
60
+ reuseLabel={t(`${type}.reuse`)}
61
+ authors={contributors}
62
+ licenseRights={license.rights}>
63
+ <FigureLicenseDialog
64
+ id={id}
65
+ authors={contributors}
66
+ locale={i18n.language}
67
+ title={title}
68
+ origin={copyright?.origin}
69
+ license={license}
70
+ messages={{
71
+ close: t('close'),
72
+ rulesForUse: t('license.concept.rules'),
73
+ source: t('source'),
74
+ learnAboutLicenses: t('license.learnMore'),
75
+ title: t('title'),
76
+ }}>
77
+ {type === 'image' && <Button outline>{t('license.copyTitle')}</Button>}
78
+ </FigureLicenseDialog>
79
+ </FigureCaption>
80
+ </>
81
+ )}
82
+ </Figure>
83
+ );
84
+ };
85
+
86
+ export default FigureNotion;
@@ -1,118 +1,248 @@
1
+ /**
2
+ * Copyright (c) 2021-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
+
1
9
  import styled from '@emotion/styled';
2
10
  import { useTranslation } from 'react-i18next';
3
11
  import HTMLReactParser from 'html-react-parser';
4
- import React, { MouseEventHandler, ReactNode } from 'react';
12
+ import React, { Fragment, ReactNode } from 'react';
13
+ import { keyframes } from '@emotion/core';
5
14
  import Button from '@ndla/button';
6
- import { joinArrayWithConjunction } from '@ndla/util';
7
- import { colors, fonts } from '@ndla/core';
8
- import { getLicenseByAbbreviation } from '@ndla/licenses';
9
- import { Locale } from '../types';
10
-
11
- const NotionContainer = styled.div`
12
- border-bottom: 1px solid ${colors.brand.greyLighter};
13
- margin-bottom: 3.5rem;
14
- `;
15
+ import { animations, breakpoints, colors, fonts, mq, spacing } from '@ndla/core';
16
+ import { CursorClick } from '@ndla/icons/action';
17
+ import { Play, ArrowCollapse } from '@ndla/icons/common';
18
+ import { ImageCrop, ImageFocalPoint, makeSrcQueryString } from '../Image';
15
19
 
16
- const AuthorsContainer = styled.div`
17
- display: flex;
18
- flex-wrap: wrap;
19
- margin: 0 0 1.5rem;
20
- font-size: 0.875rem;
20
+ const NotionContainer = styled.div``;
21
21
 
22
- p {
22
+ const ContentWrapper = styled.div`
23
+ ${mq.range({ until: breakpoints.tabletWide })} {
24
+ display: flex;
25
+ flex-direction: column-reverse;
26
+ }
27
+ .c-figure {
23
28
  margin: 0;
24
-
25
- &:not(:only-child) {
26
- margin-right: 1rem;
29
+ position: relative !important;
30
+ left: 0 !important;
31
+ width: 25% !important;
32
+ padding: 0 0 0 20px;
33
+ float: right;
34
+ &.expanded {
35
+ width: 100% !important;
36
+ padding: 0;
37
+ margin-bottom: ${spacing.normal};
38
+ }
39
+ ${mq.range({ until: breakpoints.tabletWide })} {
40
+ width: 100% !important;
41
+ padding: 0;
27
42
  }
28
43
  }
29
44
  `;
45
+ const TextWrapper = styled.div`
46
+ width: 75%;
47
+ ${mq.range({ until: breakpoints.tabletWide })} {
48
+ width: 100%;
49
+ }
50
+ font-family: ${fonts.serif};
51
+ ${fonts.sizes('18px', '28px')};
52
+ ${ContentWrapper} .c-figure.expanded + & {
53
+ width: 100%;
54
+ }
55
+ `;
30
56
 
31
- const LabelsContainer = styled.div`
32
- display: flex;
57
+ const ImageElement = styled.img``;
58
+
59
+ const fadeInMediaKeyframe = keyframes`
60
+ 0% {
61
+ opacity: 0;
62
+ height: auto;
63
+ }
64
+ 100% {
65
+ opacity: 1;
66
+ }
67
+ `;
68
+ const fadeOutMediaKeyframe = keyframes`
69
+ 0% {
70
+ opacity: 1;
71
+ height: auto;
72
+ }
73
+ 100% {
74
+ opacity: 0;
75
+ height:0;
76
+ overflow: hidden;
77
+ }
78
+ `;
79
+
80
+ const ImageWrapper = styled.div`
81
+ float: right;
82
+ width: 25%;
83
+ padding-left: ${spacing.normal};
84
+ position: relative;
85
+
86
+ ${mq.range({ until: breakpoints.tabletWide })} {
87
+ width: 100%;
88
+ padding-left: 0;
89
+ }
90
+ `;
91
+
92
+ const ExpandVisualElementButton = styled(Button)`
93
+ position: absolute;
94
+ right: 8px;
95
+ bottom: 8px;
96
+ transition: all ${animations.durations.normal};
97
+ &,
98
+ &:focus,
99
+ &:active {
100
+ background-color: rgba(255, 255, 255, 0.65);
101
+ }
102
+
103
+ color: ${colors.brand.primary};
104
+ border-radius: 50%;
105
+ border: 0;
106
+ width: 40px;
107
+ height: 40px;
108
+ display: inline-flex;
109
+ justify-content: center;
33
110
  align-items: center;
34
- font-size: 0.875rem;
35
- margin-bottom: 1rem;
36
111
 
37
- p {
38
- margin: 0;
112
+ svg {
113
+ transition: transform ${animations.durations.normal} ease-out;
114
+ }
115
+ ${ContentWrapper}:hover & {
116
+ background-color: #fff;
117
+ svg {
118
+ transform: scale(1.2);
119
+ }
120
+ }
121
+ `;
122
+
123
+ const ExpandIcon = styled.span`
124
+ ${ExpandVisualElementButton}.expanded & {
125
+ display: none;
126
+ }
127
+ `;
128
+ const CollapseIcon = styled.span`
129
+ display: none;
130
+ ${ExpandVisualElementButton}.expanded & {
131
+ display: inline-block;
39
132
  }
133
+ `;
134
+
135
+ const ClearWrapper = styled.div`
136
+ clear: both;
137
+ `;
40
138
 
41
- > :not(:last-child) {
42
- margin-right: 0.5rem;
139
+ const MediaContainer = styled.div`
140
+ opacity: 0;
141
+ height: 0;
142
+ overflow: hidden;
143
+ &.expanded {
144
+ animation-name: ${fadeInMediaKeyframe};
145
+ animation-duration: 2.8s;
146
+ opacity: 1;
147
+ height: auto;
148
+ }
149
+ &.fadeOut {
150
+ animation-name: ${fadeOutMediaKeyframe};
151
+ animation-duration: 2.8s;
43
152
  }
44
153
  `;
45
154
 
46
- const Label = styled.span`
155
+ const LabelsContainer = styled.div`
156
+ display: flex;
157
+ align-items: center;
47
158
  ${fonts.sizes('14px', '24px')};
48
- font-weight: 600;
49
- padding: 0 0.25rem;
50
- background-color: ${colors.brand.greyLightest};
51
- vertical-align: center;
159
+ font-family: ${fonts.sans};
160
+ margin: ${spacing.small} 0;
52
161
  `;
53
162
 
163
+ type VisualElementProps = {
164
+ type: 'video' | 'other';
165
+ element: ReactNode;
166
+ metaImage?: {
167
+ url: string;
168
+ alt: string;
169
+ crop?: ImageCrop;
170
+ focalPoint?: ImageFocalPoint;
171
+ };
172
+ };
173
+
54
174
  export type NotionProps = {
55
- authors?: { name: string }[];
56
- id: string;
175
+ id: string | number;
57
176
  labels?: string[];
58
- license?: string;
59
- locale?: Locale;
60
- media?: ReactNode;
61
- onReferenceClick?: MouseEventHandler<HTMLButtonElement>;
62
177
  renderMarkdown?: (text: string) => string;
63
178
  text: ReactNode;
64
179
  title: string;
180
+ visualElement?: VisualElementProps;
181
+ imageElement?: ReactNode;
182
+ children?: ReactNode;
65
183
  };
66
184
 
67
185
  const Notion = ({
68
- authors = [],
69
186
  id,
70
187
  labels = [],
71
- license,
72
- locale,
73
- media,
74
- onReferenceClick,
75
188
  renderMarkdown,
76
189
  text,
77
190
  title,
191
+ visualElement,
192
+ imageElement,
193
+ children,
78
194
  }: NotionProps) => {
79
195
  const { t } = useTranslation();
196
+
80
197
  return (
81
198
  <NotionContainer>
82
- <div>
83
- {HTMLReactParser(
84
- renderMarkdown ? renderMarkdown(`**${title}** \u2013 ${text}`) : `<b>${title}</b> \u2013 ${text}`,
199
+ {visualElement && <MediaContainer id={`notion-media-${id}`}>{visualElement.element}</MediaContainer>}
200
+ <ContentWrapper>
201
+ {imageElement}
202
+ {visualElement && visualElement.metaImage && (
203
+ <ImageWrapper>
204
+ <ImageElement
205
+ src={`${visualElement.metaImage.url}?${makeSrcQueryString(
206
+ 400,
207
+ visualElement.metaImage.crop,
208
+ visualElement.metaImage.focalPoint,
209
+ )}`}
210
+ alt={visualElement.metaImage.alt}
211
+ />
212
+ <ExpandVisualElementButton
213
+ stripped
214
+ data-notion-expand-media={true}
215
+ data-notion-media-id={`notion-media-${id}`}>
216
+ <ExpandIcon>
217
+ {visualElement.type === 'video' && <Play style={{ width: '24px', height: '24px' }} />}
218
+ {visualElement.type === 'other' && <CursorClick style={{ width: '24px', height: '24px' }} />}
219
+ </ExpandIcon>
220
+ <CollapseIcon>
221
+ <ArrowCollapse style={{ width: '24px', height: '24px' }} />
222
+ </CollapseIcon>
223
+ </ExpandVisualElementButton>
224
+ </ImageWrapper>
85
225
  )}
86
- </div>
87
- {!!media && media}
88
- {!!authors.length && (
89
- <AuthorsContainer>
90
- <p>
91
- {t('article.writtenBy', {
92
- authors: joinArrayWithConjunction(
93
- authors.map((author) => author.name),
94
- {
95
- conjunction: ` ${t('article.conjunction')} `,
96
- },
97
- ),
98
- })}
99
- {license && ` (${getLicenseByAbbreviation(license, locale).abbreviation})`}
100
- </p>
101
- {onReferenceClick && (
102
- <Button link onClick={onReferenceClick}>
103
- {t('article.citeNotion')}
104
- </Button>
226
+ <TextWrapper>
227
+ {HTMLReactParser(
228
+ renderMarkdown ? renderMarkdown(`**${title}** \u2013 ${text}`) : `<b>${title}</b> \u2013 ${text}`,
229
+ )}
230
+ {!!labels.length && (
231
+ <LabelsContainer>
232
+ {t('searchPage.resultType.notionLabels')}
233
+ {labels.map((label, i) => (
234
+ <Fragment key={`notion-${id}-label-${i + 1}`}>
235
+ {' '}
236
+ {label}
237
+ {i < labels?.length - 1 && <> &#8226;</>}
238
+ </Fragment>
239
+ ))}
240
+ </LabelsContainer>
105
241
  )}
106
- </AuthorsContainer>
107
- )}
108
- {!!labels.length && (
109
- <LabelsContainer>
110
- <p>{t('searchPage.resultType.notionLabels')}</p>
111
- {labels.map((label, i) => (
112
- <Label key={`notion-${id}-label-${i + 1}`}>{label}</Label>
113
- ))}
114
- </LabelsContainer>
115
- )}
242
+ </TextWrapper>
243
+ <ClearWrapper />
244
+ </ContentWrapper>
245
+ {children}
116
246
  </NotionContainer>
117
247
  );
118
248
  };
@@ -0,0 +1,51 @@
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
+ import React from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { Image, FigureExpandButton } from '..';
11
+ import { Copyright } from '../types';
12
+ import FigureNotion from './FigureNotion';
13
+ interface Props {
14
+ id: string;
15
+ src: string;
16
+ alt: string;
17
+ imageCopyright?: Partial<Copyright>;
18
+ }
19
+ export const NotionImage = ({ id, src, alt, imageCopyright }: Props) => {
20
+ const { t } = useTranslation();
21
+
22
+ const imageId = `image-${id}`;
23
+ const imageFigureId = `image-figure-${id}`;
24
+
25
+ return (
26
+ <FigureNotion
27
+ hideFigCaption
28
+ figureId={imageFigureId}
29
+ id={imageId}
30
+ title={alt}
31
+ copyright={imageCopyright}
32
+ licenseString={imageCopyright?.license?.license ?? ''}
33
+ type={'image'}>
34
+ {({ typeClass }) => (
35
+ <Image
36
+ alt={alt}
37
+ src={src}
38
+ expandButton={
39
+ <FigureExpandButton
40
+ typeClass={typeClass}
41
+ messages={{
42
+ zoomImageButtonLabel: t('license.images.itemImage.zoomImageButtonLabel'),
43
+ zoomOutImageButtonLabel: t('license.image.itemImage.zoomOutImageButtonLabel'),
44
+ }}
45
+ />
46
+ }
47
+ />
48
+ )}
49
+ </FigureNotion>
50
+ );
51
+ };
@@ -0,0 +1,50 @@
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
+ import React from 'react';
9
+ import { Copyright } from '../types';
10
+ import FigureNotion from './FigureNotion';
11
+
12
+ export type NotionVisualElementType = {
13
+ resource?: string;
14
+ title?: string;
15
+ url?: string;
16
+ copyright?: Partial<Copyright>;
17
+ image?: {
18
+ src: string;
19
+ alt?: string;
20
+ };
21
+ };
22
+
23
+ interface Props {
24
+ visualElement: NotionVisualElementType;
25
+ }
26
+
27
+ const supportedEmbedTypes = ['brightcove', 'h5p'];
28
+ const NotionVisualElement = ({ visualElement }: Props) => {
29
+ const id = '1';
30
+ const figureId = 'figure-1';
31
+ if (!visualElement.resource || !supportedEmbedTypes.includes(visualElement.resource)) {
32
+ return <p>Embed type is not supported!</p>;
33
+ }
34
+
35
+ const type = visualElement.resource === 'brightcove' ? 'video' : 'h5p';
36
+ return (
37
+ <FigureNotion
38
+ resizeIframe
39
+ id={id}
40
+ figureId={figureId}
41
+ title={visualElement.title ?? ''}
42
+ copyright={visualElement.copyright}
43
+ licenseString={visualElement.copyright?.license?.license ?? ''}
44
+ type={type}>
45
+ <iframe title={visualElement.title} src={visualElement.url} />
46
+ </FigureNotion>
47
+ );
48
+ };
49
+
50
+ export default NotionVisualElement;
@@ -1 +1,2 @@
1
1
  export { default as Notion } from './Notion';
2
+ export { default as ConceptNotion } from './ConceptNotion';
@@ -19,6 +19,11 @@ export const StyledActiveFilterTitle = styled.span`
19
19
  font-weight: ${fonts.weight.semibold};
20
20
  `;
21
21
 
22
+ const StyledButton = styled(Button)`
23
+ display: grid;
24
+ grid-template-columns: 1fr auto;
25
+ `;
26
+
22
27
  export type FilterProps = {
23
28
  value: string;
24
29
  title: string;
@@ -31,7 +36,7 @@ type Props = {
31
36
  };
32
37
 
33
38
  const ActiveFilterContent = ({ filter, onFilterRemove, t }: Props & WithTranslation) => (
34
- <Button
39
+ <StyledButton
35
40
  aria-label={t('searchPage.searchFilterMessages.removeFilter', {
36
41
  filterName: filter.title,
37
42
  })}
@@ -41,7 +46,7 @@ const ActiveFilterContent = ({ filter, onFilterRemove, t }: Props & WithTranslat
41
46
  onClick={() => onFilterRemove(filter.value, filter.name)}>
42
47
  <StyledActiveFilterTitle>{filter.title}</StyledActiveFilterTitle>
43
48
  <Cross />
44
- </Button>
49
+ </StyledButton>
45
50
  );
46
51
 
47
52
  export default withTranslation()(ActiveFilterContent);
@@ -7,31 +7,39 @@
7
7
  */
8
8
 
9
9
  import React, { ReactElement } from 'react';
10
+ import { useTranslation } from 'react-i18next';
10
11
  import styled from '@emotion/styled';
11
12
  import css from '@emotion/css';
12
13
  import { spacing, mq, breakpoints } from '@ndla/core';
13
14
  import Tooltip from '@ndla/tooltip';
14
- import { useTranslation } from 'react-i18next';
15
+ import Button from '@ndla/button';
15
16
  import ActiveFilterContent, { FilterProps, StyledActiveFilterTitle } from './ActiveFilterContent';
16
17
 
17
18
  type StyledActiveFiltersProps = {
18
19
  showOnSmallScreen?: boolean;
19
20
  filterLength?: number;
20
21
  };
22
+
21
23
  const StyledActiveFilters = styled.ul<StyledActiveFiltersProps>`
22
24
  margin: 0;
23
25
  padding: 0;
24
26
  display: none;
25
-
26
- ${({ showOnSmallScreen }) =>
27
- showOnSmallScreen &&
28
- `
29
- ${mq.range({ until: breakpoints.desktop })} {
27
+ ${(props: StyledActiveFiltersProps) =>
28
+ props.showOnSmallScreen &&
29
+ css`
30
+ ${StyledActiveFilterTitle} {
31
+ text-overflow: ellipsis;
32
+ overflow: hidden;
33
+ padding-right: ${spacing.small};
34
+ display: block;
35
+ /* max-width: 200px; */
36
+ }
37
+ ${mq.range({ until: breakpoints.desktop })} {
30
38
  display: flex;
31
39
  flex-direction: row;
32
40
  flex-wrap: wrap;
33
41
  }
34
- ${mq.range({ until: breakpoints.tablet })} {
42
+ ${mq.range({ until: breakpoints.tablet })} {
35
43
  display: flex;
36
44
  flex-direction: column;
37
45
  align-items: stretch;
@@ -88,46 +96,72 @@ type Props = {
88
96
  onFilterRemove: (value: string, name: string) => void;
89
97
  showOnSmallScreen?: boolean;
90
98
  customElements?: ReactElement[];
99
+ onClickShowHiddenSubjects: () => void;
91
100
  };
92
101
 
93
- const ActiveFilters = ({ filters, onFilterRemove, showOnSmallScreen, customElements }: Props) => {
102
+ const ActiveFilters = ({
103
+ filters,
104
+ onFilterRemove,
105
+ showOnSmallScreen,
106
+ customElements,
107
+ onClickShowHiddenSubjects,
108
+ }: Props) => {
94
109
  const { t } = useTranslation();
95
- if (filters && filters.length > 0) {
96
- const filterLength = filters.length;
110
+ if (!filters) return null;
111
+ const showFilterCount = 3;
112
+ const filterLength = filters.length;
97
113
 
98
- const filterItems = filters.map((filter) => {
99
- const filterKey = filter.name ? `${filter.name}${filter.value}` : filter.value;
114
+ const visibleFilters = filterLength > showFilterCount ? filters.slice(0, showFilterCount - 1) : filters;
100
115
 
101
- return (
102
- <StyledActiveFilterWrapper key={filterKey}>
103
- {filterLength > 1 ? (
104
- <Tooltip
105
- delay={2000}
106
- align="bottom"
107
- tooltip={t('searchPage.searchFilterMessages.removeFilter', {
108
- filterName: filter.title,
109
- })}>
110
- <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
111
- </Tooltip>
112
- ) : (
113
- <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
114
- )}
115
- </StyledActiveFilterWrapper>
116
- );
117
- });
116
+ const filterItems = visibleFilters.map((filter) => {
117
+ const filterKey = filter.name ? `${filter.name}${filter.value}` : filter.value;
118
118
 
119
119
  return (
120
- <StyledActiveFilters showOnSmallScreen={showOnSmallScreen} filterLength={filterLength}>
121
- {filterItems}
122
- {customElements &&
123
- customElements.map((item, index) => (
124
- <StyledActiveFilterWrapper key={index}>{item}</StyledActiveFilterWrapper>
125
- ))}
126
- </StyledActiveFilters>
120
+ <StyledActiveFilterWrapper key={filterKey}>
121
+ {filterLength > 1 ? (
122
+ <Tooltip
123
+ delay={2000}
124
+ align="bottom"
125
+ tooltip={t('searchPage.searchFilterMessages.removeFilter', {
126
+ filterName: filter.title,
127
+ })}>
128
+ <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
129
+ </Tooltip>
130
+ ) : (
131
+ <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
132
+ )}
133
+ </StyledActiveFilterWrapper>
127
134
  );
128
- }
135
+ });
129
136
 
130
- return null;
137
+ return (
138
+ <StyledActiveFilters showOnSmallScreen={showOnSmallScreen} filterLength={filterLength}>
139
+ {filterItems}
140
+ {filterLength > showFilterCount && (
141
+ <StyledActiveFilterWrapper>
142
+ <Tooltip delay={2000} align="bottom" tooltip={t('searchPage.searchFilterMessages.noValuesButtonText')}>
143
+ <Button
144
+ aria-label={t('searchPage.searchFilterMessages.additionalSubjectFilters', {
145
+ count: filterLength - showFilterCount + 1,
146
+ })}
147
+ type="button"
148
+ size="normal"
149
+ borderShape="rounded"
150
+ onClick={onClickShowHiddenSubjects}>
151
+ <StyledActiveFilterTitle>
152
+ {t('searchPage.searchFilterMessages.additionalSubjectFilters', {
153
+ count: filterLength - showFilterCount + 1,
154
+ })}
155
+ </StyledActiveFilterTitle>
156
+ </Button>
157
+ </Tooltip>
158
+ </StyledActiveFilterWrapper>
159
+ )}
160
+ {customElements?.map((item, index) => (
161
+ <StyledActiveFilterWrapper key={index}>{item}</StyledActiveFilterWrapper>
162
+ ))}
163
+ </StyledActiveFilters>
164
+ );
131
165
  };
132
166
 
133
167
  export default ActiveFilters;