@ndla/ui 56.0.18-alpha.0 → 56.0.20-alpha.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 (115) hide show
  1. package/dist/panda.buildinfo.json +10 -8
  2. package/dist/styles.css +35 -29
  3. package/es/Article/Article.js +3 -3
  4. package/es/Article/ArticleByline.js +5 -4
  5. package/es/Article/index.js +1 -2
  6. package/es/AudioPlayer/AudioPlayer.js +1 -1
  7. package/es/CampaignBlock/CampaignBlock.js +25 -13
  8. package/es/Concept/Concept.js +7 -1
  9. package/es/ContactBlock/ContactBlock.js +0 -1
  10. package/es/ContentTypeBadge/ContentTypeBadgeNew.js +0 -3
  11. package/es/ContentTypeHero/ContentTypeHero.js +2 -8
  12. package/es/Embed/AudioEmbed.js +3 -0
  13. package/es/Embed/BrightcoveEmbed.js +5 -1
  14. package/es/Embed/ConceptEmbed.js +23 -11
  15. package/es/Embed/ConceptListEmbed.js +2 -1
  16. package/es/Embed/ImageEmbed.js +3 -0
  17. package/es/Embed/index.js +1 -0
  18. package/es/FileList/FileList.js +5 -1
  19. package/es/FileList/PdfFile.js +6 -1
  20. package/es/FileList/index.js +1 -1
  21. package/es/Grid/Grid.js +0 -1
  22. package/es/LicenseByline/EmbedByline.js +4 -1
  23. package/es/TreeStructure/TreeStructure.js +0 -2
  24. package/es/i18n/index.js +1 -1
  25. package/es/i18n/useComponentTranslations.js +62 -0
  26. package/es/index.js +7 -5
  27. package/es/locale/messages-en.js +31 -5
  28. package/es/locale/messages-nb.js +31 -5
  29. package/es/locale/messages-nn.js +79 -53
  30. package/es/locale/messages-se.js +31 -5
  31. package/es/locale/messages-sma.js +31 -5
  32. package/es/styles.css +35 -29
  33. package/es/utils/licenseAttributes.js +18 -0
  34. package/lib/Article/Article.js +3 -3
  35. package/lib/Article/ArticleByline.js +5 -4
  36. package/lib/Article/index.d.ts +0 -1
  37. package/lib/Article/index.js +1 -8
  38. package/lib/AudioPlayer/AudioPlayer.js +1 -1
  39. package/lib/CampaignBlock/CampaignBlock.js +25 -13
  40. package/lib/Concept/Concept.d.ts +2 -0
  41. package/lib/Concept/Concept.js +7 -1
  42. package/lib/ContactBlock/ContactBlock.js +0 -1
  43. package/lib/ContentTypeBadge/ContentTypeBadgeNew.js +0 -3
  44. package/lib/ContentTypeHero/ContentTypeHero.js +2 -7
  45. package/lib/Embed/AudioEmbed.js +3 -0
  46. package/lib/Embed/BrightcoveEmbed.d.ts +2 -1
  47. package/lib/Embed/BrightcoveEmbed.js +5 -1
  48. package/lib/Embed/ConceptEmbed.d.ts +3 -1
  49. package/lib/Embed/ConceptEmbed.js +23 -11
  50. package/lib/Embed/ConceptListEmbed.js +2 -1
  51. package/lib/Embed/ImageEmbed.js +3 -0
  52. package/lib/Embed/index.d.ts +1 -0
  53. package/lib/Embed/index.js +7 -0
  54. package/lib/FileList/FileList.d.ts +3 -1
  55. package/lib/FileList/FileList.js +5 -1
  56. package/lib/FileList/PdfFile.js +6 -1
  57. package/lib/FileList/index.d.ts +1 -1
  58. package/lib/FileList/index.js +6 -0
  59. package/lib/Grid/Grid.js +0 -1
  60. package/lib/LicenseByline/EmbedByline.js +4 -1
  61. package/lib/TreeStructure/TreeStructure.js +0 -2
  62. package/lib/i18n/index.d.ts +1 -1
  63. package/lib/i18n/index.js +12 -0
  64. package/lib/i18n/useComponentTranslations.d.ts +31 -0
  65. package/lib/i18n/useComponentTranslations.js +65 -2
  66. package/lib/index.d.ts +6 -4
  67. package/lib/index.js +38 -6
  68. package/lib/locale/messages-en.d.ts +26 -0
  69. package/lib/locale/messages-en.js +31 -5
  70. package/lib/locale/messages-nb.d.ts +26 -0
  71. package/lib/locale/messages-nb.js +31 -5
  72. package/lib/locale/messages-nn.d.ts +26 -0
  73. package/lib/locale/messages-nn.js +79 -53
  74. package/lib/locale/messages-se.d.ts +26 -0
  75. package/lib/locale/messages-se.js +31 -5
  76. package/lib/locale/messages-sma.d.ts +26 -0
  77. package/lib/locale/messages-sma.js +31 -5
  78. package/lib/styles.css +35 -29
  79. package/lib/utils/licenseAttributes.d.ts +16 -0
  80. package/lib/utils/licenseAttributes.js +25 -0
  81. package/package.json +8 -8
  82. package/src/Article/Article.tsx +7 -6
  83. package/src/Article/ArticleByline.tsx +2 -3
  84. package/src/Article/index.ts +0 -2
  85. package/src/AudioPlayer/AudioPlayer.tsx +5 -3
  86. package/src/CampaignBlock/CampaignBlock.tsx +23 -12
  87. package/src/Concept/Concept.tsx +8 -3
  88. package/src/ContactBlock/ContactBlock.tsx +0 -1
  89. package/src/ContentTypeBadge/ContentTypeBadgeNew.tsx +0 -3
  90. package/src/ContentTypeHero/ContentTypeHero.tsx +2 -7
  91. package/src/Embed/AudioEmbed.tsx +4 -1
  92. package/src/Embed/BrightcoveEmbed.tsx +6 -3
  93. package/src/Embed/ConceptEmbed.tsx +21 -11
  94. package/src/Embed/ConceptListEmbed.tsx +1 -0
  95. package/src/Embed/ImageEmbed.tsx +4 -0
  96. package/src/Embed/index.ts +1 -0
  97. package/src/FileList/FileList.tsx +16 -10
  98. package/src/FileList/PdfFile.tsx +8 -2
  99. package/src/FileList/index.ts +1 -1
  100. package/src/Grid/Grid.tsx +0 -1
  101. package/src/LicenseByline/EmbedByline.tsx +2 -1
  102. package/src/TreeStructure/TreeStructure.tsx +0 -1
  103. package/src/i18n/index.ts +2 -0
  104. package/src/i18n/useComponentTranslations.ts +79 -0
  105. package/src/index.ts +8 -2
  106. package/src/locale/messages-en.ts +30 -3
  107. package/src/locale/messages-nb.ts +30 -3
  108. package/src/locale/messages-nn.ts +78 -51
  109. package/src/locale/messages-se.ts +30 -3
  110. package/src/locale/messages-sma.ts +30 -3
  111. package/src/utils/licenseAttributes.ts +23 -0
  112. package/es/Article/ArticleParagraph.js +0 -23
  113. package/lib/Article/ArticleParagraph.d.ts +0 -13
  114. package/lib/Article/ArticleParagraph.js +0 -30
  115. package/src/Article/ArticleParagraph.tsx +0 -27
