@ndla/ui 36.0.1 → 37.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 (188) 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/BlogPost/BlogPost.js +4 -4
  7. package/es/CampaignBlock/CampaignBlock.js +77 -0
  8. package/es/CampaignBlock/index.js +9 -0
  9. package/es/ContactBlock/ContactBlock.js +63 -39
  10. package/es/Embed/AudioEmbed.js +44 -188
  11. package/es/Embed/BrightcoveEmbed.js +27 -123
  12. package/es/Embed/ConceptEmbed.js +53 -75
  13. package/es/Embed/EmbedErrorPlaceholder.js +41 -0
  14. package/es/Embed/ExternalEmbed.js +5 -12
  15. package/es/Embed/H5pEmbed.js +4 -14
  16. package/es/Embed/IframeEmbed.js +4 -4
  17. package/es/Embed/ImageEmbed.js +41 -153
  18. package/es/Embed/conceptComponents.js +62 -228
  19. package/es/Embed/types.js +1 -0
  20. package/es/KeyFigure/KeyFigure.js +57 -0
  21. package/es/{KeyPerformanceIndicator → KeyFigure}/index.js +1 -1
  22. package/es/LicenseByline/EmbedByline.js +115 -0
  23. package/es/LicenseByline/LicenseDescription.js +39 -0
  24. package/es/LicenseByline/LicenseLink.js +36 -0
  25. package/es/LicenseByline/index.js +1 -0
  26. package/es/List/OrderedList.js +48 -0
  27. package/es/List/UnOrderedList.js +36 -0
  28. package/es/List/index.js +10 -0
  29. package/es/Navigation/NavigationBox.js +41 -48
  30. package/es/Navigation/NavigationHeading.js +18 -29
  31. package/es/Notion/Notion.js +5 -5
  32. package/es/Resource/resourceComponents.js +12 -11
  33. package/es/Typography/Heading.js +38 -0
  34. package/es/Typography/index.js +9 -0
  35. package/es/all.css +1 -1
  36. package/es/index.js +4 -2
  37. package/es/locale/messages-en.js +13 -2
  38. package/es/locale/messages-nb.js +13 -2
  39. package/es/locale/messages-nn.js +13 -2
  40. package/es/locale/messages-se.js +13 -2
  41. package/es/locale/messages-sma.js +13 -2
  42. package/es/model/ContentType.js +7 -1
  43. package/lib/Article/Article.d.ts +1 -3
  44. package/lib/Article/Article.js +7 -13
  45. package/lib/Article/ArticleByline.d.ts +3 -5
  46. package/lib/Article/ArticleByline.js +83 -126
  47. package/lib/Article/ArticleFootNotes.js +16 -11
  48. package/lib/AudioPlayer/AudioPlayer.d.ts +1 -2
  49. package/lib/AudioPlayer/AudioPlayer.js +33 -36
  50. package/lib/AudioPlayer/initAudioPlayers.d.ts +1 -0
  51. package/lib/AudioPlayer/initAudioPlayers.js +9 -3
  52. package/lib/BlogPost/BlogPost.js +4 -4
  53. package/lib/CampaignBlock/CampaignBlock.d.ts +31 -0
  54. package/lib/CampaignBlock/CampaignBlock.js +82 -0
  55. package/lib/CampaignBlock/index.d.ts +8 -0
  56. package/lib/CampaignBlock/index.js +13 -0
  57. package/lib/ContactBlock/ContactBlock.js +63 -39
  58. package/lib/Embed/AudioEmbed.d.ts +3 -2
  59. package/lib/Embed/AudioEmbed.js +53 -192
  60. package/lib/Embed/BrightcoveEmbed.d.ts +3 -1
  61. package/lib/Embed/BrightcoveEmbed.js +27 -122
  62. package/lib/Embed/ConceptEmbed.d.ts +7 -2
  63. package/lib/Embed/ConceptEmbed.js +51 -73
  64. package/lib/Embed/EmbedErrorPlaceholder.d.ts +17 -0
  65. package/lib/Embed/EmbedErrorPlaceholder.js +48 -0
  66. package/lib/Embed/ExternalEmbed.js +5 -11
  67. package/lib/Embed/H5pEmbed.js +4 -13
  68. package/lib/Embed/IframeEmbed.d.ts +2 -2
  69. package/lib/Embed/IframeEmbed.js +4 -4
  70. package/lib/Embed/ImageEmbed.d.ts +3 -10
  71. package/lib/Embed/ImageEmbed.js +48 -161
  72. package/lib/Embed/conceptComponents.d.ts +4 -2
  73. package/lib/Embed/conceptComponents.js +67 -231
  74. package/lib/Embed/index.d.ts +1 -0
  75. package/lib/Embed/types.d.ts +14 -0
  76. package/lib/Embed/types.js +5 -0
  77. package/lib/KeyFigure/KeyFigure.d.ts +10 -0
  78. package/lib/KeyFigure/KeyFigure.js +62 -0
  79. package/lib/KeyFigure/index.d.ts +1 -0
  80. package/lib/KeyFigure/index.js +13 -0
  81. package/lib/LicenseByline/EmbedByline.d.ts +51 -0
  82. package/lib/LicenseByline/EmbedByline.js +120 -0
  83. package/lib/LicenseByline/LicenseDescription.d.ts +14 -0
  84. package/lib/LicenseByline/LicenseDescription.js +44 -0
  85. package/lib/LicenseByline/LicenseLink.d.ts +14 -0
  86. package/lib/LicenseByline/LicenseLink.js +44 -0
  87. package/lib/LicenseByline/index.d.ts +1 -0
  88. package/lib/LicenseByline/index.js +13 -0
  89. package/lib/List/OrderedList.d.ts +15 -0
  90. package/lib/List/OrderedList.js +56 -0
  91. package/lib/List/UnOrderedList.d.ts +10 -0
  92. package/lib/List/UnOrderedList.js +43 -0
  93. package/lib/List/index.d.ts +9 -0
  94. package/lib/List/index.js +20 -0
  95. package/lib/Navigation/NavigationBox.js +40 -47
  96. package/lib/Navigation/NavigationHeading.js +17 -28
  97. package/lib/Notion/Notion.js +5 -5
  98. package/lib/Resource/resourceComponents.js +12 -11
  99. package/lib/Typography/Heading.d.ts +26 -0
  100. package/lib/Typography/Heading.js +45 -0
  101. package/lib/Typography/index.d.ts +8 -0
  102. package/lib/Typography/index.js +13 -0
  103. package/lib/all.css +1 -1
  104. package/lib/index.d.ts +4 -1
  105. package/lib/index.js +23 -3
  106. package/lib/locale/messages-en.d.ts +11 -0
  107. package/lib/locale/messages-en.js +13 -2
  108. package/lib/locale/messages-nb.d.ts +11 -0
  109. package/lib/locale/messages-nb.js +13 -2
  110. package/lib/locale/messages-nn.d.ts +11 -0
  111. package/lib/locale/messages-nn.js +13 -2
  112. package/lib/locale/messages-se.d.ts +11 -0
  113. package/lib/locale/messages-se.js +13 -2
  114. package/lib/locale/messages-sma.d.ts +11 -0
  115. package/lib/locale/messages-sma.js +13 -2
  116. package/lib/model/ContentType.d.ts +1 -0
  117. package/lib/model/ContentType.js +9 -2
  118. package/package.json +15 -15
  119. package/src/Article/Article.tsx +1 -8
  120. package/src/Article/ArticleByline.tsx +78 -127
  121. package/src/Article/ArticleFootNotes.tsx +33 -10
  122. package/src/Article/component.article.scss +1 -52
  123. package/src/Article/component.footnotes.scss +2 -2
  124. package/src/Aside/component.aside.scss +3 -3
  125. package/src/AudioPlayer/AudioPlayer.tsx +11 -24
  126. package/src/AudioPlayer/initAudioPlayers.tsx +7 -2
  127. package/src/BlogPost/BlogPost.tsx +0 -4
  128. package/src/CampaignBlock/CampaignBlock.stories.tsx +63 -0
  129. package/src/CampaignBlock/CampaignBlock.tsx +99 -0
  130. package/src/CampaignBlock/index.ts +9 -0
  131. package/src/ContactBlock/ContactBlock.tsx +27 -19
  132. package/src/ContactBlock/Contactblock.stories.tsx +0 -1
  133. package/src/Dialog/component.dialog.scss +4 -5
  134. package/src/Embed/AudioEmbed.stories.tsx +5 -3
  135. package/src/Embed/AudioEmbed.tsx +45 -192
  136. package/src/Embed/BrightcoveEmbed.stories.tsx +5 -1
  137. package/src/Embed/BrightcoveEmbed.tsx +20 -95
  138. package/src/Embed/ConceptEmbed.stories.tsx +5 -0
  139. package/src/Embed/ConceptEmbed.tsx +43 -54
  140. package/src/Embed/EmbedErrorPlaceholder.tsx +59 -0
  141. package/src/Embed/ExternalEmbed.stories.tsx +86 -0
  142. package/src/Embed/ExternalEmbed.tsx +3 -8
  143. package/src/Embed/H5pEmbed.stories.tsx +92 -0
  144. package/src/Embed/H5pEmbed.tsx +2 -10
  145. package/src/Embed/IframeEmbed.stories.tsx +130 -0
  146. package/src/Embed/IframeEmbed.tsx +3 -3
  147. package/src/Embed/ImageEmbed.stories.tsx +3 -1
  148. package/src/Embed/ImageEmbed.tsx +21 -116
  149. package/src/Embed/conceptComponents.tsx +67 -257
  150. package/src/Embed/index.ts +1 -0
  151. package/src/Embed/types.ts +12 -0
  152. package/src/FactBox/component.factbox.scss +3 -3
  153. package/src/Figure/component.figure-license.scss +4 -4
  154. package/src/Figure/component.figure.scss +1 -1
  155. package/src/KeyFigure/KeyFigure.stories.tsx +36 -0
  156. package/src/{KeyPerformanceIndicator/KeyPerformanceIndicator.tsx → KeyFigure/KeyFigure.tsx} +9 -7
  157. package/src/{KeyPerformanceIndicator → KeyFigure}/index.ts +1 -1
  158. package/src/LicenseByline/EmbedByline.stories.tsx +83 -0
  159. package/src/LicenseByline/EmbedByline.tsx +165 -0
  160. package/src/LicenseByline/LicenseDescription.tsx +43 -0
  161. package/src/LicenseByline/LicenseLink.tsx +42 -0
  162. package/src/LicenseByline/index.tsx +1 -0
  163. package/src/List/OrderedList.tsx +115 -0
  164. package/src/List/UnOrderedList.tsx +49 -0
  165. package/src/List/index.ts +10 -0
  166. package/src/MediaList/component.medialist.scss +2 -2
  167. package/src/Navigation/NavigationBox.tsx +10 -14
  168. package/src/Navigation/NavigationHeading.tsx +15 -24
  169. package/src/Notion/Notion.tsx +1 -1
  170. package/src/RelatedArticleList/component.related-articles.scss +3 -13
  171. package/src/Resource/resourceComponents.tsx +4 -2
  172. package/src/Table/component.tables.scss +0 -46
  173. package/src/Translation/component.translation.scss +3 -5
  174. package/src/Typography/Heading.tsx +96 -0
  175. package/src/Typography/index.ts +9 -0
  176. package/src/index.ts +5 -1
  177. package/src/locale/messages-en.ts +11 -0
  178. package/src/locale/messages-nb.ts +11 -0
  179. package/src/locale/messages-nn.ts +11 -0
  180. package/src/locale/messages-se.ts +11 -0
  181. package/src/locale/messages-sma.ts +11 -0
  182. package/src/model/ContentType.ts +7 -0
  183. package/es/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -57
  184. package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.d.ts +0 -8
  185. package/lib/KeyPerformanceIndicator/KeyPerformanceIndicator.js +0 -62
  186. package/lib/KeyPerformanceIndicator/index.d.ts +0 -1
  187. package/lib/KeyPerformanceIndicator/index.js +0 -13
  188. package/src/KeyPerformanceIndicator/KeyPerformanceIndicator.stories.tsx +0 -79
