@ndla/ui 56.0.85-alpha.0 → 56.0.87-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.
@@ -179,6 +179,7 @@
179
179
  "opacity]___[value:0",
180
180
  "cursor]___[value:pointer",
181
181
  "opacity]___[value:1]___[cond:&:focus, &:focus-visible, &:active",
182
+ "clear]___[value:both",
182
183
  "marginBlockStart]___[value:3xsmall",
183
184
  "border]___[value:0",
184
185
  "height]___[value:auto",
@@ -215,9 +216,18 @@
215
216
  "fill]___[value:stroke.default]___[cond:& svg",
216
217
  "marginBlockStart]___[value:3xsmall]___[cond:& > *:not(:first-child)",
217
218
  "clear]___[value:both]___[cond:& + [data-embed-wrapper]",
218
- "clear]___[value:both",
219
219
  "height]___[value:auto]___[cond:& iframe",
220
220
  "width]___[value:100%]___[cond:& iframe",
221
+ "color]___[value:text.link",
222
+ "textDecoration]___[value:unset]___[cond:_hover",
223
+ "gap]___[value:small",
224
+ "backgroundColor]___[value:surface.brand.2.subtle",
225
+ "borderRadius]___[value:xxsmall",
226
+ "borderColor]___[value:stroke.info",
227
+ "textOverflow]___[value:ellipsis",
228
+ "whiteSpace]___[value:nowrap",
229
+ "inset]___[value:0]___[cond:_before",
230
+ "zIndex]___[value:0]___[cond:_before",
221
231
  "textStyle]___[value:label.xsmall]___[cond:& a",
222
232
  "marginInlineStart]___[value:1]___[cond:& a",
223
233
  "borderRadius]___[value:0]___[cond:& img",
@@ -242,8 +252,6 @@
242
252
  "transitionDuration]___[value:normal]___[cond:& svg",
243
253
  "transitionTimingFunction]___[value:ease-out]___[cond:& svg",
244
254
  "paddingInlineStart]___[value:4xsmall",
245
- "color]___[value:text.link",
246
- "whiteSpace]___[value:nowrap",
247
255
  "textDecoration]___[value:none]___[cond:_focusWithin",
248
256
  "color]___[value:text.strong]___[cond:& a",
249
257
  "marginTop]___[value:0]___[cond:& h1",
@@ -291,12 +299,9 @@
291
299
  "paddingInlineEnd]___[value:medium",
292
300
  "paddingInlineStart]___[value:small",
293
301
  "flexDirection]___[value:row",
294
- "inset]___[value:0]___[cond:_before",
295
- "zIndex]___[value:0]___[cond:_before",
296
302
  "background]___[value:surface.infoSubtle",
297
303
  "borderBlockEnd]___[value:1px solid",
298
304
  "backgroundColor]___[value:surface.infoSubtle.hover]___[cond:_hover",
299
- "gap]___[value:small",
300
305
  "paddingInline]___[value:0",
301
306
  "background]___[value:surface.brand.1.subtle]___[cond:_first",
302
307
  "borderColor]___[value:stroke.default]___[cond:_first",
package/dist/styles.css CHANGED
@@ -393,6 +393,10 @@
393
393
  cursor: pointer;
394
394
  }
395
395
 
396
+ .clear_both {
397
+ clear: both;
398
+ }
399
+
396
400
  .mbs_3xsmall {
397
401
  margin-block-start: var(--spacing-3xsmall);
398
402
  }
@@ -437,8 +441,24 @@
437
441
  background: var(--colors-background-subtle);
438
442
  }
439
443
 
440
- .clear_both {
441
- clear: both;
444
+ .c_text\.link {
445
+ color: var(--colors-text-link);
446
+ }
447
+
448
+ .gap_small {
449
+ gap: var(--spacing-small);
450
+ }
451
+
452
+ .bdr_xxsmall {
453
+ border-radius: xxsmall;
454
+ }
455
+
456
+ .tov_ellipsis {
457
+ text-overflow: ellipsis;
458
+ }
459
+
460
+ .white-space_nowrap {
461
+ white-space: nowrap;
442
462
  }
443
463
 
444
464
  .z_docked {
@@ -473,14 +493,6 @@
473
493
  padding-inline-start: var(--spacing-4xsmall);
474
494
  }
475
495
 
476
- .c_text\.link {
477
- color: var(--colors-text-link);
478
- }
479
-
480
- .white-space_nowrap {
481
- white-space: nowrap;
482
- }
483
-
484
496
  .my_xxlarge {
485
497
  margin-block: var(--spacing-xxlarge);
486
498
  }
@@ -509,10 +521,6 @@
509
521
  border-block-end: 1px solid;
510
522
  }
