@ndla/ui 56.0.18-alpha.0 → 56.0.19-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 +4 -0
  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 +19 -10
  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 +61 -0
  26. package/es/index.js +7 -5
  27. package/es/locale/messages-en.js +23 -1
  28. package/es/locale/messages-nb.js +23 -1
  29. package/es/locale/messages-nn.js +71 -49
  30. package/es/locale/messages-se.js +23 -1
  31. package/es/locale/messages-sma.js +23 -1
  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 +1 -0
  41. package/lib/Concept/Concept.js +4 -0
  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 +1 -0
  49. package/lib/Embed/ConceptEmbed.js +19 -10
  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 +64 -2
  66. package/lib/index.d.ts +6 -4
  67. package/lib/index.js +38 -6
  68. package/lib/locale/messages-en.d.ts +22 -0
  69. package/lib/locale/messages-en.js +23 -1
  70. package/lib/locale/messages-nb.d.ts +22 -0
  71. package/lib/locale/messages-nb.js +23 -1
  72. package/lib/locale/messages-nn.d.ts +22 -0
  73. package/lib/locale/messages-nn.js +71 -49
  74. package/lib/locale/messages-se.d.ts +22 -0
  75. package/lib/locale/messages-se.js +23 -1
  76. package/lib/locale/messages-sma.d.ts +22 -0
  77. package/lib/locale/messages-sma.js +23 -1
  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 +6 -2
  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 +17 -10
  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 +78 -0
  105. package/src/index.ts +8 -2
  106. package/src/locale/messages-en.ts +24 -1
  107. package/src/locale/messages-nb.ts +24 -1
  108. package/src/locale/messages-nn.ts +72 -49
  109. package/src/locale/messages-se.ts +24 -1
  110. package/src/locale/messages-sma.ts +24 -1
  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/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.19-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.13-alpha.0",
38
+ "@ndla/licenses": "^8.0.2-alpha.0",
39
+ "@ndla/primitives": "^1.0.17-alpha.0",
40
+ "@ndla/safelink": "^7.0.17-alpha.0",
41
+ "@ndla/styled-system": "^0.0.15",
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.23",
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": "58687255a5be26e49b6a21d830cf0af5be37b9fa"
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,7 @@ export interface ConceptProps extends ComponentPropsWithRef<"figure"> {
20
21
  lang?: string;
21
22
  title?: string;
22
23
  children?: ReactNode;
24
+ source?: string;
23
25
  }
24
26
 