package/lib/styles.css CHANGED
@@ -93,10 +93,6 @@
93
93
  display: flex;
94
94
  }
95
95
 
96
- .gap_xxlarge {
97
- gap: var(--spacing-xxlarge);
98
- }
99
-
100
96
  .c_text\.default {
101
97
  color: var(--colors-text-default);
102
98
  }
@@ -121,6 +117,10 @@
121
117
  padding-block-start: var(--spacing-xxlarge);
122
118
  }
123
119
 
120
+ .gap_xxlarge {
121
+ gap: var(--spacing-xxlarge);
122
+ }
123
+
124
124
  .min-h_xxlarge {
125
125
  min-height: var(--sizes-xxlarge);
126
126
  }
@@ -326,10 +326,18 @@
326
326
  text-decoration: underline;
327
327
  }
328
328
 
329
+ .d_grid {
330
+ display: grid;
331
+ }
332
+
329
333
  .h_215px {
330
334
  height: 215px;
331
335
  }
332
336
 
337
+ .min-w_270px {
338
+ min-width: 270px;
339
+ }
340
+
333
341
  .bd-l_4px_solid {
334
342
  border-left: 4px solid;
335
343
  }
@@ -438,6 +446,10 @@
438
446
  width: var(--sizes-surface-xlarge);
439
447
  }
440
448
 
449
+ .max-h_50vh {
450
+ max-height: 50vh;
451
+ }
452
+
441
453
  .c_text\.error {
442
454
  color: var(--colors-text-error);
443
455
  }
@@ -539,14 +551,6 @@
539
551
  margin-block-start: var(--spacing-3xsmall);
540
552
  }
541
553
 
542
- .d_grid {
543
- display: grid;
544
- }
545
-
546
- .max-w_surface\.4xlarge {
547
- max-width: var(--sizes-surface-4xlarge);
548
- }
549
-
550
554
  .w_surface\.3xsmall {
551
555
  width: var(--sizes-surface-3xsmall);
552
556
  }