@@ -0,0 +1,99 @@
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 React from 'react';
10
+ import styled from '@emotion/styled';
11
+ import { css } from '@emotion/react';
12
+ import SafeLink from '@ndla/safelink';
13
+ import { Forward } from '@ndla/icons/common';
14
+ import { breakpoints, colors, fonts, spacing, mq, misc } from '@ndla/core';
15
+ import { HeadingLevel } from '../types';
16
+
17
+ interface Image {
18
+ src: string;
19
+ alt: string;
20
+ }
21
+ interface Props {
22
+ title: {
23
+ title: string;
24
+ language: string;
25
+ };
26
+ description: {
27
+ text: string;
28
+ language: string;
29
+ };
30
+ headingLevel?: HeadingLevel;
31
+ url: {
32
+ url: string;
33
+ text: string;
34
+ };
35
+ imageBefore?: Image;
36
+ imageAfter?: Image;
37
+ }
38
+
39
+ const Container = styled.div`
40
+ max-width: 390px;
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: ${spacing.xsmall};
44
+ border: 1px ${colors.brand.lighter} solid;
45
+ border-radius: ${misc.borderRadius};
46
+ padding: ${spacing.normal} ${spacing.small};
47
+ ${mq.range({ from: breakpoints.tabletWide })} {
48
+ max-width: 1100px;
49
+ flex-direction: row;
50
+ }
51
+ `;
52
+
53
+ const headingStyle = css`
54
+ margin: 0;
55
+ `;
56
+
57
+ const StyledDescription = styled.p`
58
+ font-family: ${fonts.serif};
59
+ margin: ${spacing.normal} 0 ${spacing.medium};
60
+ `;
61
+
62
+ const StyledImg = styled.img`
63
+ max-height: 200px;
64
+ ${mq.range({ until: breakpoints.tabletWide })} {
65
+ align-self: center;
66
+ }
67
+ ${mq.range({ from: breakpoints.tabletWide })} {
68
+ align-self: center;
69
+ }
70
+ `;
71
+
72
+ const StyledLink = styled(SafeLink)`
73
+ box-shadow: none;
74
+ text-decoration: underline;
75
+ color: ${colors.brand.primary};
76
+ &:hover,
77
+ &:focus-visible {
78
+ text-decoration: none;
79
+ }
80
+ `;
81
+
82
+ const CampaignBlock = ({ title, imageBefore, description, headingLevel: Heading = 'h2', imageAfter, url }: Props) => {
83
+ return (
84
+ <Container>
85
+ {imageBefore && <StyledImg src={imageBefore.src} data-left={true} />}
86
+ <div>
87
+ <Heading css={headingStyle}>{title.title}</Heading>
88
+ <StyledDescription>{description.text}</StyledDescription>
89
+ <StyledLink to={url.url}>
90
+ {url.text}
91
+ <Forward />
92
+ </StyledLink>
93
+ </div>
94
+ {imageAfter && <StyledImg src={imageAfter.src} data-right={true} />}
95
+ </Container>
96
+ );
97
+ };
98
+
99
+ export default CampaignBlock;
@@ -0,0 +1,9 @@
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
+ export { default as CampaignBlock } from './CampaignBlock';
@@ -25,16 +25,16 @@ interface Props {
25
25
  name: string;
26
26
  email: string;
27
27
  }
