@ndla/ui 36.0.2 → 37.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.
Files changed (174) hide show
  1. package/es/Article/Article.js +7 -13
  2. package/es/Article/ArticleByline.js +79 -123
  3. package/es/Article/ArticleFootNotes.js +16 -11
  4. package/es/AudioPlayer/AudioPlayer.js +33 -35
  5. package/es/AudioPlayer/initAudioPlayers.js +6 -1
  6. package/es/ContentTypeBadge/ContentTypeBadge.js +27 -6
  7. package/es/Embed/AudioEmbed.js +44 -188
  8. package/es/Embed/BrightcoveEmbed.js +32 -127
  9. package/es/Embed/ConceptEmbed.js +53 -75
  10. package/es/Embed/EmbedErrorPlaceholder.js +41 -0
  11. package/es/Embed/ExternalEmbed.js +5 -12
  12. package/es/Embed/H5pEmbed.js +5 -15
  13. package/es/Embed/IframeEmbed.js +4 -4
  14. package/es/Embed/ImageEmbed.js +41 -153
  15. package/es/Embed/RelatedContentEmbed.js +3 -3
  16. package/es/Embed/conceptComponents.js +62 -228
  17. package/es/Embed/types.js +1 -0
  18. package/es/KeyFigure/KeyFigure.js +57 -0
  19. package/es/{KeyPerformanceIndicator → KeyFigure}/index.js +1 -1
  20. package/es/LicenseByline/EmbedByline.js +33 -8
  21. package/es/LicenseByline/LicenseDescription.js +16 -14
  22. package/es/List/OrderedList.js +48 -0
  23. package/es/List/UnOrderedList.js +36 -0
  24. package/es/List/index.js +10 -0
  25. package/es/Navigation/NavigationBox.js +41 -48
  26. package/es/Navigation/NavigationHeading.js +18 -29
  27. package/es/Notion/Notion.js +5 -5
  28. package/es/Resource/ListResource.js +9 -9
  29. package/es/Resource/resourceComponents.js +12 -11
  30. package/es/Typography/Heading.js +38 -0
  31. package/es/Typography/index.js +9 -0
  32. package/es/all.css +1 -1
  33. package/es/index.js +4 -2
  34. package/es/locale/messages-en.js +6 -2
  35. package/es/locale/messages-nb.js +6 -2
  36. package/es/locale/messages-nn.js +6 -2
  37. package/es/locale/messages-se.js +6 -2
  38. package/es/locale/messages-sma.js +6 -2
  39. package/es/model/ContentType.js +7 -1
  40. package/lib/Article/Article.d.ts +1 -3
  41. package/lib/Article/Article.js +7 -13
  42. package/lib/Article/ArticleByline.d.ts +3 -5
  43. package/lib/Article/ArticleByline.js +83 -126
  44. package/lib/Article/ArticleFootNotes.js +16 -11
  45. package/lib/AudioPlayer/AudioPlayer.d.ts +1 -2
  46. package/lib/AudioPlayer/AudioPlayer.js +33 -36
  47. package/lib/AudioPlayer/initAudioPlayers.d.ts +1 -0
  48. package/lib/AudioPlayer/initAudioPlayers.js +9 -3
  49. package/lib/ContentTypeBadge/ContentTypeBadge.js +27 -6
  50. package/lib/Embed/AudioEmbed.d.ts +3 -2
  51. package/lib/Embed/AudioEmbed.js +53 -192
  52. package/lib/Embed/BrightcoveEmbed.d.ts +3 -1
  53. package/lib/Embed/BrightcoveEmbed.js +32 -126
  54. package/lib/Embed/ConceptEmbed.d.ts +7 -2
  55. package/lib/Embed/ConceptEmbed.js +51 -73
  56. package/lib/Embed/EmbedErrorPlaceholder.d.ts +17 -0
  57. package/lib/Embed/EmbedErrorPlaceholder.js +48 -0
  58. package/lib/Embed/ExternalEmbed.js +5 -11
  59. package/lib/Embed/H5pEmbed.js +5 -14
  60. package/lib/Embed/IframeEmbed.d.ts +2 -2
  61. package/lib/Embed/IframeEmbed.js +4 -4
  62. package/lib/Embed/ImageEmbed.d.ts +3 -10
  63. package/lib/Embed/ImageEmbed.js +48 -161
  64. package/lib/Embed/RelatedContentEmbed.js +3 -3
  65. package/lib/Embed/conceptComponents.d.ts +4 -2
  66. package/lib/Embed/conceptComponents.js +67 -231
  67. package/lib/Embed/index.d.ts +1 -0
  68. package/lib/Embed/types.d.ts +14 -0
  69. package/lib/Embed/types.js +5 -0
  70. package/lib/KeyFigure/KeyFigure.d.ts +10 -0
  71. package/lib/KeyFigure/KeyFigure.js +62 -0
  72. package/lib/KeyFigure/index.d.ts +1 -0
  73. package/lib/KeyFigure/index.js +13 -0
  74. package/lib/LicenseByline/EmbedByline.d.ts +10 -2
  75. package/lib/LicenseByline/EmbedByline.js +32 -7
  76. package/lib/LicenseByline/LicenseDescription.d.ts +3 -1
  77. package/lib/LicenseByline/LicenseDescription.js +14 -13
  78. package/lib/List/OrderedList.d.ts +15 -0
  79. package/lib/List/OrderedList.js +56 -0
  80. package/lib/List/UnOrderedList.d.ts +10 -0
  81. package/lib/List/UnOrderedList.js +43 -0
  82. package/lib/List/index.d.ts +9 -0
  83. package/lib/List/index.js +20 -0
  84. package/lib/Navigation/NavigationBox.js +40 -47
  85. package/lib/Navigation/NavigationHeading.js +17 -28
  86. package/lib/Notion/Notion.js +5 -5
  87. package/lib/Resource/ListResource.js +8 -8
  88. package/lib/Resource/resourceComponents.js +12 -11
  89. package/lib/Typography/Heading.d.ts +26 -0
  90. package/lib/Typography/Heading.js +45 -0
  91. package/lib/Typography/index.d.ts +8 -0
  92. package/lib/Typography/index.js +13 -0
  93. package/lib/all.css +1 -1
  94. package/lib/index.d.ts +4 -1
  95. package/lib/index.js +23 -3
  96. package/lib/locale/messages-en.d.ts +4 -0
  97. package/lib/locale/messages-en.js +6 -2
  98. package/lib/locale/messages-nb.d.ts +4 -0
  99. package/lib/locale/messages-nb.js +6 -2
  100. package/lib/locale/messages-nn.d.ts +4 -0
  101. package/lib/locale/messages-nn.js +6 -2
  102. package/lib/locale/messages-se.d.ts +4 -0
  103. package/lib/locale/messages-se.js +6 -2
  104. package/lib/locale/messages-sma.d.ts +4 -0
  105. package/lib/locale/messages-sma.js +6 -2
  106. package/lib/model/ContentType.d.ts +1 -0
  107. package/lib/model/ContentType.js +9 -2
  108. package/package.json +15 -15
  109. package/src/Article/Article.tsx +1 -8
  110. package/src/Article/ArticleByline.tsx +78 -127
  111. package/src/Article/ArticleFootNotes.tsx +33 -10
  112. package/src/Article/component.article.scss +1 -52
  113. package/src/Article/component.footnotes.scss +2 -2
  114. package/src/Aside/component.aside.scss +3 -3
  115. package/src/AudioPlayer/AudioPlayer.tsx +11 -24
  116. package/src/AudioPlayer/initAudioPlayers.tsx +7 -2
  117. package/src/ContentTypeBadge/ContentTypeBadge.tsx +29 -6
  118. package/src/ContentTypeBadge/component.content-type-badge.scss +9 -3
  119. package/src/Dialog/component.dialog.scss +4 -5
  120. package/src/Embed/AudioEmbed.stories.tsx +5 -3
  121. package/src/Embed/AudioEmbed.tsx +45 -192
  122. package/src/Embed/BrightcoveEmbed.stories.tsx +5 -1
  123. package/src/Embed/BrightcoveEmbed.tsx +24 -98
  124. package/src/Embed/ConceptEmbed.stories.tsx +5 -0
  125. package/src/Embed/ConceptEmbed.tsx +43 -54
  126. package/src/Embed/EmbedErrorPlaceholder.tsx +59 -0
  127. package/src/Embed/ExternalEmbed.stories.tsx +86 -0
  128. package/src/Embed/ExternalEmbed.tsx +3 -8
  129. package/src/Embed/H5pEmbed.stories.tsx +92 -0
  130. package/src/Embed/H5pEmbed.tsx +3 -11
  131. package/src/Embed/IframeEmbed.stories.tsx +130 -0
  132. package/src/Embed/IframeEmbed.tsx +3 -3
  133. package/src/Embed/ImageEmbed.stories.tsx +3 -1
  134. package/src/Embed/ImageEmbed.tsx +21 -116
  135. package/src/Embed/RelatedContentEmbed.tsx +3 -1
  136. package/src/Embed/conceptComponents.tsx +67 -257
  137. package/src/Embed/index.ts +1 -0
  138. package/src/Embed/types.ts +12 -0
  139. package/src/FactBox/component.factbox.scss +3 -3
  140. package/src/Figure/component.figure-license.scss +4 -4
  141. package/src/Figure/component.figure.scss +1 -1
  142. package/src/KeyFigure/KeyFigure.stories.tsx +36 -0
  143. package/src/{KeyPerformanceIndicator/KeyPerformanceIndicator.tsx → KeyFigure/KeyFigure.tsx} +9 -7
  144. package/src/{KeyPerformanceIndicator → KeyFigure}/index.ts +1 -1
  145. package/src/LicenseByline/EmbedByline.stories.tsx +1 -0
  146. package/src/LicenseByline/EmbedByline.tsx +57 -9
  147. package/src/LicenseByline/LicenseDescription.tsx +9 -3
  148. package/src/List/OrderedList.tsx +115 -0
  149. package/src/List/UnOrderedList.tsx +49 -0
  150. package/src/List/index.ts +10 -0
  151. package/src/MediaList/component.medialist.scss +2 -2
  152. package/src/Navigation/NavigationBox.tsx +10 -14
  153. package/src/Navigation/NavigationHeading.tsx +15 -24
  154. package/src/Notion/Notion.tsx +1 -1
  155. package/src/RelatedArticleList/component.related-articles.scss +3 -13
  156. package/src/Resource/ListResource.tsx +6 -2
  157. package/src/Resource/resourceComponents.tsx +4 -2
  158. package/src/Table/component.tables.scss +0 -46
  159. package/src/Translation/component.translation.scss +3 -5
  160. package/src/Typography/Heading.tsx +96 -0
  161. package/src/Typography/index.ts +9 -0
  162. package/src/index.ts +5 -1
  163. package/src/locale/messages-en.ts +4 -0
  164. package/src/locale/messages-nb.ts +4 -0
  165. package/src/locale/messages-nn.ts +4 -0
  166. package/src/locale/messages-se.ts +4 -0
  167. package/src/locale/messages-sma.ts +4 -0
  168. package/src/model/ContentType.ts +7 -0
  169. package/es/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -57
  170. package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.d.ts +0 -8
  171. package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -62
  172. package/lib/KeyPerformanceIndicator/index.d.ts +0 -1
  173. package/lib/KeyPerformanceIndicator/index.js +0 -13
  174. package/src/KeyPerformanceIndicator/KeyPerformanceIndicator.stories.tsx +0 -79