511
523
 
512
- .gap_small {
513
- gap: var(--spacing-small);
514
- }
515
-
516
524
  .px_0 {
517
525
  padding-inline: 0;
518
526
  }
@@ -669,6 +677,14 @@
669
677
  border-color: var(--colors-stroke-hover);
670
678
  }
671
679
 
680
+ .bg-c_surface\.brand\.2\.subtle {
681
+ background-color: var(--colors-surface-brand-2-subtle);
682
+ }
683
+
684
+ .bd-c_stroke\.info {
685
+ border-color: var(--colors-stroke-info);
686
+ }
687
+
672
688
  .top_xsmall {
673
689
  top: var(--spacing-xsmall);
674
690
  }
@@ -895,6 +911,14 @@
895
911
  width: 100%;
896
912
  }
897
913
 
914
+ .before\:inset_0::before {
915
+ inset: 0;
916
+ }
917
+
918
+ .before\:z_0::before {
919
+ z-index: 0;
920
+ }
921
+
898
922
  .\[\&_a\]\:ms_1 a {
899
923
  margin-inline-start: var(--spacing-1);
900
924
  }
@@ -963,14 +987,6 @@
963
987
  padding-block-end: var(--spacing-xsmall);
964
988
  }
965
989
 
966
- .before\:inset_0::before {
967
- inset: 0;
968
- }
969
-
970
- .before\:z_0::before {
971
- z-index: 0;
972
- }
973
-
974
990
  .first\:bg_surface\.brand\.1\.subtle:first-child {
975
991
  background: var(--colors-surface-brand-1-subtle);
976
992
  }
@@ -1126,6 +1142,10 @@
1126
1142
  background: var(--colors-surface-action-subtle-hover);
1127
1143
  }
1128
1144
 
1145
+ .hover\:td_unset:is(:hover, [data-hover]) {
1146
+ text-decoration: unset;
1147
+ }
1148
+
1129
1149
  .hover\:bd-c_text\.link:is(:hover, [data-hover]) {
1130
1150
  border-color: var(--colors-text-link);
1131
1151
  }
@@ -7,11 +7,17 @@
7
7
  */
8
8
 
9
9
  import { Figure } from "@ndla/primitives";
10
+ import { styled } from "@ndla/styled-system/jsx";
10
11
  import EmbedErrorPlaceholder from "./EmbedErrorPlaceholder";
11
12
  import AudioPlayer from "../AudioPlayer";
12
13
  import { EmbedByline } from "../LicenseByline";
13
14
  import { licenseAttributes } from "../utils/licenseAttributes";
14
15
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
+ const StyledFigure = styled(Figure, {
17
+ base: {
18
+ clear: "both"
19
+ }
20
+ });
15
21
  export const getFirstNonEmptyLicenseCredits = authors => Object.values(authors).find(i => i.length > 0) ?? [];