28
- const CardWrapper = styled.div`
28
+ const BlockWrapper = styled.div`
29
29
  display: flex;
30
30
  flex-direction: column;
31
31
  padding: 0 0 ${spacing.medium} ${spacing.medium};
32
32
  font-family: ${fonts.sans};
33
- justify-content: center;
34
33
  border-radius: ${misc.borderRadius};
35
34
  border: 1px solid ${colors.brand.lighter};
36
- margin-top: ${spacing.mediumlarge};
35
+ max-width: 348px;
37
36
  ${mq.range({ from: breakpoints.tabletWide })} {
37
+ max-width: 773px;
38
38
  flex-direction: row;
39
39
  padding: 0 0 ${spacing.medium} ${spacing.medium};
40
40
  gap: ${spacing.nsmall};
@@ -48,12 +48,13 @@ const StyledHeader = styled.div`
48
48
  padding-top: ${spacing.medium};
49
49
  `;
50
50
 
51
- const StyledDescriptionInformation = styled.div`
51
+ const StyledText = styled.div`
52
52
  display: flex;
53
53
  overflow-wrap: anywhere;
54
54
  color: ${colors.text.light};
55
55
  gap: ${spacing.xxsmall};
56
56
  `;
57
+
57
58
  const EmailLink = styled.a`
58
59
  color: ${colors.text.light};
59
60
  text-decoration-color: ${colors.text.light};
@@ -70,7 +71,7 @@ const SummaryBlock = styled.p`
70
71
  }
71
72
  `;
72
73
 
73
- const ContentWrapper = styled.div`
74
+ const TextWrapper = styled.div`
74
75
  display: flex;
75
76
  overflow: hidden;
76
77
  justify-content: space-between;
@@ -82,18 +83,16 @@ const BlobWrapper = styled.div`
82
83
  `;
83
84
 
84
85
  const ImageWrapper = styled.div`
85
- aspect-ratio: 16/9;
86
86
  display: flex;
87
87
  flex-direction: column;
88
88
  gap: ${spacing.small};
89
89
  padding: ${spacing.medium} ${spacing.medium} 0 0;
90
-
91
90
  ${mq.range({ from: breakpoints.tabletWide })} {
92
91
  padding-right: 0;
93
92
  }
94
93
  `;
95
94
 
96
- const blobStyle = css`
95
+ const blobStyling = css`
97
96
  width: 165px;
98
97
  height: 180px;
99
98
  transform: translate(10%, 0);
@@ -101,6 +100,15 @@ const blobStyle = css`
101
100
  const Email = styled.div`
102
101
  min-width: 60px;
103
102
  `;
103
+
104
+ const ContentWrapper = styled.div`
105
+ width: 100%;
106
+ `;
107
+
108
+ const StyledImage = styled.img`
109
+ object-fit: cover;
110
+ `;
111
+
104
112
  const ContactBlock = ({ image, jobTitle, description, name, email, blobColor = 'green', blob = 'pointy' }: Props) => {
105
113
  const { t } = useTranslation();
106
114
  const isGreenBlob = blobColor === 'green';
@@ -108,11 +116,11 @@ const ContactBlock = ({ image, jobTitle, description, name, email, blobColor = '
108
116
  const authors = concat(image?.copyright.processors, image?.copyright.creators, image?.copyright.rightsholders);
109
117
 
110
118
  return (
111
- <CardWrapper>
119
+ <BlockWrapper>
112
120
  <ImageWrapper>
113
121
  {image ? (
114
122
  <>
115
- <img alt={image.alttext.alttext} src={`${image.image.imageUrl}?width=286`} />
123
+ <StyledImage alt={image.alttext.alttext} src={`${image.image.imageUrl}?width=286`} />
116
124
  {`${t('photo')}: ${authors.reduce((acc, name) => (acc = `${acc} ${name?.name}`), '')} ${
117
125
  image.copyright.license.license
118
126
  }`}
@@ -121,23 +129,23 @@ const ContactBlock = ({ image, jobTitle, description, name, email, blobColor = '
121
129
  <img alt={t('image.error.url')} src={errorSvgSrc} />
122
130
  )}
123
131
  </ImageWrapper>
124
- <div>
125
- <ContentWrapper>
132
+ <ContentWrapper>
133
+ <TextWrapper>
126
134
  <div>
127
135
  <StyledHeader>{name}</StyledHeader>
128
- <StyledDescriptionInformation>{jobTitle}</StyledDescriptionInformation>
129
- <StyledDescriptionInformation>
136
+ <StyledText>{jobTitle}</StyledText>
137
+ <StyledText>
130
138
  <Email>{`${t('email')}:`}</Email>
131
139
  <EmailLink href={`mailto:${email}?subject=Contact us`}>{email}</EmailLink>
132
- </StyledDescriptionInformation>
140
+ </StyledText>
133
141
  </div>
134
142
  <BlobWrapper>
135
- <Blob css={blobStyle} color={isGreenBlob ? colors.support.greenLight : colors.support.redLight} />
143
+ <Blob css={blobStyling} color={isGreenBlob ? colors.support.greenLight : colors.support.redLight} />
136
144
  </BlobWrapper>
137
- </ContentWrapper>
145
+ </TextWrapper>
138
146
  <SummaryBlock>{description}</SummaryBlock>
139
- </div>
140
- </CardWrapper>
147
+ </ContentWrapper>
148
+ </BlockWrapper>
141
149
  );
142
150
  };
143
151
 
@@ -77,7 +77,6 @@ export default {
77
77
  },
78
78
  parameters: {
79
79
  ...defaultParameters,
80
- layout: 'centered',
81
80
  },
82
81
  } as Meta<typeof ContactBlock>;
83
82
 
@@ -17,12 +17,12 @@
17
17
  }
18
18
  &:not(&--large, &--fullscreen) {
19
19
  h1 {
20
- @include inuit-font-size(22px, 26px);
20
+ @include font-size(22px, 26px);
21
21
  }
22
22
  }
23
23
  &--small-heading {
24
24
  h1 {
25
- @include inuit-font-size(22px, 26px);
25
+ @include font-size(22px, 26px);
26
26
  }
27
27
  }
28
28
 
@@ -45,7 +45,7 @@
45
45
  @include mq(tablet) {
46
46
  min-width: 20rem;
47
47
  }
48
- @include font-size(18px);
48
+ @include font-size(18px, 1.33);
49
49
  }
50
50
 
51
51
  &--active &__content {
@@ -90,7 +90,7 @@
90
90
  background: transparent;
91
91
  border: none;
92
92
  color: $brand-color;
93
- @include font-size(20px);
93
+ @include font-size(18px, 1.33);
94
94
  font-weight: $font-weight-normal;
95
95
  padding: 0;
96
96
  box-shadow: $link;
@@ -124,7 +124,6 @@
124
124
  }
125
125
  }
126
126
 
127
-
128
127
  &--fullscreen &__content {
129
128
  @include mq(tablet) {
130
129
  height: 100vh;
@@ -11,6 +11,7 @@ import { Meta, StoryObj } from '@storybook/react';
11
11
  import { AudioEmbedData, AudioMeta } from '@ndla/types-embed';
12
12
  import AudioEmbed from './AudioEmbed';
13
13
  import { defaultParameters } from '../../../../stories/defaults';
14
+ import StoryFavoriteButton from '../../../../stories/StoryFavoriteButton';
14
15
 
15
16
  const embedData: AudioEmbedData = {
16
17
  resource: 'audio',
@@ -156,9 +157,6 @@ const meta: Meta<typeof AudioEmbed> = {
156
157
  title: 'Enkle komponenter/Embeds/AudioEmbed',
157
158
  component: AudioEmbed,
158
159
  tags: ['autodocs'],
159
- args: {
160
- articlePath: undefined,
161
- },
162
160
  decorators: [
163
161
  (Story) => (
164
162
  <div className="o-wrapper">
@@ -179,6 +177,7 @@ export default meta;
179
177
 
180
178
  export const AudioEmbedStory: StoryObj<typeof AudioEmbed> = {
181
179
  args: {
180
+ heartButton: StoryFavoriteButton,
182
181
  embed: {
183
182
  resource: 'audio',
184
183
  status: 'success',
@@ -191,6 +190,7 @@ export const AudioEmbedStory: StoryObj<typeof AudioEmbed> = {
191
190
 
192
191
  export const AudioEmbedFailed: StoryObj<typeof AudioEmbed> = {
193
192
  args: {
193
+ heartButton: StoryFavoriteButton,
194
194
  embed: {
195
195
  resource: 'audio',
196
196
  status: 'error',
@@ -202,6 +202,7 @@ export const AudioEmbedFailed: StoryObj<typeof AudioEmbed> = {
202
202
 
203
203
  export const Podcast: StoryObj<typeof AudioEmbed> = {
204
204
  args: {
205
+ heartButton: StoryFavoriteButton,
205
206
  embed: {
206
207
  resource: 'audio',
207
208
  status: 'success',
@@ -214,6 +215,7 @@ export const Podcast: StoryObj<typeof AudioEmbed> = {
214
215
 
215
216
  export const PodcastFailed: StoryObj<typeof AudioEmbed> = {
216
217
  args: {
218
+ heartButton: StoryFavoriteButton,
217
219
  embed: {
218
220
  resource: 'audio',
219
221
  status: 'error',
@@ -6,30 +6,21 @@
6
6
  *
7
7
  */
8
8
 
9
- import { AudioMetaData } from '@ndla/types-embed';
10
- import { ICopyright } from '@ndla/types-backend/image-api';
11
- import {
12
- figureApa7CopyString,
13
- getGroupedContributorDescriptionList,
14
- getLicenseByAbbreviation,
15
- getLicenseCredits,
16
- } from '@ndla/licenses';
17
- import { ModalV2 } from '@ndla/modal';
18
- import { useState } from 'react';
19
- import { useTranslation } from 'react-i18next';
9
+ import { AudioMetaData, ImageMetaData } from '@ndla/types-embed';
20
10
  //@ts-ignore
21
11
  import { Remarkable } from 'remarkable';
22
- import { ButtonV2, CopyButton } from '@ndla/button';
23
- import { SafeLinkButton } from '@ndla/safelink';
24
12
  import AudioPlayer from '../AudioPlayer';
25
- import { Figure, FigureCaption } from '../Figure';
26
- import { FigureLicenseDialogContent } from '../Figure/FigureLicenseDialogContent';
13
+ import { Figure } from '../Figure';
27
14
  import { Author } from './ImageEmbed';
15
+ import { EmbedByline } from '../LicenseByline';
16
+ import EmbedErrorPlaceholder from './EmbedErrorPlaceholder';
17
+ import { HeartButtonType } from './types';
28
18
 
29
19
  interface Props {
30
20
  embed: AudioMetaData;
31
- articlePath?: string;
21
+ heartButton?: HeartButtonType;
32
22
  }
23
+
33
24
  export const getFirstNonEmptyLicenseCredits = (authors: {
34
25
  creators: Author[];
35
26
  rightsholders: Author[];
@@ -42,29 +33,25 @@ const renderMarkdown = (text: string) => {
42
33
  return <span dangerouslySetInnerHTML={{ __html: rendered }} />;
43
34
  };
44
35
 
45
- const AudioEmbed = ({ embed, articlePath }: Props) => {
46
- const { t, i18n } = useTranslation();
47
- const [isOpen, setIsOpen] = useState(false);
36
+ const imageMetaToMockEmbed = (
37
+ imageMeta: Extract<AudioMetaData, { status: 'success' }>,
38
+ ): Extract<ImageMetaData, { status: 'success' }> => ({
39
+ resource: 'image',
40
+ status: 'success',
41
+ // Make sure the seq is unused. It's rarely used, but it's nice to ensure uniqueness.
42
+ seq: imageMeta.seq + 0.1,
43
+ // We check that this exists where the function is used.
44
+ data: imageMeta.data.imageMeta!,
45
+ embedData: {
46
+ resource: 'image',
47
+ resourceId: imageMeta.data.imageMeta?.id?.toString() || '',
48
+ alt: imageMeta.data.imageMeta?.alttext.alttext ?? '',
49
+ },
50
+ });
51
+
52
+ const AudioEmbed = ({ embed, heartButton: HeartButton }: Props) => {
48
53
  if (embed.status === 'error') {
49
- return (
50
- <Figure>
51
- <svg
52
- fill="#8A8888"
53
- height="50"
54
- viewBox="0 0 24 12"
55
- width="100%"
56
- xmlns="http://www.w3.org/2000/svg"
57
- style={{ backgroundColor: '#EFF0F2' }}
58
- >
59
- <path d="M0 0h24v24H0V0z" fill="none" />
60
- <path
61
- transform="scale(0.3) translate(28, 8.5)"
62
- d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
63
- />
64
- </svg>
65
- <figcaption>{t('audio.error.caption')}</figcaption>
66
- </Figure>
67
- );
54
+ return <EmbedErrorPlaceholder type={embed.embedData.type === 'standard' ? 'audio' : 'podcast'} />;
68
55
  }
69
56
 
70
57
  const { data, embedData, seq } = embed;
@@ -75,181 +62,47 @@ const AudioEmbed = ({ embed, articlePath }: Props) => {
75
62
 
76
63
  const subtitle = data.series ? { title: data.series.title.title, url: `/podkast/${data.series.id}` } : undefined;
77
64
 
78
- const textVersion = data.manuscript && renderMarkdown(data.manuscript.manuscript);
79
- const description = renderMarkdown(data.podcastMeta?.introduction ?? '');
65
+ const textVersion = data.manuscript?.manuscript.length ? renderMarkdown(data.manuscript.manuscript) : undefined;
80
66
 
81
67
  const coverPhoto = data.podcastMeta?.coverPhoto;
82
68
 
83
69
  const img = coverPhoto && { url: coverPhoto.url, alt: coverPhoto.altText };
84
70
 
85
- const authors = getLicenseCredits(data.copyright);
86
-
87
- const license = getLicenseByAbbreviation(data.copyright.license.license, i18n.language);
88
-
89
- const contributors = getGroupedContributorDescriptionList(data.copyright, i18n.language).map((item) => ({
90
- name: item.description,
91
- type: item.label,
92
- }));
93
-
94
71
  const figureId = `figure-${seq}-${data.id}`;
95
72
 
96
- const copyString =
97
- data.audioType === 'podcast'
98
- ? figureApa7CopyString(
99
- data.title.title,
100
- undefined,
101
- data.audioFile.url,
102
- articlePath,
103
- data.copyright,
104
- data.copyright.license.license,
105
- '',
106
- (id: string) => t(id),
107
- i18n.language,
108
- )
109
- : undefined;
110
- const captionAuthors = getFirstNonEmptyLicenseCredits(authors);
111
73
  return (
112
74
  <Figure id={figureId} type="full">
113
75
  <AudioPlayer
114
- description={description}
76
+ description={data.podcastMeta?.introduction ?? ''}
115
77
  img={img}
116
78
  src={data.audioFile.url}
117
79
  textVersion={textVersion}
118
80
  title={data.title.title}
119
81
  subtitle={subtitle}
120
82
  />
121
- <FigureCaption
122
- id=""
123
- figureId=""
124
- modalButton={
125
- <ButtonV2 variant="outline" shape="pill" size="small" onClick={() => setIsOpen(true)}>
126
- {t('audio.reuse')}
127
- </ButtonV2>
128
- }
129
- licenseRights={license.rights}
130
- authors={captionAuthors}
131
- locale={i18n.language}
132
- />
133
- <ModalV2 controlled isOpen={isOpen} onClose={() => setIsOpen(false)} labelledBy="license-dialog-rules-heading">
134
- {(close) => (
135
- <FigureLicenseDialogContent
136
- onClose={close}
137
- title={data.title.title}
138
- license={license}
139
- authors={contributors}
140
- origin={data.copyright.origin}
141
- locale={i18n.language}
142
- type="audio"
143
- >
144
- {data.copyright.license.license !== 'COPYRIGHT' && (
145
- <>
146
- {copyString && (
147
- <CopyButton
148
- variant="outline"
149
- copyNode={t('license.hasCopiedTitle')}
150
- onClick={() => navigator.clipboard.writeText(copyString)}
151
- >
152
- {t('license.copyTitle')}
153
- </CopyButton>
154
- )}
155
- <SafeLinkButton download to={data.audioFile.url} variant="outline">
156
- {t('audio.download')}
157
- </SafeLinkButton>
158
- </>
159
- )}
160
- </FigureLicenseDialogContent>
161
- )}
162
- </ModalV2>
83
+ <EmbedByline
84
+ error={false}
85
+ type={embedData.type === 'standard' ? 'audio' : 'podcast'}
86
+ topRounded={false}
87
+ bottomRounded={!data.imageMeta}
88
+ copyright={embed.data.copyright}
89
+ >
90
+ {HeartButton && <HeartButton embed={embed} />}
91
+ </EmbedByline>
163
92
  {data.imageMeta && (
164
- <ImageLicense
165
- title={data.imageMeta.title.title}
166
- imageUrl={data.imageMeta.imageUrl}
93
+ <EmbedByline
94
+ error={false}
95
+ first={false}
96
+ type="image"
97
+ topRounded={false}
98
+ bottomRounded
167
99
  copyright={data.imageMeta.copyright}
168
- articlePath={articlePath}
169
- />
100
+ >
101
+ {HeartButton && <HeartButton embed={imageMetaToMockEmbed(embed)} />}
102
+ </EmbedByline>
170
103
  )}
171
104
  </Figure>
172
105
  );
173
106
  };
174
107
 
175
- interface ImageLicenseProps {
176
- title: string;
177
- imageUrl: string;
178
- copyright: ICopyright;
179
- articlePath?: string;
180
- }
181
-
182
- const ImageLicense = ({ articlePath, title, imageUrl, copyright }: ImageLicenseProps) => {
183
- const { t, i18n } = useTranslation();
184
- const [isOpen, setIsOpen] = useState(false);
185
- const copyString = figureApa7CopyString(
186
- title,
187
- undefined,
188
- imageUrl,
189
- articlePath,
190
- copyright,
191
- copyright.license.license,
192
- undefined,
193
- (id: string) => t(id),
194
- i18n.language,
195
- );
196
- const license = getLicenseByAbbreviation(copyright.license.license, i18n.language);
197
- const authors = getLicenseCredits(copyright);
198
-
199
- const contributors = getGroupedContributorDescriptionList(copyright, i18n.language).map((item) => ({
200
- name: item.description,
201
- type: item.label,
202
- }));
203
-
204
- const captionAuthors = getFirstNonEmptyLicenseCredits(authors);
205
-
206
- return (
207
- <>
208
- <FigureCaption
209
- figureId=""
210
- id=""
211
- licenseRights={license.rights}
212
- modalButton={
213
- <ButtonV2 variant="outline" shape="pill" size="small" onClick={() => setIsOpen(true)}>
214
- {t('image.reuse')}
215
- </ButtonV2>
216
- }
217
- authors={captionAuthors}
218
- locale={i18n.language}
219
- >
220
- <ModalV2 controlled isOpen={isOpen} onClose={() => setIsOpen(false)}>
221
- {(close) => (
222
- <FigureLicenseDialogContent
223
- onClose={close}
224
- title={title}
225
- license={license}
226
- authors={contributors}
227
- origin={copyright.origin}
228
- locale={i18n.language}
229
- type="image"
230
- >
231
- {copyright.license.license !== 'COPYRIGHTED' && (
232
- <>
233
- {copyString && (
234
- <CopyButton
235
- variant="outline"
236
- copyNode={t('license.hasCopiedTitle')}
237
- onClick={() => navigator.clipboard.writeText(copyString)}
238
- >
239
- {t('license.copyTitle')}
240
- </CopyButton>
241
- )}
242
- <SafeLinkButton download to={imageUrl} variant="outline">
243
- {t('image.download')}
244
- </SafeLinkButton>
245
- </>
246
- )}
247
- </FigureLicenseDialogContent>
248
- )}
249
- </ModalV2>
250
- </FigureCaption>
251
- </>
252
- );
253
- };
254
-
255
108
  export default AudioEmbed;
@@ -8,9 +8,10 @@
8
8
 
9
9
  import React from 'react';
10
10
  import { Meta, StoryObj } from '@storybook/react';
11
- import { BrightcoveData, BrightcoveEmbedData, BrightcoveMetaData } from '@ndla/types-embed';
11
+ import { BrightcoveData, BrightcoveEmbedData, BrightcoveMetaData, EmbedMetaData } from '@ndla/types-embed';
12
12
  import BrightcoveEmbed from './BrightcoveEmbed';
13
13
  import { defaultParameters } from '../../../../stories/defaults';
14
+ import StoryFavoriteButton from '../../../../stories/StoryFavoriteButton';
14
15
 
15
16
  const embedData: BrightcoveEmbedData = {
16
17
  resource: 'brightcove',
@@ -179,6 +180,7 @@ export default meta;
179
180
 
180
181
  export const BrightcoveEmbedStory: StoryObj<typeof BrightcoveEmbed> = {
181
182
  args: {
183
+ heartButton: StoryFavoriteButton,
182
184
  embed: {
183
185
  resource: 'brightcove',
184
186
  status: 'success',
@@ -191,12 +193,14 @@ export const BrightcoveEmbedStory: StoryObj<typeof BrightcoveEmbed> = {
191
193
 
192
194
  export const VisuallyInterpreted: StoryObj<typeof BrightcoveEmbed> = {
193
195
  args: {
196
+ heartButton: StoryFavoriteButton,
194
197
  embed: visuallyInterpretedEmbedMetaData,
195
198
  },
196
199
  };
197
200
 
198
201
  export const BrightcoveFailed: StoryObj<typeof BrightcoveEmbed> = {
199
202
  args: {
203
+ heartButton: StoryFavoriteButton,
200
204
  embed: {
201
205
  resource: 'brightcove',
202
206
  status: 'error',