@@ -678,12 +682,12 @@
678
682
  align-items: flex-end;
679
683
  }
680
684
 
681
- .fw_bold {
682
- font-weight: var(--font-weights-bold);
685
+ .grid-tc_1fr {
686
+ grid-template-columns: 1fr;
683
687
  }
684
688
 
685
- .as_center {
686
- align-self: center;
689
+ .fw_bold {
690
+ font-weight: var(--font-weights-bold);
687
691
  }
688
692
 
689
693
  .bd-l-c_stroke\.default {
@@ -722,6 +726,10 @@
722
726
  top: calc(var(--spacing-4xsmall) * -1);
723
727
  }
724
728
 
729
+ .ov-y_auto {
730
+ overflow-y: auto;
731
+ }
732
+
725
733
  .bg-c_surface\.disabled {
726
734
  background-color: var(--colors-surface-disabled);
727
735
  }
@@ -900,14 +908,6 @@
900
908
  padding-block-end: var(--spacing-5xlarge);
901
909
  }
902
910
 
903
- .\[\&\[data-align\=\"center\"\]\]\:ta_center[data-align="center"] {
904
- text-align: center;
905
- }
906
-
907
- .\[\&\:has\(span\[dir\=\"rtl\"\]\)\]\:direction_rtl:has(span[dir="rtl"]) {
908
- direction: rtl;
909
- }
910
-
911
911
  .\[\&_img\]\:w_100\% img {
912
912
  width: 100%;
913
913
  }
@@ -1448,9 +1448,6 @@
1448
1448
  }
1449
1449
 
1450
1450
  @media screen and (min-width: 29.75rem) {
1451
- .mobileWide\:h_340px {
1452
- height: 340px;
1453
- }
1454
1451
  .mobileWide\:d_none {
1455
1452
  display: none;
1456
1453
  }
@@ -1465,6 +1462,9 @@
1465
1462
  @media screen and (min-width: 37.5625rem) {
1466
1463
  .tablet\:px_medium {
1467
1464
  padding-inline: var(--spacing-medium);
1465
+ }
1466
+ .tablet\:h_265px {
1467
+ height: 265px;
1468
1468
  }
1469
1469
  .tablet\:h_26px {
1470
1470
  height: 26px;
@@ -1544,8 +1544,8 @@
1544
1544
  .tabletWide\:max-w_532px {
1545
1545
  max-width: 532px;
1546
1546
  }
1547
- .tabletWide\:w_auto {
1548
- width: auto;
1547
+ .tabletWide\:h_340px {
1548
+ height: 340px;
1549
1549
  }
1550
1550
  .tabletWide\:d_block {
1551
1551
  display: block;
@@ -1570,6 +1570,12 @@
1570
1570
  }
1571
1571
  .tabletWide\:jc_space-between {
1572
1572
  justify-content: space-between;
1573
+ }
1574
+ .tabletWide\:grid-tc_minmax\(230px\,_455px\)_auto {
1575
+ grid-template-columns: minmax(230px, 455px) auto;
1576
+ }
1577
+ .tabletWide\:grid-tc_auto_minmax\(230px\,_455px\) {
1578
+ grid-template-columns: auto minmax(230px, 455px);
1573
1579
  }
1574
1580
  }
1575
1581
 
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) 2024-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
+ export declare const licenseAttributes: (license: string | undefined, lang: string | undefined, url: string | undefined) => {
9
+ "xmlns:cc": string;
10
+ "xmlns:dct": string;
11
+ about: string | undefined;
12
+ } | {
13
+ "xmlns:cc"?: undefined;
14
+ "xmlns:dct"?: undefined;
15
+ about?: undefined;
16
+ };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.licenseAttributes = void 0;
7
+ var _licenses = require("@ndla/licenses");
8
+ /**
9
+ * Copyright (c) 2024-present, NDLA.
10
+ *
11
+ * This source code is licensed under the GPLv3 license found in the
12
+ * LICENSE file in the root directory of this source tree.
13
+ *
14
+ */
15
+
16
+ const licenseAttributes = (license, lang, url) => {
17
+ const licenseAbbr = (0, _licenses.getLicenseByAbbreviation)(license ? license : "", lang);
18
+ const licenseProps = (0, _licenses.isCreativeCommonsLicense)(licenseAbbr.rights) ? {
19
+ "xmlns:cc": "https://creativecommons.org/ns#",
20
+ "xmlns:dct": "http://purl.org/dc/terms/",
21
+ about: url ?? undefined
22
+ } : {};
23
+ return licenseProps;
24
+ };
25
+ exports.licenseAttributes = licenseAttributes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
- "version": "56.0.18-alpha.0",
3
+ "version": "56.0.20-alpha.0",
4
4
  "description": "UI component library for NDLA",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -34,11 +34,11 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "@ndla/core": "^5.0.2",
37
- "@ndla/icons": "^8.0.12-alpha.0",
38
- "@ndla/licenses": "^8.0.1-alpha.0",
39
- "@ndla/primitives": "^1.0.16-alpha.0",
40
- "@ndla/safelink": "^7.0.16-alpha.0",
41
- "@ndla/styled-system": "^0.0.14",
37
+ "@ndla/icons": "^8.0.14-alpha.0",
38
+ "@ndla/licenses": "^8.0.2-alpha.0",
39
+ "@ndla/primitives": "^1.0.18-alpha.0",
40
+ "@ndla/safelink": "^7.0.18-alpha.0",
41
+ "@ndla/styled-system": "^0.0.16",
42
42
  "@ndla/util": "^4.1.0",
43
43
  "html-react-parser": "^5.1.8",
44
44
  "i18next-browser-languagedetector": "^7.1.0"
@@ -53,7 +53,7 @@
53
53
  "react-router-dom": "> 6.0.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@ndla/preset-panda": "^0.0.22",
56
+ "@ndla/preset-panda": "^0.0.24",
57
57
  "@ndla/types-backend": "^0.2.86",
58
58
  "@ndla/types-embed": "^5.0.1-alpha.0",
59
59
  "@pandacss/dev": "^0.45.2",
@@ -66,5 +66,5 @@
66
66
  "publishConfig": {
67
67
  "access": "public"
68
68
  },
69
- "gitHead": "77cd2b0c19a7631796405960ebf799f50e93b44c"
69
+ "gitHead": "66b3b382188ec1730fa409d4adb3672942f00670"
70
70
  }
@@ -32,7 +32,6 @@ const StyledArticleWrapper = styled(
32
32
  background: "background.default",
33
33
  display: "flex",
34
34
  flexDirection: "column",
35
- gap: "xxlarge",
36
35
  color: "text.default",
37
36
  alignItems: "center",
38
37
  width: "100%",
@@ -131,11 +130,13 @@ export const ArticleTitle = ({
131
130
  return (
132
131
  <ArticleHeader>
133
132
  <ArticleHGroup>
134
- <StyledStack justify="space-between" align="center" direction="row" gap="small">
135
- {!!contentType && <ContentTypeBadgeNew contentType={contentType} />}
136
- {heartButton}
137
- </StyledStack>
138
- <Heading textStyle="heading.large" id={id} lang={lang}>
133
+ {(!!contentType || !!heartButton) && (
134
+ <StyledStack justify="space-between" align="center" direction="row" gap="small">
135
+ {!!contentType && <ContentTypeBadgeNew contentType={contentType} />}
136
+ {heartButton}
137
+ </StyledStack>
138
+ )}
139
+ <Heading textStyle="heading.medium" id={id} lang={lang} property="dct:title">
139
140
  {title}
140
141
  </Heading>
141
142
  </ArticleHGroup>
@@ -36,8 +36,6 @@ const Wrapper = styled("div", {
36
36
  },
37
37
  });
38
38
 
39
- // TODO: This is designed with 24px of inline padding. If you do this, most bylines will break into two lines.
40
- // Should reconsider.
41
39
  const TextWrapper = styled("div", {
42
40
  base: {
43
41
  display: "flex",
@@ -169,7 +167,8 @@ export const ArticleByline = ({
169
167
  <LicenseWrapper>
170
168
  {license && <LicenseLink license={license} />}
171
169
  {showPrimaryContributors && (
172
- <span>
170
+ //eslint-disable-next-line react/no-unknown-property
171
+ <span property="cc:attributionName">
173
172
  {authors.length > 0 &&
174
173
  `${t("article.authorsLabel", {
175
174
  names: renderContributors(authors, t),
@@ -18,5 +18,3 @@ export {
18
18
 
19
19
  export { ArticleByline, ArticleBylineAccordionItem } from "./ArticleByline";
20
20
  export { ArticleFootNotes } from "./ArticleFootNotes";
21
-
22
- export { ArticleParagraph } from "./ArticleParagraph";
@@ -190,9 +190,11 @@ const AudioPlayer = ({ src, title, subtitle, speech, description, img, textVersi
190
190
  {showFullDescription || description.length < DESCRIPTION_MAX_LENGTH
191
191
  ? description
192
192
  : `${truncatedDescription}...`}
193
- <Button variant="link" onClick={() => setShowFullDescription((p) => !p)}>
194
- {t(`audio.${showFullDescription ? "readLessDescriptionLabel" : "readMoreDescriptionLabel"}`)}
195
- </Button>
193
+ {description.length > DESCRIPTION_MAX_LENGTH && (
194
+ <Button variant="link" onClick={() => setShowFullDescription((p) => !p)}>
195
+ {t(`audio.${showFullDescription ? "readLessDescriptionLabel" : "readMoreDescriptionLabel"}`)}
196
+ </Button>
197
+ )}
196
198
  </Text>
197
199
  )}
198
200
  {!!textVersion && !!img && textVersionButton}
@@ -36,21 +36,33 @@ interface Props {
36
36
 
37
37
  const Container = styled("div", {
38
38
  base: {
39
- width: "100%",
40
- display: "flex",
39
+ display: "grid",
40
+ gridTemplateColumns: "1fr",
41
41
  gap: "medium",
42
- flexDirection: "column",
43
42
  border: "1px solid",
44
43
  borderColor: "stroke.default",
45
44
  backgroundColor: "background.default",
46
45
  borderRadius: "xsmall",
47
46
  boxShadow: "full",
48
- marginBlockEnd: "4xsmall",
49
47
  overflow: "hidden",
50
- tabletWide: {
51
- flexDirection: "row",
48
+ },
49
+ variants: {
50
+ imageSide: {
51
+ left: {
52
+ tabletWide: {
53
+ gridTemplateColumns: "minmax(230px, 455px) auto", //required for campaign block in myNdla
54
+ },
55
+ },
56
+ right: {
57
+ tabletWide: {
58
+ gridTemplateColumns: "auto minmax(230px, 455px)", //required for campaign block in myNdla
59
+ },
60
+ },
52
61
  },
53
62
  },
63
+ defaultVariants: {
64
+ imageSide: "left",
65
+ },
54
66
  });
55
67
 
56
68
  const LinkText = styled(Text, {
@@ -78,15 +90,14 @@ const LinkHeader = styled(Text, {
78
90
 
79
91
  const StyledImg = styled("img", {
80
92
  base: {
81
- alignSelf: "center",
82
93
  objectFit: "cover",
83
94
  width: "100%",
84
95
  height: "215px",
85
- mobileWide: {
86
- height: "340px",
96
+ tablet: {
97
+ height: "265px",
87
98
  },
88
99
  tabletWide: {
89
- width: "auto",
100
+ height: "340px",
90
101
  },
91
102
  },
92
103
  });
@@ -94,7 +105,6 @@ const StyledImg = styled("img", {
94
105
  const ContentWrapper = styled("div", {
95
106
  base: {
96
107
  width: "100%",
97
- position: "relative",
98
108
  display: "flex",
99
109
  flexDirection: "column",
100
110
  gap: "medium",
@@ -102,6 +112,7 @@ const ContentWrapper = styled("div", {
102
112
  justifyContent: "center",
103
113
  paddingBlock: "medium",
104
114
  paddingInline: "medium",
115
+ minWidth: "270px", //required for campaign block in myNdla
105
116
  },
106
117
  });
107
118
 
@@ -147,7 +158,7 @@ const CampaignBlock = ({
147
158
  const imageComponent = image && <StyledImg src={`${image.src}?width=455`} height={340} width={455} alt={image.alt} />;
148
159
  const HeaderComponent = url?.url ? LinkHeader : Text;
149
160
  return (
150
- <Container className={className} data-embed-type="campaign-block">
161
+ <Container className={className} data-embed-type="campaign-block" imageSide={imageSide}>
151
162
  {imageSide === "left" && imageComponent}
152
163
  <ContentWrapper>
153
164
  <MaybeLinkText url={url?.url} path={path}>
@@ -13,6 +13,7 @@ import { IDraftCopyright as ConceptCopyright } from "@ndla/types-backend/concept
13
13
  import { ConceptVisualElementMeta } from "@ndla/types-embed";
14
14
  import { BrightcoveEmbed, ExternalEmbed, H5pEmbed, IframeEmbed, ImageEmbed } from "../Embed";
15
15
  import { EmbedByline } from "../LicenseByline/EmbedByline";
16
+ import { licenseAttributes } from "../utils/licenseAttributes";
16
17
 
17
18
  export interface ConceptProps extends ComponentPropsWithRef<"figure"> {
18
19
  copyright?: ConceptCopyright;
@@ -20,6 +21,8 @@ export interface ConceptProps extends ComponentPropsWithRef<"figure"> {
20
21
  lang?: string;
21
22
  title?: string;
22
23
  children?: ReactNode;
24
+ source?: string;
25
+ previewAlt?: boolean;
23
26
  }
24
27
 
25
28
  const StyledFigure = styled(Figure, {
@@ -43,9 +46,11 @@ const ContentWrapper = styled("div", {
43
46
  // TODO: Figure out if we need to support headerButtons.
44
47
 
45
48
  export const Concept = forwardRef<HTMLElement, ConceptProps>(
46
- ({ copyright, visualElement, lang, children, title, ...rest }, ref) => {
49
+ ({ copyright, visualElement, lang, children, title, source, previewAlt, ...rest }, ref) => {
50
+ const licenseProps = licenseAttributes(copyright?.license?.license, lang, source);
51
+
47
52
  return (
48
- <StyledFigure ref={ref} {...rest}>
53
+ <StyledFigure ref={ref} {...rest} {...licenseProps}>
49
54
  <ContentWrapper lang={lang}>
50
55
  {!!title && (
51
56
  <>
@@ -56,7 +61,7 @@ export const Concept = forwardRef<HTMLElement, ConceptProps>(
56
61
  {children}
57
62
  </ContentWrapper>
58
63
  {visualElement?.resource === "image" ? (
59
- <ImageEmbed embed={visualElement} lang={lang} />
64
+ <ImageEmbed embed={visualElement} lang={lang} previewAlt={previewAlt} />
60
65
  ) : visualElement?.resource === "brightcove" ? (
61
66
  <BrightcoveEmbed embed={visualElement} />
62
67
  ) : visualElement?.resource === "h5p" ? (
@@ -31,7 +31,6 @@ const StyledWrapper = styled("div", {
31
31
  alignItems: "center",
32
32
  justifyContent: "space-between",
33
33
  boxShadow: "full",
34
- marginBlockEnd: "4xsmall",
35
34
  border: "1px solid",
36
35
  gap: "medium",
37
36
  position: "relative",
@@ -42,12 +42,9 @@ export const contentTypeToBadgeVariantMap: Record<ContentType, BadgeVariant> = {
42
42
  [contentTypes.SOURCE_MATERIAL]: "brand1",
43
43
  [contentTypes.LEARNING_PATH]: "brand3",
44
44
  [contentTypes.TOPIC]: "neutral",
45
- // TODO: Verify this color
46
45
  [contentTypes.MULTIDISCIPLINARY]: "neutral",
47
46
  [contentTypes.CONCEPT]: "brand1",
48
- // TODO: Verify this color
49
47
  [contentTypes.EXTERNAL]: "brand2",
50
- // TODO: Verify resourceEmbedTypeMapping colors
51
48
  [contentTypes.IMAGE]: "brand1",
52
49
  [contentTypes.AUDIO]: "brand1",
53
50
  [contentTypes.PODCAST]: "brand1",
@@ -11,23 +11,18 @@ import { Hero, HeroProps, HeroVariant } from "@ndla/primitives";
11
11
  import { ContentType } from "../ContentTypeBadge/ContentTypeBadgeNew";
12
12
  import * as contentTypes from "../model/ContentType";
13
13
 
14
- // TODO: Figure out what to do with frontpage articles. If anything...
15
- // Also, verify all of these colors.
16
14
  export const contentTypeToHeroMap: Record<ContentType, HeroVariant> = {
17
15
  [contentTypes.SUBJECT_MATERIAL]: "primary",
18
- [contentTypes.TASKS_AND_ACTIVITIES]: "brand2Strong",
16
+ [contentTypes.TASKS_AND_ACTIVITIES]: "brand2Bold",
19
17
  [contentTypes.ASSESSMENT_RESOURCES]: "brand2",
20
18
  // This will never happen
21
19
  [contentTypes.SUBJECT]: "primary",
22
20
  [contentTypes.SOURCE_MATERIAL]: "brand1",
23
21
  // This will never happen
24
22
  [contentTypes.LEARNING_PATH]: "primary",
25
- // TODO: This needs a color
26
23
  [contentTypes.TOPIC]: "neutral",
27
- // TODO: This is just taken from thin air.
28
- [contentTypes.MULTIDISCIPLINARY]: "brand4",
24
+ [contentTypes.MULTIDISCIPLINARY]: "primary",
29
25
  [contentTypes.CONCEPT]: "brand1Subtle",
30
- // TODO: No clue what this'll be. Maybe unused?
31
26
  [contentTypes.EXTERNAL]: "primary",
32
27
  [contentTypes.IMAGE]: "primary",
33
28
  [contentTypes.AUDIO]: "primary",
@@ -12,6 +12,7 @@ import EmbedErrorPlaceholder from "./EmbedErrorPlaceholder";
12
12
  import { Author } from "./ImageEmbed";
13
13
  import AudioPlayer from "../AudioPlayer";
14
14
  import { EmbedByline } from "../LicenseByline";
15
+ import { licenseAttributes } from "../utils/licenseAttributes";
15
16
 
16
17
  interface Props {
17
18
  embed: AudioMetaData;
@@ -42,8 +43,10 @@ const AudioEmbed = ({ embed, lang }: Props) => {
42
43
 
43
44
  const img = coverPhoto && { url: coverPhoto.url, alt: coverPhoto.altText };
44
45
 
46
+ const licenseProps = licenseAttributes(data.copyright.license.license, lang, embedData.url);
47
+
45
48
  return (
46
- <Figure lang={lang} data-embed-type={type}>
49
+ <Figure lang={lang} data-embed-type={type} {...licenseProps}>
47
50
  <AudioPlayer
48
51
  description={data.podcastMeta?.introduction ?? ""}
49
52
  img={img}
@@ -15,10 +15,12 @@ import { BrightcoveEmbedData, BrightcoveMetaData, BrightcoveVideoSource } from "
15
15
  import EmbedErrorPlaceholder from "./EmbedErrorPlaceholder";
16
16
  import { RenderContext } from "./types";
17
17
  import { EmbedByline } from "../LicenseByline";
18
+ import { licenseAttributes } from "../utils/licenseAttributes";
18
19
 
19
20
  interface Props {
20
21
  embed: BrightcoveMetaData;
21
22
  renderContext?: RenderContext;
23
+ lang?: string;
22
24
  }
23
25
 
24
26
  const LinkedVideoButton = styled(Button, {
@@ -54,7 +56,7 @@ const getIframeProps = (data: BrightcoveEmbedData, sources: BrightcoveVideoSourc
54
56
  width: source?.width ?? "640",
55
57
  };
56
58
  };
57
- const BrightcoveEmbed = ({ embed, renderContext = "article" }: Props) => {
59
+ const BrightcoveEmbed = ({ embed, renderContext = "article", lang }: Props) => {
58
60
  const [showOriginalVideo, setShowOriginalVideo] = useState(true);
59
61
  const { t } = useTranslation();
60
62
  const iframeRef = useRef<HTMLIFrameElement>(null);
@@ -100,8 +102,10 @@ const BrightcoveEmbed = ({ embed, renderContext = "article" }: Props) => {
100
102
  ? getIframeProps({ ...embedData, videoid: linkedVideoId }, data.sources)
101
103
  : undefined;
102
104
 
105
+ const licenseProps = licenseAttributes(data?.copyright?.license.license, lang, embedData.pageUrl);
106
+
103
107
  return (
104
- <Figure data-embed-type="brightcove">
108
+ <Figure data-embed-type="brightcove" {...licenseProps}>
105
109
  <div className="brightcove-video">
106
110
  <BrightcoveIframe
107
111
  ref={iframeRef}
@@ -114,7 +118,6 @@ const BrightcoveEmbed = ({ embed, renderContext = "article" }: Props) => {
114
118
  />
115
119
  </div>
116
120
  <EmbedByline type="video" copyright={data.copyright!} description={parsedDescription}>
117
- {/* TODO: Figure out if this button should still be here. If yes, figure out what it should look like. */}
118
121
  {!!linkedVideoId && (
119
122
  <LinkedVideoButton size="small" variant="secondary" onClick={() => setShowOriginalVideo((p) => !p)}>
120
123
  {t(`figure.button.${!showOriginalVideo ? "original" : "alternative"}`)}
@@ -8,6 +8,7 @@
8
8
 
9
9
  import parse from "html-react-parser";
10
10
  import { forwardRef, useMemo } from "react";
11
+ import { Portal } from "@ark-ui/react";
11
12
  import { PopoverContent, PopoverRoot, PopoverTrigger } from "@ndla/primitives";
12
13
  import { styled } from "@ndla/styled-system/jsx";
13
14
  import { ConceptMetaData } from "@ndla/types-embed";
@@ -20,6 +21,7 @@ import { Concept, ConceptProps } from "../Concept/Concept";
20
21
  interface BaseProps {
21
22
  renderContext?: RenderContext;
22
23
  lang?: string;
24
+ previewAlt?: boolean;
23
25
  }
24
26
 
25
27
  interface Props extends BaseProps {
@@ -29,10 +31,12 @@ interface Props extends BaseProps {
29
31
  const StyledPopoverContent = styled(PopoverContent, {
30
32
  base: {
31
33
  width: "surface.xlarge",
34
+ maxHeight: "50vh",
35
+ overflowY: "auto",
32
36
  },
33
37
  });
34
38
 
35
- export const ConceptEmbed = ({ embed, renderContext, lang }: Props) => {
39
+ export const ConceptEmbed = ({ embed, renderContext, lang, previewAlt }: Props) => {
36
40
  const parsedContent = useMemo(() => {
37
41
  if (embed.status === "error" || !embed.data.concept.content) return undefined;
38
42
  return parse(embed.data.concept.content.htmlContent);
@@ -55,11 +59,13 @@ export const ConceptEmbed = ({ embed, renderContext, lang }: Props) => {
55
59
  if (embed.embedData.type === "inline") {
56
60
  return (
57
61
  <InlineConcept
62
+ previewAlt={previewAlt}
58
63
  linkText={embed.embedData.linkText}
59
64
  copyright={concept.copyright}
60
65
  visualElement={visualElement}
61
66
  lang={lang}
62
67
  title={concept.title.title}
68
+ source={concept.source}
63
69
  >
64
70
  {parsedContent}
65
71
  </InlineConcept>
@@ -68,10 +74,12 @@ export const ConceptEmbed = ({ embed, renderContext, lang }: Props) => {
68
74
 
69
75
  return (
70
76
  <BlockConcept
77
+ previewAlt={previewAlt}
71
78
  copyright={concept.copyright}
72
79
  visualElement={visualElement}
73
80
  lang={lang}
74
81
  title={renderContext === "embed" ? undefined : concept.title.title}
82
+ source={concept.source}
75
83
  >
76
84
  {parsedContent}
77
85
  </BlockConcept>
@@ -80,21 +88,23 @@ export const ConceptEmbed = ({ embed, renderContext, lang }: Props) => {
80
88
 
81
89
  export interface InlineConceptProps extends ConceptProps, BaseProps {
82
90
  linkText?: string;
91
+ source?: string;
83
92
  }
84
93
 
85
94
  export const InlineConcept = forwardRef<HTMLSpanElement, InlineConceptProps>(
86
- ({ linkText, copyright, visualElement, lang, children, title, ...rest }, ref) => (
95
+ ({ linkText, copyright, visualElement, lang, children, title, source, ...rest }, ref) => (
87
96
  <PopoverRoot>
88
- <PopoverTrigger asChild>
89
- <InlineTriggerButton {...rest} ref={ref}>
90
- {linkText}
91
- </InlineTriggerButton>
97
+ {/* @ts-expect-error placing ref and rest on popover trigger somehow removes a bug where the popover target becomes a bit bigger */}
98
+ <PopoverTrigger asChild ref={ref} {...rest}>
99
+ <InlineTriggerButton>{linkText}</InlineTriggerButton>
92
100
  </PopoverTrigger>
93
- <StyledPopoverContent>
94
- <Concept copyright={copyright} visualElement={visualElement} lang={lang} title={title}>
95
- {children}
96
- </Concept>
97
- </StyledPopoverContent>
101
+ <Portal>
102
+ <StyledPopoverContent>
103
+ <Concept copyright={copyright} visualElement={visualElement} lang={lang} title={title} source={source}>
104
+ {children}
105
+ </Concept>
106
+ </StyledPopoverContent>
107
+ </Portal>
98
108
  </PopoverRoot>
99
109
  ),
100
110
  );
@@ -54,6 +54,7 @@ const ConceptListEmbed = ({ embed, lang }: Props) => {
54
54
  copyright={concept.copyright}
55
55
  visualElement={visualElement}
56
56
  lang={lang}
57
+ source={concept.source}
57
58
  />
58
59
  </li>
59
60
  ))}
@@ -17,6 +17,7 @@ import { ImageEmbedData, ImageMetaData } from "@ndla/types-embed";
17
17
  import EmbedErrorPlaceholder from "./EmbedErrorPlaceholder";
18
18
  import { RenderContext } from "./types";
19
19
  import { EmbedByline } from "../LicenseByline";
20
+ import { licenseAttributes } from "../utils/licenseAttributes";
20
21
 
21
22
  interface Props {
22
23
  embed: ImageMetaData;
@@ -247,12 +248,15 @@ const ImageEmbed = ({ embed, previewAlt, lang, renderContext = "article", childr
247
248
  setImageSizes((sizes) => (!sizes ? expandedSizes : undefined));
248
249
  };
249
250
 
251
+ const licenseProps = licenseAttributes(data.copyright.license.license, lang, embedData.url);
252
+
250
253
  // TODO: Check how this works with `children`. Will only be important for ED
251
254
  return (
252
255
  <StyledFigure
253
256
  float={figureProps?.float}
254
257
  size={imageSizes ? "full" : figureProps?.size ?? "medium"}
255
258
  data-embed-type="image"
259
+ {...licenseProps}
256
260
  >
257
261
  {children}
258
262
  <ImageWrapper border={embedData.border === "true"} expandable={!!figureProps?.float}>
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  export { default as ImageEmbed, getCrop, getFocalPoint } from "./ImageEmbed";
10
+ export { InlineTriggerButton } from "./InlineTriggerButton";
10
11
  export { default as AudioEmbed } from "./AudioEmbed";
11
12
  export { default as H5pEmbed } from "./H5pEmbed";
12
13
  export { default as ExternalEmbed } from "./ExternalEmbed";