16
22
  const AudioEmbed = _ref => {
17
23
  let {
@@ -45,7 +51,7 @@ const AudioEmbed = _ref => {
45
51
  alt: coverPhoto.altText
46
52
  };
47
53
  const licenseProps = licenseAttributes(data.copyright.license.license, lang, embedData.url);
48
- return /*#__PURE__*/_jsxs(Figure, {
54
+ return /*#__PURE__*/_jsxs(StyledFigure, {
49
55
  lang: lang,
50
56
  "data-embed-type": type,
51
57
  ...licenseProps,
@@ -8,11 +8,14 @@
8
8
 
9
9
  import { useEffect, useRef } from "react";
10
10
  import { useTranslation } from "react-i18next";
11
- import { Figure } from "@ndla/primitives";
11
+ import { ArrowRightShortLine } from "@ndla/icons";
12
+ import { Figure, Text } from "@ndla/primitives";
13
+ import { SafeLink } from "@ndla/safelink";
12
14
  import { styled } from "@ndla/styled-system/jsx";
15
+ import { linkOverlay } from "@ndla/styled-system/patterns";
13
16
  import EmbedErrorPlaceholder from "./EmbedErrorPlaceholder";
14
17
  import { ResourceBox } from "../ResourceBox";
15
- import { jsx as _jsx } from "react/jsx-runtime";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
19
  const StyledFigure = styled(Figure, {
17
20
  base: {
18
21
  "& iframe": {
@@ -21,6 +24,55 @@ const StyledFigure = styled(Figure, {
21
24
  }
22
25
  }
23
26
  });
27
+
28
+ // TODO: Move this to own component in UI? Use variant of existing?
29
+ const StyledSafeLink = styled(SafeLink, {
30
+ base: {
31
+ textDecoration: "underline",
32
+ textStyle: "label.large",
33
+ color: "text.link",
34
+ fontWeight: "bold",
35
+ _hover: {
36
+ textDecoration: "unset"
37
+ }
38
+ }
39
+ });
40
+ const LinkWrapper = styled("div", {
41
+ base: {
42
+ display: "flex",
43
+ alignItems: "center",
44
+ justifyContent: "space-between",
45
+ gap: "xsmall"
46
+ }
47
+ });
48
+ const TextWrapper = styled("div", {
49
+ base: {
50
+ display: "flex",
51
+ flexDirection: "column",
52
+ alignItems: "flex-start",
53
+ gap: "xxsmall"
54
+ }
55
+ });
56
+ const Wrapper = styled("div", {
57
+ base: {
58
+ display: "flex",
59
+ width: "100%",
60
+ gap: "small",
61
+ flexDirection: "column",
62
+ backgroundColor: "surface.brand.2.subtle",
63
+ borderRadius: "xxsmall",
64
+ padding: "medium",
65
+ border: "1px solid",
66
+ borderColor: "stroke.info"
67
+ }
68
+ });
69
+ const UrlText = styled(Text, {
70
+ base: {
71
+ textOverflow: "ellipsis",
72
+ whiteSpace: "nowrap",
73
+ overflow: "hidden"
74
+ }
75
+ });
24
76
  const ExternalEmbed = _ref => {
25
77
  let {
26
78
  embed
@@ -63,11 +115,35 @@ const ExternalEmbed = _ref => {
63
115
  })
64
116
  });
65
117
  }
118
+ if (embedData.type === "link") {
119
+ return /*#__PURE__*/_jsx(Figure, {
120
+ "data-embed-type": "external",
121
+ children: /*#__PURE__*/_jsxs(Wrapper, {
122
+ children: [/*#__PURE__*/_jsxs(LinkWrapper, {
123
+ children: [/*#__PURE__*/_jsxs(TextWrapper, {
124
+ children: [/*#__PURE__*/_jsx(StyledSafeLink, {
125
+ to: embedData.url,
126
+ unstyled: true,
127
+ css: linkOverlay.raw(),
128
+ children: embedData.title
129
+ }), /*#__PURE__*/_jsx(Text, {
130
+ textStyle: "label.medium",
131
+ children: embedData.caption
132
+ })]
133
+ }), /*#__PURE__*/_jsx(ArrowRightShortLine, {})]
134
+ }), /*#__PURE__*/_jsx(UrlText, {
135
+ textStyle: "label.medium",
136
+ color: "text.subtle",
137
+ children: embedData.url
138
+ })]
139
+ })
140
+ });
141
+ }
66
142
  return /*#__PURE__*/_jsx(StyledFigure, {
67
143
  "data-embed-type": "external",
68
144
  ref: figRef,
69
145
  dangerouslySetInnerHTML: {
70
- __html: data.oembed.html ?? ""
146
+ __html: data?.oembed?.html ?? ""
71
147
  }
72
148
  });
73
149
  };
@@ -19,6 +19,11 @@ const StyledIframe = styled("iframe", {
19
19
  border: 0
20
20
  }
21
21
  });
22
+ const StyledFigure = styled(Figure, {
23
+ base: {
24
+ clear: "both"
25
+ }
26
+ });
22
27
  const IframeEmbed = _ref => {
23
28
  let {
24
29
  embed
@@ -52,7 +57,7 @@ const IframeEmbed = _ref => {
52
57
  src: iframeImage?.image.imageUrl,
53
58
  alt: alt ?? ""
54
59
  };
55
- return /*#__PURE__*/_jsx(Figure, {
60
+ return /*#__PURE__*/_jsx(StyledFigure, {
56
61
  "data-embed-type": "iframe",
57
62
  children: /*#__PURE__*/_jsx(ResourceBox, {
58
63
  image: image,
@@ -72,7 +77,7 @@ const IframeEmbed = _ref => {
72
77
  const strippedWidth = typeof width === "number" ? width : width?.replace(/\s*px/, "");
73
78
  const strippedHeight = typeof height === "number" ? height : height?.replace(/\s*px/, "");
74
79
  const urlOrTitle = title || url;
75
- return /*#__PURE__*/_jsx(Figure, {
80
+ return /*#__PURE__*/_jsx(StyledFigure, {
76
81
  "data-embed-type": "iframe",
77
82
  children: /*#__PURE__*/_jsx(StyledIframe, {
78
83
  ref: iframeRef,
@@ -37,6 +37,11 @@ const StyledSafeLink = styled(SafeLink, {
37
37
  }
38
38
  }
39
39
  });
40
+ const Wrapper = styled("div", {
41
+ base: {
42
+ clear: "both"
43
+ }
44
+ });
40
45
  const UuDisclaimerEmbed = _ref => {
41
46
  let {
42
47
  embed,
@@ -55,7 +60,7 @@ const UuDisclaimerEmbed = _ref => {
55
60
  rel: "noopener noreferrer",
56
61
  children: data.disclaimerLink.text
57
62
  }) : null;
58
- return /*#__PURE__*/_jsxs("div", {
63
+ return /*#__PURE__*/_jsxs(Wrapper, {
59
64
  role: "region",
60
65
  "data-embed-type": "uu-disclaimer",
61
66
  children: [/*#__PURE__*/_jsxs(StyledMessageBox, {
@@ -88,7 +88,8 @@ const StyledSection = styled("section", {
88
88
  display: "flex",
89
89
  flexDirection: "column",
90
90
  alignItems: "center",
91
- gap: "medium"
91
+ gap: "medium",
92
+ clear: "both"
92
93
  }
93
94
  });
94
95
  const StyledButton = styled(Button, {
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.getFirstNonEmptyLicenseCredits = exports.default = void 0;
7
7
  var _primitives = require("@ndla/primitives");
8
+ var _jsx2 = require("@ndla/styled-system/jsx");
8
9
  var _EmbedErrorPlaceholder = _interopRequireDefault(require("./EmbedErrorPlaceholder"));
9
10
  var _AudioPlayer = _interopRequireDefault(require("../AudioPlayer"));
10
11
  var _LicenseByline = require("../LicenseByline");
@@ -19,6 +20,11 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
19
20
  *
20
21
  */
21
22
 
23
+ const StyledFigure = (0, _jsx2.styled)(_primitives.Figure, {
24
+ base: {
25
+ clear: "both"
26
+ }
27
+ });
22
28
  const getFirstNonEmptyLicenseCredits = authors => Object.values(authors).find(i => i.length > 0) ?? [];
23
29
  exports.getFirstNonEmptyLicenseCredits = getFirstNonEmptyLicenseCredits;
24
30
  const AudioEmbed = _ref => {
@@ -53,7 +59,7 @@ const AudioEmbed = _ref => {
53
59
  alt: coverPhoto.altText
54
60
  };
55
61
  const licenseProps = (0, _licenseAttributes.licenseAttributes)(data.copyright.license.license, lang, embedData.url);
56
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Figure, {
62
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(StyledFigure, {
57
63
  lang: lang,
58
64
  "data-embed-type": type,
59
65
  ...licenseProps,
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _react = require("react");
8
8
  var _reactI18next = require("react-i18next");
9
+ var _icons = require("@ndla/icons");
9
10
  var _primitives = require("@ndla/primitives");
11
+ var _safelink = require("@ndla/safelink");
10
12
  var _jsx2 = require("@ndla/styled-system/jsx");
13
+ var _patterns = require("@ndla/styled-system/patterns");
11
14
  var _EmbedErrorPlaceholder = _interopRequireDefault(require("./EmbedErrorPlaceholder"));
12
15
  var _ResourceBox = require("../ResourceBox");
13
16
  var _jsxRuntime = require("react/jsx-runtime");
@@ -28,6 +31,55 @@ const StyledFigure = (0, _jsx2.styled)(_primitives.Figure, {
28
31
  }
29
32
  }
30
33
  });
34
+
35
+ // TODO: Move this to own component in UI? Use variant of existing?
36
+ const StyledSafeLink = (0, _jsx2.styled)(_safelink.SafeLink, {
37
+ base: {
38
+ textDecoration: "underline",
39
+ textStyle: "label.large",
40
+ color: "text.link",
41
+ fontWeight: "bold",
42
+ _hover: {
43
+ textDecoration: "unset"
44
+ }
45
+ }
46
+ });
47
+ const LinkWrapper = (0, _jsx2.styled)("div", {
48
+ base: {
49
+ display: "flex",
50
+ alignItems: "center",
51
+ justifyContent: "space-between",
52
+ gap: "xsmall"
53
+ }
54
+ });
55
+ const TextWrapper = (0, _jsx2.styled)("div", {
56
+ base: {
57
+ display: "flex",
58
+ flexDirection: "column",
59
+ alignItems: "flex-start",
60
+ gap: "xxsmall"
61
+ }
62
+ });
63
+ const Wrapper = (0, _jsx2.styled)("div", {
64
+ base: {
65
+ display: "flex",
66
+ width: "100%",
67
+ gap: "small",
68
+ flexDirection: "column",
69
+ backgroundColor: "surface.brand.2.subtle",
70
+ borderRadius: "xxsmall",
71
+ padding: "medium",
72
+ border: "1px solid",
73
+ borderColor: "stroke.info"
74
+ }
75
+ });
76
+ const UrlText = (0, _jsx2.styled)(_primitives.Text, {
77
+ base: {
78
+ textOverflow: "ellipsis",
79
+ whiteSpace: "nowrap",
80
+ overflow: "hidden"
81
+ }
82
+ });
31
83
  const ExternalEmbed = _ref => {
32
84
  let {
33
85
  embed
@@ -70,11 +122,35 @@ const ExternalEmbed = _ref => {
70
122
  })
71
123
  });
72
124
  }
125
+ if (embedData.type === "link") {
126
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Figure, {
127
+ "data-embed-type": "external",
128
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(Wrapper, {
129
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(LinkWrapper, {
130
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(TextWrapper, {
131
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(StyledSafeLink, {
132
+ to: embedData.url,
133
+ unstyled: true,
134
+ css: _patterns.linkOverlay.raw(),
135
+ children: embedData.title
136
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Text, {
137
+ textStyle: "label.medium",
138
+ children: embedData.caption
139
+ })]
140
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.ArrowRightShortLine, {})]
141
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(UrlText, {
142
+ textStyle: "label.medium",
143
+ color: "text.subtle",
144
+ children: embedData.url
145
+ })]
146
+ })
147
+ });
148
+ }
73
149
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledFigure, {
74
150
  "data-embed-type": "external",
75
151
  ref: figRef,
76
152
  dangerouslySetInnerHTML: {
77
- __html: data.oembed.html ?? ""
153
+ __html: data?.oembed?.html ?? ""
78
154
  }
79
155
  });
80
156
  };
@@ -26,6 +26,11 @@ const StyledIframe = (0, _jsx2.styled)("iframe", {
26
26
  border: 0
27
27
  }
28
28
  });
29
+ const StyledFigure = (0, _jsx2.styled)(_primitives.Figure, {
30
+ base: {
31
+ clear: "both"
32
+ }
33
+ });
29
34
  const IframeEmbed = _ref => {
30
35
  let {
31
36
  embed
@@ -59,7 +64,7 @@ const IframeEmbed = _ref => {
59
64
  src: iframeImage?.image.imageUrl,
60
65
  alt: alt ?? ""
61
66
  };
62
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Figure, {
67
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledFigure, {
63
68
  "data-embed-type": "iframe",
64
69
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ResourceBox.ResourceBox, {
65
70
  image: image,
@@ -79,7 +84,7 @@ const IframeEmbed = _ref => {
79
84
  const strippedWidth = typeof width === "number" ? width : width?.replace(/\s*px/, "");
80
85
  const strippedHeight = typeof height === "number" ? height : height?.replace(/\s*px/, "");
81
86
  const urlOrTitle = title || url;
82
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Figure, {
87
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledFigure, {
83
88
  "data-embed-type": "iframe",
84
89
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledIframe, {
85
90
  ref: iframeRef,
@@ -43,6 +43,11 @@ const StyledSafeLink = (0, _jsx2.styled)(_safelink.SafeLink, {
43
43
  }
44
44
  }
45
45
  });
46
+ const Wrapper = (0, _jsx2.styled)("div", {
47
+ base: {
48
+ clear: "both"
49
+ }
50
+ });
46
51
  const UuDisclaimerEmbed = _ref => {
47
52
  let {
48
53
  embed,
@@ -61,7 +66,7 @@ const UuDisclaimerEmbed = _ref => {
61
66
  rel: "noopener noreferrer",
62
67
  children: data.disclaimerLink.text
63
68
  }) : null;
64
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
69
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(Wrapper, {
65
70
  role: "region",
66
71
  "data-embed-type": "uu-disclaimer",
67
72
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(StyledMessageBox, {
@@ -95,7 +95,8 @@ const StyledSection = (0, _jsx2.styled)("section", {
95
95
  display: "flex",
96
96
  flexDirection: "column",
97
97
  alignItems: "center",
98
- gap: "medium"
98
+ gap: "medium",
99
+ clear: "both"
99
100
  }
100
101
  });
101
102
  const StyledButton = (0, _jsx2.styled)(_primitives.Button, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
- "version": "56.0.85-alpha.0",
3
+ "version": "56.0.87-alpha.0",
4
4
  "description": "UI component library for NDLA",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -35,8 +35,8 @@
35
35
  "@ndla/core": "^5.0.3",
36
36
  "@ndla/icons": "^8.0.49-alpha.0",
37
37
  "@ndla/licenses": "^8.0.6-alpha.0",
38
- "@ndla/primitives": "^1.0.68-alpha.0",
39
- "@ndla/safelink": "^7.0.69-alpha.0",
38
+ "@ndla/primitives": "^1.0.69-alpha.0",
39
+ "@ndla/safelink": "^7.0.70-alpha.0",
40
40
  "@ndla/styled-system": "^0.0.30",
41
41
  "@ndla/util": "^5.0.5-alpha.0",
42
42
  "html-react-parser": "^5.1.19",
@@ -52,11 +52,11 @@
52
52
  "devDependencies": {
53
53
  "@ndla/preset-panda": "^0.0.49",
54
54
  "@ndla/types-backend": "^1.0.1",
55
- "@ndla/types-embed": "^5.0.6-alpha.0",
55
+ "@ndla/types-embed": "^5.0.8-alpha.0",
56
56
  "@pandacss/dev": "^0.48.0"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public"
60
60
  },
61
- "gitHead": "7960790a61f90467f2c0b0919201967eac276662"
61
+ "gitHead": "34ad8530b56efe3dc38052ccffaa6cd03d940d01"
62
62
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { Figure } from "@ndla/primitives";
10
+ import { styled } from "@ndla/styled-system/jsx";
10
11
  import type { AudioMetaData } from "@ndla/types-embed";
11
12
  import EmbedErrorPlaceholder from "./EmbedErrorPlaceholder";
12
13
  import type { Author } from "./ImageEmbed";
@@ -14,6 +15,12 @@ import AudioPlayer from "../AudioPlayer";
14
15
  import { EmbedByline } from "../LicenseByline";
15
16
  import { licenseAttributes } from "../utils/licenseAttributes";
16
17
 
18
+ const StyledFigure = styled(Figure, {
19
+ base: {
20
+ clear: "both",
21
+ },
22
+ });
23
+
17
24
  interface Props {
18
25
  embed: AudioMetaData;
19
26
  lang?: string;
@@ -46,7 +53,7 @@ const AudioEmbed = ({ embed, lang }: Props) => {
46
53
  const licenseProps = licenseAttributes(data.copyright.license.license, lang, embedData.url);
47
54
 
48
55
  return (
49
- <Figure lang={lang} data-embed-type={type} {...licenseProps}>
56
+ <StyledFigure lang={lang} data-embed-type={type} {...licenseProps}>
50
57
  <AudioPlayer
51
58
  description={data.podcastMeta?.introduction ?? ""}
52
59
  img={img}
@@ -64,7 +71,7 @@ const AudioEmbed = ({ embed, lang }: Props) => {
64
71
  type={data.audioType === "standard" ? "audio" : "podcast"}
65
72
  copyright={embed.data.copyright}
66
73
  />
67
- </Figure>
74
+ </StyledFigure>
68
75
  );
69
76
  };
70
77
 
@@ -105,7 +105,7 @@ const visuallyInterpretedEmbedMetaData: BrightcoveMetaData = {
105
105
  src: "https://cf-images.eu-west-1.prod.boltdns.net/v1/static/4806596774001/a09bcb7b-ffe1-4c3a-98ea-bf997340a8cd/23f01e33-413e-418c-a4de-2c87c8b05c26/1280x720/match/image.jpg",
106
106
  },
107
107
  },
108
- link: { text: "6242635463001" },
108
+ link: { text: "6242635463001", url: "" },
109
109
  long_description: null,
110
110
  name: "Frivillighet i helse og omsorgstjenesten (synstolket)",
111
111
  published_at: "2021-03-25T11:40:38.476Z",
@@ -174,3 +174,14 @@ export const Fullscreen: StoryObj<typeof ExternalEmbed> = {
174
174
  },
175
175
  },
176
176
  };
177
+
178
+ export const Link: StoryObj<typeof ExternalEmbed> = {
179
+ args: {
180
+ embed: {
181
+ resource: "external",
182
+ status: "success",
183
+ embedData: { ...embedDataFullscreen, type: "link" },
184
+ data: opensInNewMetaData,
185
+ },
186
+ },
187
+ };
@@ -8,8 +8,11 @@
8
8
 
9
9
  import { useEffect, useRef } from "react";
10
10
  import { useTranslation } from "react-i18next";
11
- import { Figure } from "@ndla/primitives";
11
+ import { ArrowRightShortLine } from "@ndla/icons";
12
+ import { Figure, Text } from "@ndla/primitives";
13
+ import { SafeLink } from "@ndla/safelink";
12
14
  import { styled } from "@ndla/styled-system/jsx";
15
+ import { linkOverlay } from "@ndla/styled-system/patterns";
13
16
  import type { OembedMetaData } from "@ndla/types-embed";
14
17
  import EmbedErrorPlaceholder from "./EmbedErrorPlaceholder";
15
18
  import { ResourceBox } from "../ResourceBox";
@@ -27,6 +30,59 @@ const StyledFigure = styled(Figure, {
27
30
  },
28
31
  });
29
32
 
33
+ // TODO: Move this to own component in UI? Use variant of existing?
34
+ const StyledSafeLink = styled(SafeLink, {
35
+ base: {
36
+ textDecoration: "underline",
37
+ textStyle: "label.large",
38
+ color: "text.link",
39
+ fontWeight: "bold",
40
+ _hover: {
41
+ textDecoration: "unset",
42
+ },
43
+ },
44
+ });
45
+
46
+ const LinkWrapper = styled("div", {
47
+ base: {
48
+ display: "flex",
49
+ alignItems: "center",
50
+ justifyContent: "space-between",
51
+ gap: "xsmall",
52
+ },
53
+ });
54
+
55
+ const TextWrapper = styled("div", {
56
+ base: {
57
+ display: "flex",
58
+ flexDirection: "column",
59
+ alignItems: "flex-start",
60
+ gap: "xxsmall",
61
+ },
62
+ });
63
+
64
+ const Wrapper = styled("div", {
65
+ base: {
66
+ display: "flex",
67
+ width: "100%",
68
+ gap: "small",
69
+ flexDirection: "column",
70
+ backgroundColor: "surface.brand.2.subtle",
71
+ borderRadius: "xxsmall",
72
+ padding: "medium",
73
+ border: "1px solid",
74
+ borderColor: "stroke.info",
75
+ },
76
+ });
77
+
78
+ const UrlText = styled(Text, {
79
+ base: {
80
+ textOverflow: "ellipsis",
81
+ whiteSpace: "nowrap",
82
+ overflow: "hidden",
83
+ },
84
+ });
85
+
30
86
  const ExternalEmbed = ({ embed }: Props) => {
31
87
  const { t } = useTranslation();
32
88
  const figRef = useRef<HTMLElement>(null);
@@ -65,13 +121,33 @@ const ExternalEmbed = ({ embed }: Props) => {
65
121
  );
66
122
  }
67
123
 
124
+ if (embedData.type === "link") {
125
+ return (
126
+ <Figure data-embed-type="external">
127
+ <Wrapper>
128
+ <LinkWrapper>
129
+ <TextWrapper>
130
+ <StyledSafeLink to={embedData.url} unstyled css={linkOverlay.raw()}>
131
+ {embedData.title}
132
+ </StyledSafeLink>
133
+ <Text textStyle="label.medium">{embedData.caption}</Text>
134
+ </TextWrapper>
135
+ <ArrowRightShortLine />
136
+ </LinkWrapper>
137
+ <UrlText textStyle="label.medium" color="text.subtle">
138
+ {embedData.url}
139
+ </UrlText>
140
+ </Wrapper>
141
+ </Figure>
142
+ );
143
+ }
144
+
68
145
  return (
69
146
  <StyledFigure
70
147
  data-embed-type="external"
71
148
  ref={figRef}
72
- dangerouslySetInnerHTML={{ __html: data.oembed.html ?? "" }}
149
+ dangerouslySetInnerHTML={{ __html: data?.oembed?.html ?? "" }}
73
150
  />
74
151
  );
75
152
  };
76
-
77
153
  export default ExternalEmbed;
@@ -25,6 +25,12 @@ const StyledIframe = styled("iframe", {
25
25
  },
26
26
  });
27
27
 
28
+ const StyledFigure = styled(Figure, {
29
+ base: {
30
+ clear: "both",
31
+ },
32
+ });
33
+
28
34
  const IframeEmbed = ({ embed }: Props) => {
29
35
  const { t } = useTranslation();
30
36
  const iframeRef = useRef<HTMLIFrameElement>(null);
@@ -50,7 +56,7 @@ const IframeEmbed = ({ embed }: Props) => {
50
56
  const alt = embedData.alt !== undefined ? embedData.alt : iframeImage?.alttext.alttext;
51
57
  const image = { src: iframeImage?.image.imageUrl, alt: alt ?? "" };
52
58
  return (
53
- <Figure data-embed-type="iframe">
59
+ <StyledFigure data-embed-type="iframe">
54
60
  <ResourceBox
55
61
  image={image}
56
62
  title={embedData.title ?? ""}
@@ -58,7 +64,7 @@ const IframeEmbed = ({ embed }: Props) => {
58
64
  caption={embedData.caption ?? ""}
59
65
  buttonText={t("license.other.itemImage.ariaLabel")}
60
66
  />
61
- </Figure>
67
+ </StyledFigure>
62
68
  );
63
69
  }
64
70
 
@@ -69,7 +75,7 @@ const IframeEmbed = ({ embed }: Props) => {
69
75
  const urlOrTitle = title || url;
70
76
 
71
77
  return (
72
- <Figure data-embed-type="iframe">
78
+ <StyledFigure data-embed-type="iframe">
73
79
  <StyledIframe
74
80
  ref={iframeRef}
75
81
  title={urlOrTitle}
@@ -80,7 +86,7 @@ const IframeEmbed = ({ embed }: Props) => {
80
86
  allow="fullscreen; encrypted-media"
81
87
  loading="lazy"
82
88
  />
83
- </Figure>
89
+ </StyledFigure>
84
90
  );
85
91
  };
86
92
 
@@ -45,6 +45,12 @@ const StyledSafeLink = styled(SafeLink, {
45
45
  },
46
46
  });
47
47
 
48
+ const Wrapper = styled("div", {
49
+ base: {
50
+ clear: "both",
51
+ },
52
+ });
53
+
48
54
  const UuDisclaimerEmbed = ({ embed, children }: Props) => {
49
55
  if (embed.status === "error") {
50
56
  return null;
@@ -59,7 +65,7 @@ const UuDisclaimerEmbed = ({ embed, children }: Props) => {
59
65
  ) : null;
60
66
 
61
67
  return (
62
- <div role="region" data-embed-type="uu-disclaimer">
68
+ <Wrapper role="region" data-embed-type="uu-disclaimer">
63
69
  <StyledMessageBox variant="warning" contentEditable={false}>
64
70
  <InformationLine />
65
71
  <Disclaimer>
@@ -68,7 +74,7 @@ const UuDisclaimerEmbed = ({ embed, children }: Props) => {
68
74
  </Disclaimer>
69
75
  </StyledMessageBox>
70
76
  <div data-uu-content="">{children}</div>
71
- </div>
77
+ </Wrapper>
72
78
  );
73
79
  };
74
80
 
@@ -98,6 +98,7 @@ const StyledSection = styled("section", {
98
98
  flexDirection: "column",
99
99
  alignItems: "center",
100
100
  gap: "medium",
101
+ clear: "both",
101
102
  },
102
103
  });
103
104