@@ -12,6 +12,7 @@ import { ImageEmbedData } from '@ndla/types-embed';
12
12
  import { IImageMetaInformationV2 } from '@ndla/types-backend/build/image-api';
13
13
  import ImageEmbed from './ImageEmbed';
14
14
  import { defaultParameters } from '../../../../stories/defaults';
15
+ import StoryFavoriteButton from '../../../../stories/StoryFavoriteButton';
15
16
 
16
17
  const embedData: ImageEmbedData = {
17
18
  resource: 'image',
@@ -59,7 +60,6 @@ const meta: Meta<typeof ImageEmbed> = {
59
60
  component: ImageEmbed,
60
61
  tags: ['autodocs'],
61
62
  args: {
62
- articlePath: undefined,
63
63
  previewAlt: true,
64
64
  },
65
65
  decorators: [
@@ -82,6 +82,7 @@ export default meta;
82
82
 
83
83
  export const ImageEmbedStory: StoryObj<typeof ImageEmbed> = {
84
84
  args: {
85
+ heartButton: StoryFavoriteButton,
85
86
  embed: {
86
87
  resource: 'image',
87
88
  status: 'success',
@@ -94,6 +95,7 @@ export const ImageEmbedStory: StoryObj<typeof ImageEmbed> = {
94
95
 
95
96
  export const Failed: StoryObj<typeof ImageEmbed> = {
96
97
  args: {
98
+ heartButton: StoryFavoriteButton,
97
99
  embed: {
98
100
  resource: 'image',
99
101
  status: 'error',
@@ -7,25 +7,21 @@
7
7
  */
8
8
 
9
9
  import isNumber from 'lodash/isNumber';
10
- import styled from '@emotion/styled';
11
- import { figureApa7CopyString, getGroupedContributorDescriptionList, getLicenseByAbbreviation } from '@ndla/licenses';
12
10
  import { ImageEmbedData, ImageMetaData } from '@ndla/types-embed';
13
11
  import { useTranslation } from 'react-i18next';
14
- import { ModalV2 } from '@ndla/modal';
15
- import { SafeLinkButton } from '@ndla/safelink';
16
12
  import { MouseEventHandler, useState } from 'react';
17
- import { ButtonV2, CopyButton } from '@ndla/button';
18
13
  import { ExpandTwoArrows } from '@ndla/icons/action';
19
14
  import { ArrowCollapse, ChevronDown, ChevronUp } from '@ndla/icons/common';
20
- import { Figure, FigureCaption, FigureType } from '../Figure';
15
+ import { Figure, FigureType } from '../Figure';
21
16
  import Image, { ImageLink } from '../Image';
22
- import { FigureLicenseDialogContent } from '../Figure/FigureLicenseDialogContent';
23
- import { Copyright } from '../types';
17
+ import { EmbedByline } from '../LicenseByline';
18
+ import EmbedErrorPlaceholder from './EmbedErrorPlaceholder';
19
+ import { HeartButtonType } from './types';
24
20
 
25
21
  interface Props {
26
22
  embed: ImageMetaData;
27
- articlePath?: string;
28
23
  previewAlt?: boolean;
24
+ heartButton?: HeartButtonType;
29
25
  }
30
26
 
31
27
  export interface Author {
@@ -100,38 +96,20 @@ const getCrop = (data: ImageEmbedData) => {
100
96
  return undefined;
101
97
  };
102
98
 
103
- const StyledSpan = styled.span`
104
- font-style: italic;
105
- color: grey;
106
- `;
107
-
108
99
  const expandedSizes = '(min-width: 1024px) 1024px, 100vw';
109
100
 
110
- const ImageEmbed = ({ embed, articlePath, previewAlt }: Props) => {
111
- const [isOpen, setIsOpen] = useState(false);
101
+ const ImageEmbed = ({ embed, previewAlt, heartButton: HeartButton }: Props) => {
112
102
  const [isBylineHidden, setIsBylineHidden] = useState(hideByline(embed.embedData.size));
113
103
  const [imageSizes, setImageSizes] = useState<string | undefined>(undefined);
114
- const { t, i18n } = useTranslation();
115
104
  if (embed.status === 'error') {
116
105
  const { align, size } = embed.embedData;
117
106
  const figureType = getFigureType(size, align);
118
- return (
119
- <Figure type={figureType}>
120
- <div className="c-figure__img">
121
- <img alt={t('image.error.url')} src={errorSvgSrc} />
122
- </div>
123
- <figcaption>{t('image.error.caption')}</figcaption>
124
- </Figure>
125
- );
107
+ return <EmbedErrorPlaceholder type={'image'} figureType={figureType} />;
126
108
  }
127
109
 
128
110
  const { data, embedData, seq } = embed;
129
111
 
130
- const authors = getLicenseCredits(data.copyright);
131
-
132
112
  const altText = embedData.alt || '';
133
- const caption = embedData.caption || '';
134
- const license = getLicenseByAbbreviation(data.copyright.license.license, i18n.language);
135
113
 
136
114
  const figureType = getFigureType(embedData.size, embedData.align);
137
115
  const sizes = getSizes(embedData.size, embedData.align);
@@ -139,29 +117,22 @@ const ImageEmbed = ({ embed, articlePath, previewAlt }: Props) => {
139
117
  const focalPoint = getFocalPoint(embedData);
140
118
  const crop = getCrop(embedData);
141
119
 
142
- const contributors = getGroupedContributorDescriptionList(data.copyright, i18n.language).map((item) => ({
143
- name: item.description,
144
- type: item.label,
145
- }));
146
-
147
120
  const figureId = `figure-${seq}-${data.id}`;
148
121
 
149
- const { creators, rightsholders, processors } = authors;
150
- const captionAuthors = creators.length || rightsholders.length ? [...creators, ...rightsholders] : processors;
151
122
  return (
152
123
  <Figure
153
124
  id={figureId}
154
125
  type={imageSizes ? undefined : figureType}
155
126
  className={imageSizes ? 'c-figure--right expanded' : ''}
156
127
  >
157
- <ImageWrapper src={data.imageUrl} crop={crop} size={embedData.size}>
128
+ <ImageWrapper src={data.image.imageUrl} crop={crop} size={embedData.size}>
158
129
  <Image
159
130
  focalPoint={focalPoint}
160
- contentType={data.contentType}
131
+ contentType={data.image.contentType}
161
132
  crop={crop}
162
133
  sizes={imageSizes ?? sizes}
163
134
  alt={altText}
164
- src={data.imageUrl}
135
+ src={data.image.imageUrl}
165
136
  expandButton={
166
137
  <ExpandButton
167
138
  size={embedData.size}
@@ -173,43 +144,17 @@ const ImageEmbed = ({ embed, articlePath, previewAlt }: Props) => {
173
144
  }
174
145
  />
175
146
  </ImageWrapper>
176
- {previewAlt ? <StyledSpan>{`Alt: ${embedData.alt}`}</StyledSpan> : null}
177
- <FigureCaption
178
- hideFigcaption={isSmall(embedData.size) || isBylineHidden}
179
- figureId={figureId}
180
- id={figureId}
181
- caption={caption}
182
- reuseLabel={t('image.reuse')}
183
- modalButton={
184
- <ButtonV2 shape="pill" variant="outline" size="small" onClick={() => setIsOpen(true)}>
185
- {t('image.reuse')}
186
- </ButtonV2>
187
- }
188
- licenseRights={license.rights}
189
- authors={captionAuthors}
190
- locale={i18n.language}
191
- >
192
- <ModalV2 controlled isOpen={isOpen} onClose={() => setIsOpen(false)} labelledBy="license-dialog-rules-heading">
193
- {(close) => (
194
- <FigureLicenseDialogContent
195
- title={data.title.title}
196
- license={license}
197
- onClose={close}
198
- authors={contributors}
199
- origin={data.copyright.origin}
200
- locale={i18n.language}
201
- type="image"
202
- >
203
- <ImageLicenseButtons
204
- articlePath={articlePath}
205
- title={data.title.title}
206
- imageUrl={data.imageUrl}
207
- copyright={data.copyright}
208
- />
209
- </FigureLicenseDialogContent>
210
- )}
211
- </ModalV2>
212
- </FigureCaption>
147
+ {isBylineHidden || (isSmall(embedData.size) && !imageSizes) ? null : (
148
+ <EmbedByline
149
+ type="image"
150
+ copyright={data.copyright}
151
+ description={embedData.caption ?? data.caption.caption}
152
+ bottomRounded
153
+ visibleAlt={previewAlt ? embed.embedData.alt : ''}
154
+ >
155
+ {HeartButton && <HeartButton embed={embed} />}
156
+ </EmbedByline>
157
+ )}
213
158
  </Figure>
214
159
  );
215
160
  };
@@ -229,46 +174,6 @@ const hideByline = (size?: string): boolean => {
229
174
  return !!size && size.endsWith('-hide-byline');
230
175
  };
231
176
 
232
- interface ImageLicenseButtonsProps {
233
- imageUrl: string;
234
- title?: string;
235
- articlePath?: string;
236
- copyright?: Partial<Copyright>;
237
- }
238
-
239
- export const ImageLicenseButtons = ({ imageUrl, title, articlePath, copyright }: ImageLicenseButtonsProps) => {
240
- const { t, i18n } = useTranslation();
241
- if (!copyright?.license?.license || copyright?.license?.license === 'COPYRIGHTED') return null;
242
-
243
- const copyString = figureApa7CopyString(
244
- title,
245
- undefined,
246
- imageUrl,
247
- articlePath,
248
- copyright,
249
- copyright?.license?.license,
250
- '',
251
- t,
252
- i18n.language,
253
- );
254
-
255
- return (
256
- <>
257
- <CopyButton
258
- variant="outline"
259
- onClick={() => navigator.clipboard.writeText(copyString)}
260
- copyNode={t('license.hasCopiedTitle')}
261
- aria-live="assertive"
262
- >
263
- {t('license.copyTitle')}
264
- </CopyButton>
265
- <SafeLinkButton to={`${imageUrl}?download=true`} download variant="outline">
266
- {t('image.download')}
267
- </SafeLinkButton>
268
- </>
269
- );
270
- };
271
-
272
177
  const ImageWrapper = ({ src, crop, size, children }: ImageWrapperProps) => {
273
178
  const { t } = useTranslation();
274
179
  if (isSmall(size) || hideByline(size)) {
@@ -30,7 +30,9 @@ const RelatedContentEmbed = ({ embed, isOembed, subject, ndlaFrontendDomain }: P
30
30
  const typeId = data.resource?.resourceTypes.find((rt) => contentTypeMapping[rt.id])?.id;
31
31
  const type = typeId ? contentTypeMapping[typeId] : undefined;
32
32
  const path =
33
- data.resource?.paths.find((p) => p.split('/')[1] === subject?.replace('urn:', '')) ?? data.resource?.path;
33
+ data.resource?.paths.find((p) => p.split('/')[1] === subject?.replace('urn:', '')) ??
34
+ data.resource?.path ??
35
+ `/article/${embedData.articleId}`;
34
36
  return (
35
37
  <RelatedArticleV2
36
38
  title={data.article.title?.title ?? ''}
@@ -6,28 +6,24 @@
6
6
  *
7
7
  */
8
8
 
9
- import { forwardRef, ReactNode, RefAttributes, useEffect, useRef } from 'react';
9
+ import { forwardRef, ReactNode, RefAttributes } from 'react';
10
10
  import { ConceptVisualElementMeta } from '@ndla/types-embed';
11
11
  import { useTranslation } from 'react-i18next';
12
- import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
13
- import { getGroupedContributorDescriptionList, getLicenseByAbbreviation, getLicenseCredits } from '@ndla/licenses';
14
12
  import { css } from '@emotion/react';
15
- import { ButtonV2 } from '@ndla/button';
16
13
  import { breakpoints, colors, fonts, misc, mq, spacing } from '@ndla/core';
17
- import { ChevronDown } from '@ndla/icons/common';
18
14
  import { parseMarkdown } from '@ndla/util';
19
15
  import styled from '@emotion/styled';
20
- import { NotionDialogContent, NotionDialogLicenses, NotionDialogText } from '@ndla/notion';
21
- import { Figure, FigureCaption } from '../Figure';
22
- import { NotionVisualElementType } from '../Notion';
23
- import { classLicenses, FigureLicenseByline, FigureLicenseCta } from '../Figure/FigureLicense';
16
+ import { NotionDialogContent, NotionDialogText } from '@ndla/notion';
24
17
  import { Copyright } from '../types';
25
- import { ImageLicenseButtons } from './ImageEmbed';
18
+ import ImageEmbed from './ImageEmbed';
19
+ import BrightcoveEmbed from './BrightcoveEmbed';
20
+ import H5pEmbed from './H5pEmbed';
21
+ import { ExternalEmbed, HeartButtonType, IframeEmbed } from '.';
22
+ import { EmbedByline } from '../LicenseByline';
26
23
 
27
24
  export interface ConceptNotionData {
28
25
  title: string;
29
26
  content?: string;
30
- articlePath?: string;
31
27
  metaImage?: {
32
28
  url?: string;
33
29
  alt?: string;
@@ -44,42 +40,13 @@ interface ConceptNotionProps extends RefAttributes<HTMLDivElement>, ConceptNotio
44
40
  inPopover?: boolean;
45
41
  tags?: string[];
46
42
  subjects?: string[];
43
+ heartButton?: HeartButtonType;
44
+ conceptHeartButton?: ReactNode;
47
45
  }
48
46
 
49
- const getConceptVisualElement = (visualElement: ConceptVisualElementMeta): NotionVisualElementType | undefined => {
50
- if (visualElement.status === 'error') {
51
- return undefined;
52
- }
53
-
54
- if (visualElement.resource === 'image') {
55
- return {
56
- resource: visualElement.resource,
57
- title: visualElement.data.title.title,
58
- copyright: visualElement.data.copyright,
59
- image: { src: visualElement.data.imageUrl, alt: visualElement.data.alttext.alttext },
60
- url: visualElement.data.imageUrl,
61
- };
62
- } else if (visualElement.resource === 'brightcove') {
63
- return {
64
- resource: visualElement.resource,
65
- title: visualElement.data.name,
66
- url: `https://players.brightcove.net/${visualElement.embedData.account}/${visualElement.embedData.player}_default/index.html?videoId=${visualElement.embedData.videoid}`,
67
- copyright: visualElement.data.copyright,
68
- };
69
- } else if (visualElement.resource === 'external' || visualElement.resource === 'iframe') {
70
- return {
71
- resource: visualElement.resource,
72
- url: visualElement.embedData.url,
73
- title: visualElement.embedData.url,
74
- };
75
- } else {
76
- return {
77
- resource: visualElement.resource,
78
- url: visualElement.data.h5pUrl,
79
- title: visualElement.embedData.title,
80
- };
81
- }
82
- };
47
+ const ContentPadding = styled.div`
48
+ padding: ${spacing.normal};
49
+ `;
83
50
 
84
51
  const notionContentCss = css`
85
52
  @keyframes animateIn {
@@ -95,7 +62,6 @@ const notionContentCss = css`
95
62
 
96
63
  animation-name: animateIn;
97
64
  animation-duration: 300ms;
98
- padding: ${spacing.normal};
99
65
  background-color: white;
100
66
  z-index: 1;
101
67
  box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
@@ -115,11 +81,6 @@ const notionContentCss = css`
115
81
  }
116
82
  `;
117
83
 
118
- const StyledIframe = styled.iframe<{ type?: string }>`
119
- height: auto;
120
- min-height: ${(p) => p.type === 'video' && '400px'};
121
- `;
122
-
123
84
  const NotionHeader = styled.div`
124
85
  display: flex;
125
86
  align-items: center;
@@ -141,72 +102,6 @@ const NotionHeader = styled.div`
141
102
  }
142
103
  `;
143
104
 
144
- const StyledAccordionContent = styled(Content)`
145
- background-color: ${colors.brand.lighter};
146
- padding: ${spacing.small};
147
- border-radius: ${misc.borderRadius};
148
- overflow: hidden;
149
- &[data-state='open'] {
150
- animation: slideDown 300ms ease-out;
151
- }
152
- &[data-state='closed'] {
153
- animation: slideUp 300ms ease-out;
154
- }
155
- @keyframes slideDown {
156
- from {
157
- height: 0;
158
- }
159
- to {
160
- height: var(--radix-accordion-content-height);
161
- }
162
- }
163
- @keyframes slideUp {
164
- from {
165
- height: var(--radix-accordion-content-height);
166
- }
167
- to {
168
- height: 0;
169
- }
170
- }
171
- `;
172
-
173
- const StyledRoot = styled(Root)`
174
- border-bottom: 1px solid ${colors.brand.greyLight};
175
- `;
176
-
177
- const StyledFigure = styled(Figure)`
178
- && {
179
- margin: ${spacing.normal} 0;
180
- }
181
- padding-bottom: 0;
182
- `;
183
-
184
- const StyledFigureCaption = styled(FigureCaption)`
185
- border-bottom: 0;
186
-
187
- h3 {
188
- margin: 0;
189
- }
190
- `;
191
-
192
- const StyledSpan = styled.span`
193
- font-style: italic;
194
- color: grey;
195
- `;
196
-
197
- const StyledAccordionTrigger = styled(ButtonV2)`
198
- color: ${colors.brand.primary};
199
- border-color: ${colors.brand.primary};
200
- &[data-state='open'] {
201
- svg {
202
- transform: rotate(180deg);
203
- }
204
- }
205
- svg {
206
- transition: transform 300ms;
207
- }
208
- `;
209
-
210
105
  const ListWrapper = styled.div`
211
106
  display: flex;
212
107
  gap: ${spacing.small};
@@ -215,6 +110,13 @@ const ListWrapper = styled.div`
215
110
 
216
111
  const StyledNotionDialogContent = styled(NotionDialogContent)`
217
112
  padding-top: ${spacing.small};
113
+ .c-figure {
114
+ left: unset !important;
115
+ width: 100% !important;
116
+ padding: 0;
117
+ margin: 0;
118
+ padding-bottom: ${spacing.normal};
119
+ }
218
120
  `;
219
121
 
220
122
  const StyledList = styled.ul`
@@ -237,7 +139,6 @@ export const ConceptNotionV2 = forwardRef<HTMLDivElement, ConceptNotionProps>(
237
139
  (
238
140
  {
239
141
  visualElement,
240
- articlePath,
241
142
  title,
242
143
  content,
243
144
  source,
@@ -247,154 +148,63 @@ export const ConceptNotionV2 = forwardRef<HTMLDivElement, ConceptNotionProps>(
247
148
  previewAlt,
248
149
  tags,
249
150
  subjects,
151
+ heartButton,
152
+ conceptHeartButton,
250
153
  ...rest
251
154
  },
252
155
  ref,
253
156
  ) => {
254
- const { t, i18n } = useTranslation();
255
- const iframeRef = useRef<HTMLIFrameElement>(null);
256
-
257
- useEffect(() => {
258
- const iframe = iframeRef.current;
259
- if (iframe) {
260
- const [width, height] = [parseInt(iframe.width), parseInt(iframe.height)];
261
- iframe.style.aspectRatio = `${width ? width : 16}/${height ? height : 9}`;
262
- iframe.width = '';
263
- iframe.height = '';
264
- }
265
- }, []);
266
-
267
- const licenseCredits = getLicenseCredits(copyright);
268
- const { creators, rightsholders, processors } = licenseCredits;
269
- const authors = creators.length || rightsholders.length ? creators.concat(rightsholders) : processors;
157
+ const { t } = useTranslation();
270
158
 
271
- const visualElementType =
272
- visualElement?.embedData.resource === 'brightcove' ? 'video' : visualElement?.embedData.resource;
273
-
274
- const notionVisualElement = visualElement ? getConceptVisualElement(visualElement) : undefined;
275
- const visualElementLicense = getLicenseByAbbreviation(
276
- notionVisualElement?.copyright?.license?.license ?? '',
277
- i18n.language,
278
- );
279
- const visualElementLicenseCredits = getLicenseCredits(notionVisualElement?.copyright);
280
- const visualElementAuthors =
281
- visualElementLicenseCredits.creators.length || visualElementLicenseCredits.rightsholders.length
282
- ? visualElementLicenseCredits.creators.concat(visualElementLicenseCredits.rightsholders)
283
- : visualElementLicenseCredits.processors;
284
- const visualElementGroupedAuthors = getGroupedContributorDescriptionList(
285
- visualElementLicenseCredits,
286
- i18n.language,
287
- ).map((item) => ({
288
- name: item.description,
289
- type: item.label,
290
- }));
291
159
  return (
292
160
  <div css={inPopover ? notionContentCss : undefined} {...rest} ref={ref}>
293
- <NotionHeader>
294
- <h1>
295
- {title} {<small>{t('searchPage.resultType.notionsHeading')}</small>}
296
- </h1>
297
- {closeButton}
298
- </NotionHeader>
299
- <StyledNotionDialogContent>
300
- {notionVisualElement && (
301
- <StyledFigure resizeIframe type={'full-column'}>
302
- {notionVisualElement.image?.src ? (
303
- <img src={notionVisualElement.image.src} alt={notionVisualElement.image.alt} />
304
- ) : (
305
- <StyledIframe
306
- ref={iframeRef}
307
- allowFullScreen
308
- type={visualElementType}
309
- src={notionVisualElement?.url}
310
- title={notionVisualElement?.title}
311
- />
312
- )}
313
- {previewAlt && visualElement?.resource === 'image' ? (
314
- <StyledSpan>{`Alt: ${visualElement.embedData.alt}`}</StyledSpan>
315
- ) : null}
316
- {visualElementLicense && (
317
- <StyledRoot type="single" collapsible>
318
- <Item value="licenseInfo">
319
- <StyledFigureCaption
320
- figureId={''}
321
- id={''}
322
- modalButton={<></>}
323
- reuseLabel={t('reuse')}
324
- authors={visualElementAuthors}
325
- licenseRights={visualElementLicense.rights}
326
- >
327
- {visualElementLicense.abbreviation && (
328
- <Header>
329
- <Trigger asChild>
330
- <StyledAccordionTrigger variant="outline" shape="pill" size="small" colorTheme="lighter">
331
- {t('license.info')}
332
- <ChevronDown />
333
- </StyledAccordionTrigger>
334
- </Trigger>
335
- </Header>
336
- )}
337
- </StyledFigureCaption>
338
- <StyledAccordionContent>
339
- <div {...classLicenses()}>
340
- <h1 {...classLicenses('title')}>{t(`license.${visualElementType}.rules`)}</h1>
341
- <FigureLicenseByline
342
- license={visualElementLicense}
343
- messages={{
344
- learnAboutLicenses: t('license.learnMore'),
345
- }}
346
- locale={i18n.language}
347
- />
348
- <FigureLicenseCta
349
- authors={visualElementGroupedAuthors}
350
- title={notionVisualElement?.title}
351
- origin={notionVisualElement?.copyright?.origin}
352
- messages={{ source: t('source'), title: t('title') }}
353
- >
354
- {visualElementType === 'image' ? (
355
- <ImageLicenseButtons
356
- imageUrl={notionVisualElement.image?.src ?? ''}
357
- title={notionVisualElement.title}
358
- copyright={notionVisualElement.copyright}
359
- articlePath={articlePath}
360
- />
361
- ) : null}
362
- </FigureLicenseCta>
363
- </div>
364
- </StyledAccordionContent>
365
- </Item>
366
- </StyledRoot>
367
- )}
368
- </StyledFigure>
161
+ <ContentPadding>
162
+ <NotionHeader>
163
+ <h1>
164
+ {title} {<small>{t('searchPage.resultType.notionsHeading')}</small>}
165
+ </h1>
166
+ {closeButton}
167
+ </NotionHeader>
168
+ <StyledNotionDialogContent>
169
+ {visualElement?.resource === 'image' ? (
170
+ <ImageEmbed embed={visualElement} heartButton={heartButton} />
171
+ ) : visualElement?.resource === 'brightcove' ? (
172
+ <BrightcoveEmbed embed={visualElement} heartButton={heartButton} />
173
+ ) : visualElement?.resource === 'h5p' ? (
174
+ <H5pEmbed embed={visualElement} />
175
+ ) : visualElement?.resource === 'iframe' ? (
176
+ <IframeEmbed embed={visualElement} />
177
+ ) : visualElement?.resource === 'external' ? (
178
+ <ExternalEmbed embed={visualElement} />
179
+ ) : null}
180
+ <NotionDialogText>{parseMarkdown(content ?? '', 'body')}</NotionDialogText>
181
+ </StyledNotionDialogContent>
182
+ {tags && (
183
+ <ListWrapper>
184
+ {`${t('notions.tags')}:`}
185
+ <StyledList>
186
+ {tags.map((tag, index) => (
187
+ <li key={index}>{tag}</li>
188
+ ))}
189
+ </StyledList>
190
+ </ListWrapper>
369
191
  )}
370
- <NotionDialogText>{parseMarkdown(content ?? '', 'body')}</NotionDialogText>
371
- </StyledNotionDialogContent>
372
- {tags && (
373
- <ListWrapper>
374
- {`${t('notions.tags')}:`}
375
- <StyledList>
376
- {tags.map((tag, index) => (
377
- <li key={index}>{tag}</li>
378
- ))}
379
- </StyledList>
380
- </ListWrapper>
381
- )}
382
- {subjects && (
383
- <ListWrapper>
384
- {`${t('notions.usedIn')}:`}
385
- <StyledList>
386
- {subjects.map((subject, index) => (
387
- <li key={index}>{subject}</li>
388
- ))}
389
- </StyledList>
390
- </ListWrapper>
192
+ {subjects && (
193
+ <ListWrapper>
194
+ {`${t('notions.usedIn')}:`}
195
+ <StyledList>
196
+ {subjects.map((subject, index) => (
197
+ <li key={index}>{subject}</li>
198
+ ))}
199
+ </StyledList>
200
+ </ListWrapper>
201
+ )}
202
+ </ContentPadding>
203
+ {copyright && (
204
+ <EmbedByline copyright={copyright} type="concept">
205
+ {conceptHeartButton}
206
+ </EmbedByline>
391
207
  )}
392
-
393
- <NotionDialogLicenses
394
- authors={authors.map((a) => a.name)}
395
- license={copyright?.license?.license ?? ''}
396
- source={parseMarkdown(source ?? '', 'body')}
397
- />
398
208
  </div>
399
209
  );
400
210
  },
@@ -19,3 +19,4 @@ export { default as ConceptEmbed } from './ConceptEmbed';
19
19
  export { ConceptNotionV2 } from './conceptComponents';
20
20
  export { default as ConceptListEmbed } from './ConceptListEmbed';
21
21
  export { default as UnknownEmbed } from './UnknownEmbed';
22
+ export type { HeartButtonType } from './types';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright (c) 2023-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 { ElementType } from 'react';
10
+ import { EmbedMetaData } from '@ndla/types-embed';
11
+
12
+ export type HeartButtonType = ElementType<{ embed: Extract<EmbedMetaData, { status: 'success' }> }>;