@ndla/ui 44.0.18 → 44.0.20
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.
- package/es/Article/ArticleByline.js +18 -12
- package/es/BlogPost/BlogPost.js +9 -6
- package/es/CampaignBlock/CampaignBlock.js +10 -7
- package/es/ContactBlock/ContactBlock.js +30 -33
- package/es/Embed/AudioEmbed.js +1 -6
- package/es/Embed/BrightcoveEmbed.js +3 -6
- package/es/Embed/ImageEmbed.js +2 -5
- package/es/FileList/File.js +6 -9
- package/es/FileList/FileList.js +4 -5
- package/es/FileList/FileV2.js +0 -2
- package/es/Grid/Grid.js +2 -2
- package/es/KeyFigure/KeyFigure.js +12 -10
- package/es/LinkBlock/LinkBlock.js +10 -7
- package/es/Table/Table.js +26 -14
- package/es/utils/relativeUrl.js +18 -0
- package/lib/Article/ArticleByline.js +18 -12
- package/lib/BlogPost/BlogPost.d.ts +2 -1
- package/lib/BlogPost/BlogPost.js +9 -6
- package/lib/CampaignBlock/CampaignBlock.d.ts +2 -1
- package/lib/CampaignBlock/CampaignBlock.js +10 -7
- package/lib/ContactBlock/ContactBlock.js +30 -33
- package/lib/Embed/AudioEmbed.js +1 -6
- package/lib/Embed/BrightcoveEmbed.js +3 -6
- package/lib/Embed/ImageEmbed.js +2 -5
- package/lib/FileList/File.d.ts +1 -2
- package/lib/FileList/File.js +6 -9
- package/lib/FileList/FileList.js +4 -5
- package/lib/FileList/FileV2.d.ts +1 -2
- package/lib/FileList/FileV2.js +0 -2
- package/lib/Grid/Grid.js +2 -2
- package/lib/KeyFigure/KeyFigure.js +12 -10
- package/lib/LinkBlock/LinkBlock.d.ts +4 -1
- package/lib/LinkBlock/LinkBlock.js +10 -7
- package/lib/Table/Table.js +26 -14
- package/lib/utils/relativeUrl.d.ts +8 -0
- package/lib/utils/relativeUrl.js +25 -0
- package/package.json +3 -3
- package/src/Article/ArticleByline.tsx +9 -3
- package/src/BlogPost/BlogPost.tsx +7 -4
- package/src/CampaignBlock/CampaignBlock.tsx +5 -1
- package/src/ContactBlock/ContactBlock.tsx +20 -7
- package/src/Embed/AudioEmbed.stories.tsx +0 -4
- package/src/Embed/AudioEmbed.tsx +2 -6
- package/src/Embed/BrightcoveEmbed.stories.tsx +0 -3
- package/src/Embed/BrightcoveEmbed.tsx +2 -3
- package/src/Embed/ConceptEmbed.stories.tsx +0 -5
- package/src/Embed/ExternalEmbed.stories.tsx +0 -2
- package/src/Embed/H5pEmbed.stories.tsx +0 -2
- package/src/Embed/IframeEmbed.stories.tsx +0 -4
- package/src/Embed/ImageEmbed.stories.tsx +0 -2
- package/src/Embed/ImageEmbed.tsx +1 -4
- package/src/FileList/File.tsx +4 -7
- package/src/FileList/FileList.tsx +1 -1
- package/src/FileList/FileV2.tsx +1 -3
- package/src/Grid/Grid.tsx +1 -1
- package/src/KeyFigure/KeyFigure.tsx +6 -2
- package/src/LinkBlock/LinkBlock.tsx +8 -2
- package/src/Table/Table.tsx +5 -2
- package/src/utils/relativeUrl.ts +20 -0
|
@@ -15,6 +15,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
15
15
|
import concat from 'lodash/concat';
|
|
16
16
|
import { errorSvgSrc } from '../Embed/ImageEmbed';
|
|
17
17
|
|
|
18
|
+
const BLOB_WIDTH = 90;
|
|
19
|
+
|
|
18
20
|
interface Props {
|
|
19
21
|
image?: IImageMetaInformationV3;
|
|
20
22
|
jobTitle: string;
|
|
@@ -27,6 +29,7 @@ interface Props {
|
|
|
27
29
|
}
|
|
28
30
|
const BlockWrapper = styled.div`
|
|
29
31
|
display: flex;
|
|
32
|
+
position: relative;
|
|
30
33
|
flex-direction: column;
|
|
31
34
|
padding: 0 0 ${spacing.medium} ${spacing.medium};
|
|
32
35
|
font-family: ${fonts.sans};
|
|
@@ -54,6 +57,7 @@ const StyledHeader = styled.div`
|
|
|
54
57
|
|
|
55
58
|
const StyledText = styled.div`
|
|
56
59
|
display: flex;
|
|
60
|
+
${fonts.sizes('16px', '26px')};
|
|
57
61
|
overflow-wrap: anywhere;
|
|
58
62
|
color: ${colors.text.light};
|
|
59
63
|
gap: ${spacing.xxsmall};
|
|
@@ -68,13 +72,18 @@ const EmailLink = styled.a`
|
|
|
68
72
|
`;
|
|
69
73
|
|
|
70
74
|
const SummaryBlock = styled.p`
|
|
71
|
-
font-family: ${fonts.
|
|
72
|
-
padding: 0
|
|
75
|
+
font-family: ${fonts.sans};
|
|
76
|
+
padding: 0;
|
|
77
|
+
max-width: calc(100% - (${BLOB_WIDTH}px / 2));
|
|
73
78
|
${mq.range({ from: breakpoints.tabletWide })} {
|
|
74
79
|
padding-top: 0;
|
|
75
80
|
}
|
|
76
81
|
`;
|
|
77
82
|
|
|
83
|
+
const InfoWrapper = styled.div`
|
|
84
|
+
max-width: calc(100% - ${BLOB_WIDTH}px);
|
|
85
|
+
`;
|
|
86
|
+
|
|
78
87
|
const TextWrapper = styled.div`
|
|
79
88
|
display: flex;
|
|
80
89
|
overflow: hidden;
|
|
@@ -83,13 +92,17 @@ const TextWrapper = styled.div`
|
|
|
83
92
|
|
|
84
93
|
const BlobWrapper = styled.div`
|
|
85
94
|
height: 180px;
|
|
86
|
-
width:
|
|
95
|
+
width: ${BLOB_WIDTH}px;
|
|
96
|
+
position: absolute;
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
right: 0px;
|
|
87
99
|
`;
|
|
88
100
|
|
|
89
101
|
const ImageWrapper = styled.div`
|
|
90
102
|
display: flex;
|
|
91
103
|
flex-direction: column;
|
|
92
|
-
gap: ${spacing.
|
|
104
|
+
gap: ${spacing.xsmall};
|
|
105
|
+
${fonts.sizes('16px', '26px')};
|
|
93
106
|
padding: ${spacing.medium} ${spacing.medium} 0 0;
|
|
94
107
|
${mq.range({ from: breakpoints.tabletWide })} {
|
|
95
108
|
padding-right: 0;
|
|
@@ -102,7 +115,7 @@ const blobStyling = css`
|
|
|
102
115
|
transform: translate(10%, 0);
|
|
103
116
|
`;
|
|
104
117
|
const Email = styled.div`
|
|
105
|
-
|
|
118
|
+
white-space: nowrap;
|
|
106
119
|
`;
|
|
107
120
|
|
|
108
121
|
const ContentWrapper = styled.div`
|
|
@@ -135,14 +148,14 @@ const ContactBlock = ({ image, jobTitle, description, name, email, blobColor = '
|
|
|
135
148
|
</ImageWrapper>
|
|
136
149
|
<ContentWrapper>
|
|
137
150
|
<TextWrapper>
|
|
138
|
-
<
|
|
151
|
+
<InfoWrapper>
|
|
139
152
|
<StyledHeader>{name}</StyledHeader>
|
|
140
153
|
<StyledText>{jobTitle}</StyledText>
|
|
141
154
|
<StyledText>
|
|
142
155
|
<Email>{`${t('email')}:`}</Email>
|
|
143
156
|
<EmailLink href={`mailto:${email}?subject=Contact us`}>{email}</EmailLink>
|
|
144
157
|
</StyledText>
|
|
145
|
-
</
|
|
158
|
+
</InfoWrapper>
|
|
146
159
|
<BlobWrapper>
|
|
147
160
|
<Blob css={blobStyling} color={isGreenBlob ? colors.support.greenLight : colors.support.redLight} />
|
|
148
161
|
</BlobWrapper>
|
|
@@ -203,7 +203,6 @@ export const AudioEmbedStory: StoryObj<typeof AudioEmbed> = {
|
|
|
203
203
|
embed: {
|
|
204
204
|
resource: 'audio',
|
|
205
205
|
status: 'success',
|
|
206
|
-
seq: 1,
|
|
207
206
|
embedData: embedData,
|
|
208
207
|
data: successData,
|
|
209
208
|
},
|
|
@@ -216,7 +215,6 @@ export const AudioEmbedFailed: StoryObj<typeof AudioEmbed> = {
|
|
|
216
215
|
embed: {
|
|
217
216
|
resource: 'audio',
|
|
218
217
|
status: 'error',
|
|
219
|
-
seq: 1,
|
|
220
218
|
embedData: embedData,
|
|
221
219
|
},
|
|
222
220
|
},
|
|
@@ -228,7 +226,6 @@ export const Podcast: StoryObj<typeof AudioEmbed> = {
|
|
|
228
226
|
embed: {
|
|
229
227
|
resource: 'audio',
|
|
230
228
|
status: 'success',
|
|
231
|
-
seq: 1,
|
|
232
229
|
embedData: podcastEmbedData,
|
|
233
230
|
data: podcastSuccessData,
|
|
234
231
|
},
|
|
@@ -241,7 +238,6 @@ export const PodcastFailed: StoryObj<typeof AudioEmbed> = {
|
|
|
241
238
|
embed: {
|
|
242
239
|
resource: 'audio',
|
|
243
240
|
status: 'error',
|
|
244
|
-
seq: 1,
|
|
245
241
|
embedData: podcastEmbedData,
|
|
246
242
|
},
|
|
247
243
|
},
|
package/src/Embed/AudioEmbed.tsx
CHANGED
|
@@ -39,8 +39,6 @@ const imageMetaToMockEmbed = (
|
|
|
39
39
|
): Extract<ImageMetaData, { status: 'success' }> => ({
|
|
40
40
|
resource: 'image',
|
|
41
41
|
status: 'success',
|
|
42
|
-
// Make sure the seq is unused. It's rarely used, but it's nice to ensure uniqueness.
|
|
43
|
-
seq: imageMeta.seq + 0.1,
|
|
44
42
|
// We check that this exists where the function is used.
|
|
45
43
|
data: imageMeta.data.imageMeta!,
|
|
46
44
|
embedData: {
|
|
@@ -55,7 +53,7 @@ const AudioEmbed = ({ embed, heartButton: HeartButton }: Props) => {
|
|
|
55
53
|
return <EmbedErrorPlaceholder type={embed.embedData.type === 'standard' ? 'audio' : 'podcast'} />;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
const { data, embedData
|
|
56
|
+
const { data, embedData } = embed;
|
|
59
57
|
|
|
60
58
|
if (embedData.type === 'minimal') {
|
|
61
59
|
return <AudioPlayer speech src={data.audioFile.url} title={data.title.title} />;
|
|
@@ -69,10 +67,8 @@ const AudioEmbed = ({ embed, heartButton: HeartButton }: Props) => {
|
|
|
69
67
|
|
|
70
68
|
const img = coverPhoto && { url: coverPhoto.url, alt: coverPhoto.altText };
|
|
71
69
|
|
|
72
|
-
const figureId = `figure-${seq}-${data.id}`;
|
|
73
|
-
|
|
74
70
|
return (
|
|
75
|
-
<Figure
|
|
71
|
+
<Figure type="full">
|
|
76
72
|
<AudioPlayer
|
|
77
73
|
description={data.podcastMeta?.introduction ?? ''}
|
|
78
74
|
img={img}
|
|
@@ -82,7 +82,6 @@ const metaData: BrightcoveData = {
|
|
|
82
82
|
|
|
83
83
|
const visuallyInterpretedEmbedMetaData: BrightcoveMetaData = {
|
|
84
84
|
resource: 'brightcove',
|
|
85
|
-
seq: 3,
|
|
86
85
|
status: 'success',
|
|
87
86
|
embedData: {
|
|
88
87
|
resource: 'brightcove',
|
|
@@ -183,7 +182,6 @@ export const BrightcoveEmbedStory: StoryObj<typeof BrightcoveEmbed> = {
|
|
|
183
182
|
embed: {
|
|
184
183
|
resource: 'brightcove',
|
|
185
184
|
status: 'success',
|
|
186
|
-
seq: 1,
|
|
187
185
|
embedData: embedData,
|
|
188
186
|
data: metaData,
|
|
189
187
|
},
|
|
@@ -203,7 +201,6 @@ export const BrightcoveFailed: StoryObj<typeof BrightcoveEmbed> = {
|
|
|
203
201
|
embed: {
|
|
204
202
|
resource: 'brightcove',
|
|
205
203
|
status: 'error',
|
|
206
|
-
seq: 1,
|
|
207
204
|
embedData: embedData,
|
|
208
205
|
},
|
|
209
206
|
},
|
|
@@ -86,18 +86,17 @@ const BrightcoveEmbed = ({ embed, isConcept, heartButton: HeartButton }: Props)
|
|
|
86
86
|
</EmbedErrorPlaceholder>
|
|
87
87
|
);
|
|
88
88
|
}
|
|
89
|
-
const { data
|
|
89
|
+
const { data } = embed;
|
|
90
90
|
|
|
91
91
|
const linkedVideoId = isNumeric(data.link?.text) ? data.link?.text : undefined;
|
|
92
92
|
|
|
93
|
-
const figureId = `figure-${seq}-${data.id}`;
|
|
94
93
|
const originalVideoProps = getIframeProps(embedData, data.sources);
|
|
95
94
|
const alternativeVideoProps = linkedVideoId
|
|
96
95
|
? getIframeProps({ ...embedData, videoid: linkedVideoId }, data.sources)
|
|
97
96
|
: undefined;
|
|
98
97
|
|
|
99
98
|
return (
|
|
100
|
-
<Figure
|
|
99
|
+
<Figure type={isConcept ? 'full-column' : 'full'} resizeIframe>
|
|
101
100
|
<div className="brightcove-video">
|
|
102
101
|
<BrightcoveIframe
|
|
103
102
|
ref={iframeRef}
|
|
@@ -72,7 +72,6 @@ const conceptMetaData: ConceptData['concept'] = {
|
|
|
72
72
|
const visualElementData: ConceptData['visualElement'] = {
|
|
73
73
|
resource: 'image',
|
|
74
74
|
status: 'success',
|
|
75
|
-
seq: 6,
|
|
76
75
|
embedData: {
|
|
77
76
|
resource: 'image',
|
|
78
77
|
resourceId: '52863',
|
|
@@ -170,7 +169,6 @@ export const Block: StoryObj<typeof ConceptEmbed> = {
|
|
|
170
169
|
embed: {
|
|
171
170
|
resource: 'concept',
|
|
172
171
|
status: 'success',
|
|
173
|
-
seq: 1,
|
|
174
172
|
embedData: blockEmbedData,
|
|
175
173
|
data: blockMetaData,
|
|
176
174
|
},
|
|
@@ -183,7 +181,6 @@ export const BlockFailed: StoryObj<typeof ConceptEmbed> = {
|
|
|
183
181
|
embed: {
|
|
184
182
|
resource: 'concept',
|
|
185
183
|
status: 'error',
|
|
186
|
-
seq: 1,
|
|
187
184
|
embedData: blockEmbedData,
|
|
188
185
|
},
|
|
189
186
|
},
|
|
@@ -195,7 +192,6 @@ export const Inline: StoryObj<typeof ConceptEmbed> = {
|
|
|
195
192
|
embed: {
|
|
196
193
|
resource: 'concept',
|
|
197
194
|
status: 'success',
|
|
198
|
-
seq: 1,
|
|
199
195
|
embedData: inlineEmbedData,
|
|
200
196
|
data: blockMetaData,
|
|
201
197
|
},
|
|
@@ -208,7 +204,6 @@ export const InlineFailed: StoryObj<typeof ConceptEmbed> = {
|
|
|
208
204
|
embed: {
|
|
209
205
|
resource: 'concept',
|
|
210
206
|
status: 'error',
|
|
211
|
-
seq: 1,
|
|
212
207
|
embedData: inlineEmbedData,
|
|
213
208
|
},
|
|
214
209
|
},
|
|
@@ -66,7 +66,6 @@ export const Regular: StoryObj<typeof ExternalEmbed> = {
|
|
|
66
66
|
embed: {
|
|
67
67
|
resource: 'external',
|
|
68
68
|
status: 'success',
|
|
69
|
-
seq: 8,
|
|
70
69
|
embedData: embedData,
|
|
71
70
|
data: metaData,
|
|
72
71
|
},
|
|
@@ -78,7 +77,6 @@ export const Failed: StoryObj<typeof ExternalEmbed> = {
|
|
|
78
77
|
embed: {
|
|
79
78
|
resource: 'external',
|
|
80
79
|
status: 'error',
|
|
81
|
-
seq: 3,
|
|
82
80
|
embedData: embedData,
|
|
83
81
|
},
|
|
84
82
|
},
|
|
@@ -72,7 +72,6 @@ export const Regular: StoryObj<typeof H5pEmbed> = {
|
|
|
72
72
|
embed: {
|
|
73
73
|
resource: 'h5p',
|
|
74
74
|
status: 'success',
|
|
75
|
-
seq: 5,
|
|
76
75
|
embedData: embedData,
|
|
77
76
|
data: metaData,
|
|
78
77
|
},
|
|
@@ -84,7 +83,6 @@ export const Failed: StoryObj<typeof H5pEmbed> = {
|
|
|
84
83
|
embed: {
|
|
85
84
|
resource: 'h5p',
|
|
86
85
|
status: 'error',
|
|
87
|
-
seq: 3,
|
|
88
86
|
embedData: embedData,
|
|
89
87
|
},
|
|
90
88
|
},
|
|
@@ -47,7 +47,6 @@ export const Regular: StoryObj<typeof IframeEmbed> = {
|
|
|
47
47
|
embed: {
|
|
48
48
|
resource: 'iframe',
|
|
49
49
|
status: 'success',
|
|
50
|
-
seq: 3,
|
|
51
50
|
embedData: embedData,
|
|
52
51
|
data: {},
|
|
53
52
|
},
|
|
@@ -59,7 +58,6 @@ export const Failed: StoryObj<typeof IframeEmbed> = {
|
|
|
59
58
|
embed: {
|
|
60
59
|
resource: 'iframe',
|
|
61
60
|
status: 'error',
|
|
62
|
-
seq: 3,
|
|
63
61
|
embedData: embedData,
|
|
64
62
|
},
|
|
65
63
|
},
|
|
@@ -132,7 +130,6 @@ export const OpensInNewWindow: StoryObj<typeof IframeEmbed> = {
|
|
|
132
130
|
embed: {
|
|
133
131
|
resource: 'iframe',
|
|
134
132
|
status: 'success',
|
|
135
|
-
seq: 4,
|
|
136
133
|
embedData: opensInNewEmbedData,
|
|
137
134
|
data: opensInnewMetaData,
|
|
138
135
|
},
|
|
@@ -144,7 +141,6 @@ export const OpensInNewWindowFailed: StoryObj<typeof IframeEmbed> = {
|
|
|
144
141
|
embed: {
|
|
145
142
|
resource: 'iframe',
|
|
146
143
|
status: 'error',
|
|
147
|
-
seq: 4,
|
|
148
144
|
embedData: opensInNewEmbedData,
|
|
149
145
|
},
|
|
150
146
|
},
|
|
@@ -111,7 +111,6 @@ export const ImageEmbedStory: StoryObj<typeof ImageEmbed> = {
|
|
|
111
111
|
embed: {
|
|
112
112
|
resource: 'image',
|
|
113
113
|
status: 'success',
|
|
114
|
-
seq: 1,
|
|
115
114
|
embedData: embedData,
|
|
116
115
|
data: metaData,
|
|
117
116
|
},
|
|
@@ -124,7 +123,6 @@ export const Failed: StoryObj<typeof ImageEmbed> = {
|
|
|
124
123
|
embed: {
|
|
125
124
|
resource: 'image',
|
|
126
125
|
status: 'error',
|
|
127
|
-
seq: 1,
|
|
128
126
|
embedData: embedData,
|
|
129
127
|
},
|
|
130
128
|
},
|
package/src/Embed/ImageEmbed.tsx
CHANGED
|
@@ -111,7 +111,7 @@ const ImageEmbed = ({ embed, previewAlt, heartButton: HeartButton, inGrid, path
|
|
|
111
111
|
return <EmbedErrorPlaceholder type={'image'} figureType={figureType} />;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
const { data, embedData
|
|
114
|
+
const { data, embedData } = embed;
|
|
115
115
|
|
|
116
116
|
const altText = embedData.alt || '';
|
|
117
117
|
|
|
@@ -121,13 +121,10 @@ const ImageEmbed = ({ embed, previewAlt, heartButton: HeartButton, inGrid, path
|
|
|
121
121
|
const focalPoint = getFocalPoint(embedData);
|
|
122
122
|
const crop = getCrop(embedData);
|
|
123
123
|
|
|
124
|
-
const figureId = `figure-${seq}-${data.id}`;
|
|
125
|
-
|
|
126
124
|
const isCopyrighted = data.copyright.license.license.toLowerCase() === COPYRIGHTED;
|
|
127
125
|
|
|
128
126
|
return (
|
|
129
127
|
<Figure
|
|
130
|
-
id={figureId}
|
|
131
128
|
type={imageSizes ? undefined : figureType}
|
|
132
129
|
className={imageSizes ? `c-figure--${embedData.align} expanded` : ''}
|
|
133
130
|
>
|
package/src/FileList/File.tsx
CHANGED
|
@@ -31,11 +31,9 @@ const FileLink = styled(SafeLink)`
|
|
|
31
31
|
}
|
|
32
32
|
`;
|
|
33
33
|
|
|
34
|
-
const renderFormat = (format: FileFormat, title: string, isPrimary: boolean,
|
|
34
|
+
const renderFormat = (format: FileFormat, title: string, isPrimary: boolean, isDeadLink: boolean) => {
|
|
35
35
|
const titleWithFormat = `${title} (${format.fileType.toUpperCase()})`;
|
|
36
36
|
|
|
37
|
-
const formatId = `${id}_${format.fileType}`;
|
|
38
|
-
|
|
39
37
|
if (isDeadLink) {
|
|
40
38
|
return (
|
|
41
39
|
<span key={format.url}>
|
|
@@ -46,7 +44,7 @@ const renderFormat = (format: FileFormat, title: string, isPrimary: boolean, id:
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
return (
|
|
49
|
-
<FileLink key={format.url} to={format.url} target="_blank" aria-label={titleWithFormat}
|
|
47
|
+
<FileLink key={format.url} to={format.url} target="_blank" aria-label={titleWithFormat}>
|
|
50
48
|
<Download />
|
|
51
49
|
<Tooltip tooltip={format.tooltip}>
|
|
52
50
|
<LinkTextWrapper>
|
|
@@ -58,7 +56,6 @@ const renderFormat = (format: FileFormat, title: string, isPrimary: boolean, id:
|
|
|
58
56
|
};
|
|
59
57
|
|
|
60
58
|
interface Props {
|
|
61
|
-
id: string;
|
|
62
59
|
file: FileType;
|
|
63
60
|
}
|
|
64
61
|
|
|
@@ -77,9 +74,9 @@ const FileListItem = styled.li`
|
|
|
77
74
|
}
|
|
78
75
|
`;
|
|
79
76
|
|
|
80
|
-
const File = ({ file
|
|
77
|
+
const File = ({ file }: Props) => {
|
|
81
78
|
const formatLinks = file.formats.map((format, index) =>
|
|
82
|
-
renderFormat(format, file.title, index === 0,
|
|
79
|
+
renderFormat(format, file.title, index === 0, !file.fileExists),
|
|
83
80
|
);
|
|
84
81
|
|
|
85
82
|
return <FileListItem key={file.title}>{formatLinks}</FileListItem>;
|
|
@@ -55,7 +55,7 @@ const FileList = ({ files, heading, id }: Props) => (
|
|
|
55
55
|
<FileListHeading>{heading}</FileListHeading>
|
|
56
56
|
<FilesList>
|
|
57
57
|
{files.map((file) => (
|
|
58
|
-
<File key={`file-${id}-${file.title}`} file={file}
|
|
58
|
+
<File key={`file-${id}-${file.title}`} file={file} />
|
|
59
59
|
))}
|
|
60
60
|
</FilesList>
|
|
61
61
|
</FileListSection>
|
package/src/FileList/FileV2.tsx
CHANGED
|
@@ -10,19 +10,17 @@ import { useTranslation } from 'react-i18next';
|
|
|
10
10
|
import File from './File';
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
|
-
id: string;
|
|
14
13
|
title: string;
|
|
15
14
|
url: string;
|
|
16
15
|
fileExists: boolean;
|
|
17
16
|
fileType: string;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
const FileV2 = ({ title, url,
|
|
19
|
+
const FileV2 = ({ title, url, fileExists, fileType }: Props) => {
|
|
21
20
|
const { t } = useTranslation();
|
|
22
21
|
const tooltip = `${t('download')} ${url.split('/').pop()}`;
|
|
23
22
|
return (
|
|
24
23
|
<File
|
|
25
|
-
id={id}
|
|
26
24
|
file={{
|
|
27
25
|
title,
|
|
28
26
|
fileExists,
|
package/src/Grid/Grid.tsx
CHANGED
|
@@ -16,10 +16,14 @@ const ContentWrapper = styled.div`
|
|
|
16
16
|
align-items: center;
|
|
17
17
|
padding: ${spacing.small};
|
|
18
18
|
align-items: center;
|
|
19
|
+
${mq.range({ from: breakpoints.tabletWide })} {
|
|
20
|
+
padding: ${spacing.medium} ${spacing.large};
|
|
21
|
+
}
|
|
19
22
|
`;
|
|
20
23
|
|
|
21
24
|
const StyledImage = styled.img`
|
|
22
|
-
|
|
25
|
+
height: 150px;
|
|
26
|
+
width: 150px;
|
|
23
27
|
`;
|
|
24
28
|
|
|
25
29
|
const TitleWrapper = styled.div`
|
|
@@ -60,7 +64,7 @@ interface Props {
|
|
|
60
64
|
const KeyFigure = ({ image, title, subtitle }: Props) => {
|
|
61
65
|
return (
|
|
62
66
|
<ContentWrapper>
|
|
63
|
-
<StyledImage src={image?.src} width={
|
|
67
|
+
<StyledImage src={image?.src} width={150} height={150} alt={image?.alt} />
|
|
64
68
|
<TitleWrapper>{title}</TitleWrapper>
|
|
65
69
|
<SubTitleWrapper>{subtitle}</SubTitleWrapper>
|
|
66
70
|
</ContentWrapper>
|
|
@@ -15,6 +15,7 @@ import { breakpoints, colors, spacing, mq } from '@ndla/core';
|
|
|
15
15
|
import { LinkBlockEmbedData } from '@ndla/types-embed';
|
|
16
16
|
import { useMemo } from 'react';
|
|
17
17
|
import Heading from '../Typography/Heading';
|
|
18
|
+
import { usePossiblyRelativeUrl } from '../utils/relativeUrl';
|
|
18
19
|
|
|
19
20
|
const StyledForward = styled(Forward)`
|
|
20
21
|
margin: 0 ${spacing.nsmall};
|
|
@@ -72,7 +73,12 @@ const StyledCalenderEd = styled(CalendarEd)`
|
|
|
72
73
|
color: ${colors.icon.iconBlue};
|
|
73
74
|
`;
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
interface Props extends Omit<LinkBlockEmbedData, 'resource'> {
|
|
77
|
+
path?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const LinkBlock = ({ title, language, date, url, path }: Props) => {
|
|
81
|
+
const href = usePossiblyRelativeUrl(url, path);
|
|
76
82
|
const formattedDate = useMemo(() => {
|
|
77
83
|
if (!date) return null;
|
|
78
84
|
const locale = language === 'nb' ? nb : language === 'nn' ? nn : enGB;
|
|
@@ -80,7 +86,7 @@ const LinkBlock = ({ title, language, date, url }: Omit<LinkBlockEmbedData, 'res
|
|
|
80
86
|
}, [date, language]);
|
|
81
87
|
|
|
82
88
|
return (
|
|
83
|
-
<StyledSafeLink to={
|
|
89
|
+
<StyledSafeLink to={href}>
|
|
84
90
|
<InfoWrapper>
|
|
85
91
|
<Heading element="h3" margin="none" headingStyle="h3" lang={language}>
|
|
86
92
|
{title}
|
package/src/Table/Table.tsx
CHANGED
|
@@ -194,6 +194,9 @@ export const TableStyling = css`
|
|
|
194
194
|
const TableWrapper = styled.div`
|
|
195
195
|
display: flex;
|
|
196
196
|
justify-content: center;
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
const OverflowWrapper = styled.div`
|
|
197
200
|
overflow-x: auto;
|
|
198
201
|
`;
|
|
199
202
|
|
|
@@ -242,13 +245,13 @@ const Table = ({ children, id, ...rest }: Props) => {
|
|
|
242
245
|
|
|
243
246
|
return (
|
|
244
247
|
<TableWrapper>
|
|
245
|
-
<
|
|
248
|
+
<OverflowWrapper>
|
|
246
249
|
<LeftScrollBorder data-block={scrollPosition === 'end' || scrollPosition === 'center'} />
|
|
247
250
|
<table ref={tableRef} id={id} onScroll={onScroll} css={TableStyling} {...rest}>
|
|
248
251
|
{children}
|
|
249
252
|
</table>
|
|
250
253
|
<RightScrollBorder data-block={scrollPosition === 'start' || scrollPosition === 'center'} />
|
|
251
|
-
</
|
|
254
|
+
</OverflowWrapper>
|
|
252
255
|
</TableWrapper>
|
|
253
256
|
);
|
|
254
257
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
const ENV_REGEX = /(staging|test)\.?/;
|
|
10
|
+
|
|
11
|
+
const NDLA_URL = /(.*\.)?ndla.no.*/;
|
|
12
|
+
|
|
13
|
+
export const usePossiblyRelativeUrl = (url: string, path?: string) => {
|
|
14
|
+
if (!path) return url;
|
|
15
|
+
if (!NDLA_URL.test(url) || !NDLA_URL.test(path)) return url;
|
|
16
|
+
const urlObj = new URL(url.replace(ENV_REGEX, ''));
|
|
17
|
+
const pathObj = new URL(path.replace(ENV_REGEX, ''));
|
|
18
|
+
if (urlObj.host === pathObj.host) return urlObj.pathname;
|
|
19
|
+
return url;
|
|
20
|
+
};
|