@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.
Files changed (59) hide show
  1. package/es/Article/ArticleByline.js +18 -12
  2. package/es/BlogPost/BlogPost.js +9 -6
  3. package/es/CampaignBlock/CampaignBlock.js +10 -7
  4. package/es/ContactBlock/ContactBlock.js +30 -33
  5. package/es/Embed/AudioEmbed.js +1 -6
  6. package/es/Embed/BrightcoveEmbed.js +3 -6
  7. package/es/Embed/ImageEmbed.js +2 -5
  8. package/es/FileList/File.js +6 -9
  9. package/es/FileList/FileList.js +4 -5
  10. package/es/FileList/FileV2.js +0 -2
  11. package/es/Grid/Grid.js +2 -2
  12. package/es/KeyFigure/KeyFigure.js +12 -10
  13. package/es/LinkBlock/LinkBlock.js +10 -7
  14. package/es/Table/Table.js +26 -14
  15. package/es/utils/relativeUrl.js +18 -0
  16. package/lib/Article/ArticleByline.js +18 -12
  17. package/lib/BlogPost/BlogPost.d.ts +2 -1
  18. package/lib/BlogPost/BlogPost.js +9 -6
  19. package/lib/CampaignBlock/CampaignBlock.d.ts +2 -1
  20. package/lib/CampaignBlock/CampaignBlock.js +10 -7
  21. package/lib/ContactBlock/ContactBlock.js +30 -33
  22. package/lib/Embed/AudioEmbed.js +1 -6
  23. package/lib/Embed/BrightcoveEmbed.js +3 -6
  24. package/lib/Embed/ImageEmbed.js +2 -5
  25. package/lib/FileList/File.d.ts +1 -2
  26. package/lib/FileList/File.js +6 -9
  27. package/lib/FileList/FileList.js +4 -5
  28. package/lib/FileList/FileV2.d.ts +1 -2
  29. package/lib/FileList/FileV2.js +0 -2
  30. package/lib/Grid/Grid.js +2 -2
  31. package/lib/KeyFigure/KeyFigure.js +12 -10
  32. package/lib/LinkBlock/LinkBlock.d.ts +4 -1
  33. package/lib/LinkBlock/LinkBlock.js +10 -7
  34. package/lib/Table/Table.js +26 -14
  35. package/lib/utils/relativeUrl.d.ts +8 -0
  36. package/lib/utils/relativeUrl.js +25 -0
  37. package/package.json +3 -3
  38. package/src/Article/ArticleByline.tsx +9 -3
  39. package/src/BlogPost/BlogPost.tsx +7 -4
  40. package/src/CampaignBlock/CampaignBlock.tsx +5 -1
  41. package/src/ContactBlock/ContactBlock.tsx +20 -7
  42. package/src/Embed/AudioEmbed.stories.tsx +0 -4
  43. package/src/Embed/AudioEmbed.tsx +2 -6
  44. package/src/Embed/BrightcoveEmbed.stories.tsx +0 -3
  45. package/src/Embed/BrightcoveEmbed.tsx +2 -3
  46. package/src/Embed/ConceptEmbed.stories.tsx +0 -5
  47. package/src/Embed/ExternalEmbed.stories.tsx +0 -2
  48. package/src/Embed/H5pEmbed.stories.tsx +0 -2
  49. package/src/Embed/IframeEmbed.stories.tsx +0 -4
  50. package/src/Embed/ImageEmbed.stories.tsx +0 -2
  51. package/src/Embed/ImageEmbed.tsx +1 -4
  52. package/src/FileList/File.tsx +4 -7
  53. package/src/FileList/FileList.tsx +1 -1
  54. package/src/FileList/FileV2.tsx +1 -3
  55. package/src/Grid/Grid.tsx +1 -1
  56. package/src/KeyFigure/KeyFigure.tsx +6 -2
  57. package/src/LinkBlock/LinkBlock.tsx +8 -2
  58. package/src/Table/Table.tsx +5 -2
  59. 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.serif};
72
- padding: 0 ${spacing.medium} 0 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: 90px;
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.small};
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
- min-width: 60px;
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
- <div>
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
- </div>
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
  },
@@ -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, seq } = embed;
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 id={figureId} type="full">
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, seq } = embed;
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 id={figureId} type={isConcept ? 'full-column' : 'full'} resizeIframe>
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
  },
@@ -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, seq } = embed;
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
  >
@@ -31,11 +31,9 @@ const FileLink = styled(SafeLink)`
31
31
  }
32
32
  `;
33
33
 
34
- const renderFormat = (format: FileFormat, title: string, isPrimary: boolean, id: string, isDeadLink: 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} aria-describedby={formatId}>
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, id }: Props) => {
77
+ const File = ({ file }: Props) => {
81
78
  const formatLinks = file.formats.map((format, index) =>
82
- renderFormat(format, file.title, index === 0, id, !file.fileExists),
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} id={id} />
58
+ <File key={`file-${id}-${file.title}`} file={file} />
59
59
  ))}
60
60
  </FilesList>
61
61
  </FileListSection>
@@ -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, id, fileExists, fileType }: Props) => {
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
@@ -60,7 +60,7 @@ const GridContainer = styled.div`
60
60
  }
61
61
 
62
62
  &[data-border='lightBlue'] {
63
- border: 1px solid ${colors.brand.light};
63
+ border: 1px solid ${colors.brand.lighter};
64
64
  }
65
65
  &[data-background='white'] {
66
66
  background-color: ${colors.white};
@@ -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
- width: 100%;
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={250} height={200} alt={image?.alt} />
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
- const LinkBlock = ({ title, language, date, url }: Omit<LinkBlockEmbedData, 'resource'>) => {
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={url}>
89
+ <StyledSafeLink to={href}>
84
90
  <InfoWrapper>
85
91
  <Heading element="h3" margin="none" headingStyle="h3" lang={language}>
86
92
  {title}
@@ -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
- <div>
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
- </div>
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
+ };