25
27
  const StyledFigure = styled(Figure, {
@@ -43,9 +45,11 @@ const ContentWrapper = styled("div", {
43
45
  // TODO: Figure out if we need to support headerButtons.
44
46
 
45
47
  export const Concept = forwardRef<HTMLElement, ConceptProps>(
46
- ({ copyright, visualElement, lang, children, title, ...rest }, ref) => {
48
+ ({ copyright, visualElement, lang, children, title, source, ...rest }, ref) => {
49
+ const licenseProps = licenseAttributes(copyright?.license?.license, lang, source);
50
+
47
51
  return (
48
- <StyledFigure ref={ref} {...rest}>
52
+ <StyledFigure ref={ref} {...rest} {...licenseProps}>
49
53
  <ContentWrapper lang={lang}>
50
54
  {!!title && (
51
55
  <>
@@ -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";
@@ -29,6 +30,8 @@ interface Props extends BaseProps {
29
30
  const StyledPopoverContent = styled(PopoverContent, {
30
31
  base: {
31
32
  width: "surface.xlarge",
33
+ maxHeight: "50vh",
34
+ overflowY: "auto",
32
35
  },
33
36
  });
34
37
 
@@ -60,6 +63,7 @@ export const ConceptEmbed = ({ embed, renderContext, lang }: Props) => {
60
63
  visualElement={visualElement}
61
64
  lang={lang}
62
65
  title={concept.title.title}
66
+ source={concept.source}
63
67
  >
64
68
  {parsedContent}
65
69
  </InlineConcept>
@@ -72,6 +76,7 @@ export const ConceptEmbed = ({ embed, renderContext, lang }: Props) => {
72
76
  visualElement={visualElement}
73
77
  lang={lang}
74
78
  title={renderContext === "embed" ? undefined : concept.title.title}
79
+ source={concept.source}
75
80
  >
76
81
  {parsedContent}
77
82
  </BlockConcept>
@@ -80,21 +85,23 @@ export const ConceptEmbed = ({ embed, renderContext, lang }: Props) => {
80
85
 
81
86
  export interface InlineConceptProps extends ConceptProps, BaseProps {
82
87
  linkText?: string;
88
+ source?: string;
83
89
  }
84
90
 
85
91
  export const InlineConcept = forwardRef<HTMLSpanElement, InlineConceptProps>(
86
- ({ linkText, copyright, visualElement, lang, children, title, ...rest }, ref) => (
92
+ ({ linkText, copyright, visualElement, lang, children, title, source, ...rest }, ref) => (
87
93
  <PopoverRoot>
88
- <PopoverTrigger asChild>
89
- <InlineTriggerButton {...rest} ref={ref}>
90
- {linkText}
91
- </InlineTriggerButton>
94
+ {/* @ts-expect-error placing ref and rest on popover trigger somehow removes a bug where the popover target becomes a bit bigger */}
95
+ <PopoverTrigger asChild ref={ref} {...rest}>
96
+ <InlineTriggerButton>{linkText}</InlineTriggerButton>
92
97
  </PopoverTrigger>
93
- <StyledPopoverContent>
94
- <Concept copyright={copyright} visualElement={visualElement} lang={lang} title={title}>
95
- {children}
96
- </Concept>
97
- </StyledPopoverContent>
98
+ <Portal>
99
+ <StyledPopoverContent>
100
+ <Concept copyright={copyright} visualElement={visualElement} lang={lang} title={title} source={source}>
101
+ {children}
102
+ </Concept>
103
+ </StyledPopoverContent>
104
+ </Portal>
98
105
  </PopoverRoot>
99
106
  ),
100
107
  );
@@ -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";
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { ComponentPropsWithoutRef } from "react";
10
10
  import { useTranslation } from "react-i18next";
11
+ import { ark } from "@ark-ui/react";
11
12
  import { Heading } from "@ndla/primitives";
12
13
  import { styled } from "@ndla/styled-system/jsx";
13
14
 
@@ -21,19 +22,24 @@ export const FileListWrapper = styled("div", {
21
22
  },
22
23
  });
23
24
 
24
- export const FileListItem = styled("li", {
25
- base: {
26
- background: "surface.infoSubtle",
27
- borderBlockEnd: "1px solid",
28
- borderColor: "stroke.default",
29
- display: "flex",
30
- justifyContent: "space-between",
25
+ export const FileListItem = styled(
26
+ ark.li,
27
+ {
28
+ base: {
29
+ listStyle: "none",
30
+ background: "surface.infoSubtle",
31
+ borderBlockEnd: "1px solid",
32
+ borderColor: "stroke.default",
33
+ display: "flex",
34
+ justifyContent: "space-between",
31
35
 
32
- _hover: {
33
- backgroundColor: "surface.infoSubtle.hover",
36
+ _hover: {
37
+ backgroundColor: "surface.infoSubtle.hover",
38
+ },
34
39
  },
35
40
  },
36
- });
41
+ { baseComponent: true },
42
+ );
37
43
 
38
44
  export const FileListEmbed = ({ children, ...rest }: Props) => {
39
45
  const { t } = useTranslation();
@@ -20,6 +20,12 @@ const StyledIframe = styled("iframe", {
20
20
  },
21
21
  });
22
22
 
23
+ const StyledListElement = styled("li", {
24
+ base: {
25
+ listStyle: "none",
26
+ },
27
+ });
28
+
23
29
  const StyledFigure = styled(Figure, {
24
30
  base: {
25
31
  display: "flex",
@@ -30,13 +36,13 @@ const StyledFigure = styled(Figure, {
30
36
 
31
37
  export const PdfFile = ({ title, url }: Props) => {
32
38
  return (
33
- <li>
39
+ <StyledListElement>
34
40
  <StyledFigure>
35
41
  <Heading asChild consumeCss textStyle="title.medium">
36
42
  <h4>{title}</h4>
37
43
  </Heading>
38
44
  <StyledIframe title={title} height="1050" src={url} />
39
45
  </StyledFigure>
40
- </li>
46
+ </StyledListElement>
41
47
  );
42
48
  };
@@ -6,6 +6,6 @@
6
6
  *
7
7
  */
8
8
 
9
- export { FileListEmbed, FileListItem } from "./FileList";
9
+ export { FileListEmbed, FileListItem, FileListWrapper } from "./FileList";
10
10
  export { File, FileListElement } from "./File";
11
11
  export { PdfFile } from "./PdfFile";
package/src/Grid/Grid.tsx CHANGED
@@ -18,7 +18,6 @@ const GridContainer = styled("div", {
18
18
  gridColumnGap: "medium",
19
19
  width: "100%",
20
20
  backgroundColor: "background.subtle",
21
- maxWidth: "surface.4xlarge",
22
21
  minWidth: "surface.xxsmall",
23
22
  gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
24
23
  tabletDown: {
@@ -233,7 +233,8 @@ export const LicenseContainerContent = ({ children, copyright, type }: LicenseCo
233
233
  <>
234
234
  {children}
235
235
  {` ${t(`embed.type.${type}`)}${captionAuthors.length ? ": " : ""}`}
236
- {captionAuthors.map((author) => author.name).join(", ")}
236
+ {/*eslint-disable-next-line react/no-unknown-property */}
237
+ <span property="cc:attributionName">{captionAuthors.map((author) => author.name).join(", ")}</span>
237
238
  {license ? (
238
239
  <>
239
240
  {" / "}
@@ -293,7 +293,6 @@ const TreeStructureItem = ({ folder, targetResource }: TreeStructureItemProps) =
293
293
 
294
294
  const FolderIcon = folder.status === "shared" ? StyledFolderUserLine : StyledFolderLine;
295
295
 
296
- // TODO: Pressing enter selects the item and closes the popover immediately. Do we actually want this? Old behavior.
297
296
  const onKeyDown = useCallback(
298
297
  (e: KeyboardEvent<HTMLElement>) => {
299
298
  if (e.key === "Enter") {
package/src/i18n/index.ts CHANGED
@@ -13,4 +13,6 @@ export {
13
13
  useTagSelectorTranslations,
14
14
  useTagsInputTranslations,
15
15
  usePaginationTranslations,
16
+ useAudioSearchTranslations,
17
+ useImageSearchTranslations,
16
18
  } from "./useComponentTranslations";
@@ -11,6 +11,10 @@ import type { ComboboxCollectionItem } from "@ark-ui/react";
11
11
  import type { ComboboxRootProps, PaginationRootProps, TagsInputRootProps } from "@ndla/primitives";
12
12
  import { TagSelectorRootProps } from "../TagSelector/TagSelector";
13
13
 
14
+ type DeepPartial<T> = {
15
+ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
16
+ };
17
+
14
18
  export const useTagsInputTranslations = (
15
19
  translations?: Partial<TagsInputRootProps["translations"]>,
16
20
  ): TagsInputRootProps["translations"] => {
@@ -70,3 +74,77 @@ export const usePaginationTranslations = (
70
74
  ...translations,
71
75
  };
72
76
  };
77
+
78
+ // TODO: Deduplicate this and place it somewhere smart. Maybe core?
79
+ interface AudioSearchTranslations {
80
+ searchPlaceholder: string;
81
+ searchButtonTitle: string;
82
+ useAudio: string;
83
+ noResults: string;
84
+ paginationTranslations: PaginationRootProps["translations"];
85
+ }
86
+
87
+ interface MetadataTranslations {
88
+ creatorsLabel: string;
89
+ license: string;
90
+ caption: string;
91
+ altText: string;
92
+ modelRelease: string;
93
+ tags: string;
94
+ }
95
+
96
+ interface ImageSearchTranslations {
97
+ searchPlaceholder: string;
98
+ searchButtonTitle: string;
99
+ useImageTitle: string;
100
+ close: string;
101
+ imageMetadata: MetadataTranslations;
102
+ paginationTranslations: PaginationRootProps["translations"];
103
+ missingTitleFallback?: string;
104
+ checkboxLabel?: string;
105
+ }
106
+
107
+ export const useImageSearchTranslations = (
108
+ translations: DeepPartial<ImageSearchTranslations> = {},
109
+ ): ImageSearchTranslations => {
110
+ const { t } = useTranslation("translation", { keyPrefix: "component.imageSearch" });
111
+ const paginationTranslations = usePaginationTranslations();
112
+
113
+ const { imageMetadata, paginationTranslations: fallbackPaginationTranslations, ...remaining } = translations;
114
+
115
+ return {
116
+ close: t("close"),
117
+ searchPlaceholder: t("searchPlaceholder"),
118
+ searchButtonTitle: t("searchButtonTitle"),
119
+ useImageTitle: t("useImageTitle"),
120
+ imageMetadata: {
121
+ creatorsLabel: t("imageMetadata.creatorsLabel"),
122
+ license: t("imageMetadata.license"),
123
+ caption: t("imageMetadata.caption"),
124
+ altText: t("imageMetadata.altText"),
125
+ modelRelease: t("imageMetadata.modelRelease"),
126
+ tags: t("imageMetadata.tags"),
127
+ ...imageMetadata,
128
+ },
129
+ paginationTranslations: { ...paginationTranslations, ...fallbackPaginationTranslations },
130
+ ...remaining,
131
+ };
132
+ };
133
+
134
+ export const useAudioSearchTranslations = (
135
+ translations: DeepPartial<AudioSearchTranslations> = {},
136
+ ): AudioSearchTranslations => {
137
+ const { t } = useTranslation("translation", { keyPrefix: "component.audioSearch" });
138
+ const paginationTranslations = usePaginationTranslations();
139
+
140
+ const { paginationTranslations: fallbackPaginationTranslations, ...remaining } = translations;
141
+
142
+ return {
143
+ searchPlaceholder: t("searchPlaceholder"),
144
+ searchButtonTitle: t("searchButtonTitle"),
145
+ useAudio: t("useAudio"),
146
+ noResults: t("noResults"),
147
+ paginationTranslations: { ...paginationTranslations, ...fallbackPaginationTranslations },
148
+ ...remaining,
149
+ };
150
+ };