@ndla/ui 43.0.3 → 44.0.1

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 (53) hide show
  1. package/es/Article/Article.js +6 -14
  2. package/es/Article/ArticleNotions.js +24 -48
  3. package/es/Article/index.js +1 -2
  4. package/es/ContactBlock/ContactBlock.js +13 -13
  5. package/es/Embed/ImageEmbed.js +2 -2
  6. package/es/Grid/Grid.js +15 -2
  7. package/es/Grid/index.js +1 -1
  8. package/es/LinkBlock/LinkBlock.js +16 -6
  9. package/es/{Article/ArticleContent.js → LinkBlock/LinkBlockSection.js} +22 -30
  10. package/es/LinkBlock/index.js +2 -1
  11. package/es/Topic/Topic.js +60 -42
  12. package/es/index.js +4 -3
  13. package/lib/Article/Article.d.ts +3 -9
  14. package/lib/Article/Article.js +6 -14
  15. package/lib/Article/ArticleNotions.d.ts +3 -9
  16. package/lib/Article/ArticleNotions.js +30 -56
  17. package/lib/Article/index.d.ts +1 -2
  18. package/lib/Article/index.js +0 -7
  19. package/lib/ContactBlock/ContactBlock.js +13 -13
  20. package/lib/Embed/ImageEmbed.d.ts +11 -1
  21. package/lib/Embed/ImageEmbed.js +3 -1
  22. package/lib/Grid/Grid.d.ts +4 -0
  23. package/lib/Grid/Grid.js +23 -10
  24. package/lib/Grid/index.d.ts +1 -1
  25. package/lib/Grid/index.js +10 -2
  26. package/lib/LinkBlock/LinkBlock.d.ts +2 -7
  27. package/lib/LinkBlock/LinkBlock.js +16 -6
  28. package/lib/LinkBlock/LinkBlockSection.d.ts +13 -0
  29. package/lib/{Article/ArticleContent.js → LinkBlock/LinkBlockSection.js} +22 -27
  30. package/lib/LinkBlock/index.d.ts +1 -0
  31. package/lib/LinkBlock/index.js +7 -0
  32. package/lib/Topic/Topic.d.ts +10 -17
  33. package/lib/Topic/Topic.js +62 -44
  34. package/lib/index.d.ts +3 -2
  35. package/lib/index.js +19 -6
  36. package/lib/types.d.ts +1 -1
  37. package/package.json +6 -5
  38. package/src/Article/Article.tsx +6 -16
  39. package/src/Article/ArticleNotions.tsx +9 -78
  40. package/src/Article/index.ts +1 -10
  41. package/src/ContactBlock/ContactBlock.tsx +3 -0
  42. package/src/Embed/ImageEmbed.tsx +2 -2
  43. package/src/Grid/Grid.tsx +9 -1
  44. package/src/Grid/index.ts +1 -1
  45. package/src/LinkBlock/LinkBlock.stories.tsx +18 -8
  46. package/src/LinkBlock/LinkBlock.tsx +12 -9
  47. package/src/LinkBlock/LinkBlockSection.tsx +42 -0
  48. package/src/LinkBlock/index.ts +1 -0
  49. package/src/Topic/Topic.tsx +58 -39
  50. package/src/index.ts +3 -2
  51. package/src/types.ts +1 -1
  52. package/lib/Article/ArticleContent.d.ts +0 -7
  53. package/src/Article/ArticleContent.tsx +0 -40
package/lib/index.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  export { default as SectionHeading } from './SectionHeading';
9
9
  export { ConceptNotionV2, ImageEmbed, AudioEmbed, H5pEmbed, ExternalEmbed, IframeEmbed, FootnoteEmbed, BrightcoveEmbed, ContentLinkEmbed, RelatedContentEmbed, ConceptEmbed, ConceptListEmbed, UnknownEmbed, } from './Embed';
10
- export { ArticleByline, ArticleContent, ArticleFootNotes, ArticleIntroduction, ArticleTitle, ArticleWrapper, ArticleHeaderWrapper, default as Article, } from './Article';
10
+ export { ArticleByline, ArticleFootNotes, ArticleIntroduction, ArticleTitle, ArticleWrapper, ArticleHeaderWrapper, default as Article, } from './Article';
11
11
  export { default as Table } from './Table';
12
12
  export { default as ResourcesWrapper, ResourcesTopicTitle } from './ResourcesWrapper';
13
13
  export { createUniversalPortal } from './utils/createUniversalPortal';
@@ -94,7 +94,7 @@ export { KeyFigure } from './KeyFigure';
94
94
  export { default as ContactBlock } from './ContactBlock';
95
95
  export type { HeartButtonType } from './Embed';
96
96
  export { CampaignBlock } from './CampaignBlock';
97
- export { Grid } from './Grid';
97
+ export { Grid, GridParallaxItem } from './Grid';
98
98
  export type { GridType } from './Grid';
99
99
  export { Heading } from './Typography';
100
100
  export type { HeadingLevel } from './types';
@@ -102,3 +102,4 @@ export { default as FrontpageArticle } from './FrontpageArticle';
102
102
  export { DefinitionTerm, DefinitionDescription } from './DefinitionList';
103
103
  export type { ProgrammeV2 } from './ProgrammeCard';
104
104
  export { Gloss } from './Gloss';
105
+ export { LinkBlock, LinkBlockSection } from './LinkBlock';
package/lib/index.js CHANGED
@@ -34,12 +34,6 @@ Object.defineProperty(exports, "ArticleByline", {
34
34
  return _Article.ArticleByline;
35
35
  }
36
36
  });
37
- Object.defineProperty(exports, "ArticleContent", {
38
- enumerable: true,
39
- get: function get() {
40
- return _Article.ArticleContent;
41
- }
42
- });
43
37
  Object.defineProperty(exports, "ArticleFootNotes", {
44
38
  enumerable: true,
45
39
  get: function get() {
@@ -502,6 +496,12 @@ Object.defineProperty(exports, "Grid", {
502
496
  return _Grid.Grid;
503
497
  }
504
498
  });
499
+ Object.defineProperty(exports, "GridParallaxItem", {
500
+ enumerable: true,
501
+ get: function get() {
502
+ return _Grid.GridParallaxItem;
503
+ }
504
+ });
505
505
  Object.defineProperty(exports, "H5pEmbed", {
506
506
  enumerable: true,
507
507
  get: function get() {
@@ -670,6 +670,18 @@ Object.defineProperty(exports, "LetterFilter", {
670
670
  return _LetterFilter.default;
671
671
  }
672
672
  });
673
+ Object.defineProperty(exports, "LinkBlock", {
674
+ enumerable: true,
675
+ get: function get() {
676
+ return _LinkBlock.LinkBlock;
677
+ }
678
+ });
679
+ Object.defineProperty(exports, "LinkBlockSection", {
680
+ enumerable: true,
681
+ get: function get() {
682
+ return _LinkBlock.LinkBlockSection;
683
+ }
684
+ });
673
685
  Object.defineProperty(exports, "ListResource", {
674
686
  enumerable: true,
675
687
  get: function get() {
@@ -1334,6 +1346,7 @@ var _Typography = require("./Typography");
1334
1346
  var _FrontpageArticle = _interopRequireDefault(require("./FrontpageArticle"));
1335
1347
  var _DefinitionList = require("./DefinitionList");
1336
1348
  var _Gloss = require("./Gloss");
1349
+ var _LinkBlock = require("./LinkBlock");
1337
1350
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
1338
1351
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
1339
1352
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
package/lib/types.d.ts CHANGED
@@ -57,7 +57,7 @@ export interface FootNote {
57
57
  export interface Article {
58
58
  title: string;
59
59
  introduction: string;
60
- content: string | ReactNode;
60
+ content: ReactNode;
61
61
  footNotes: Array<FootNote>;
62
62
  copyright: Copyright;
63
63
  published: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
- "version": "43.0.3",
3
+ "version": "44.0.1",
4
4
  "description": "UI component library for NDLA.",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -40,9 +40,9 @@
40
40
  "@ndla/forms": "^4.3.17",
41
41
  "@ndla/hooks": "^2.0.9",
42
42
  "@ndla/icons": "^4.0.2",
43
- "@ndla/licenses": "^7.1.2",
43
+ "@ndla/licenses": "^7.1.3",
44
44
  "@ndla/modal": "^4.0.1",
45
- "@ndla/notion": "^5.0.15",
45
+ "@ndla/notion": "^5.0.16",
46
46
  "@ndla/safelink": "^4.1.16",
47
47
  "@ndla/select": "^2.4.8",
48
48
  "@ndla/switch": "^1.1.9",
@@ -51,6 +51,7 @@
51
51
  "@ndla/util": "^3.1.15",
52
52
  "@radix-ui/react-popover": "^1.0.6",
53
53
  "@radix-ui/react-slider": "^1.1.2",
54
+ "date-fns": "^2.30.0",
54
55
  "framer-motion": "^6.5.1",
55
56
  "html-react-parser": "^3.0.8",
56
57
  "i18next-browser-languagedetector": "^6.1.1",
@@ -72,7 +73,7 @@
72
73
  "devDependencies": {
73
74
  "@babel/plugin-proposal-optional-chaining": "^7.11.0",
74
75
  "@ndla/types-backend": "^0.2.5",
75
- "@ndla/types-embed": "^2.0.6",
76
+ "@ndla/types-embed": "^2.0.7",
76
77
  "css-loader": "^6.7.3",
77
78
  "mini-css-extract-plugin": "^2.7.5",
78
79
  "sass-loader": "^13.2.2",
@@ -82,5 +83,5 @@
82
83
  "publishConfig": {
83
84
  "access": "public"
84
85
  },
85
- "gitHead": "6f18d70d6d1cc3e21697da608ab1f136a1fd53aa"
86
+ "gitHead": "494677b86370dc86e7642c6c3934435339f87e18"
86
87
  }
@@ -14,15 +14,13 @@ import styled from '@emotion/styled';
14
14
  import { useIntersectionObserver } from '@ndla/hooks';
15
15
  import { resizeObserver } from '@ndla/util';
16
16
  import { spacing, spacingUnit, mq, breakpoints } from '@ndla/core';
17
- import { Article as ArticleType, Locale } from '../types';
18
- import ArticleContent from './ArticleContent';
17
+ import { Article as ArticleType } from '../types';
19
18
  import ArticleByline from './ArticleByline';
20
19
  import LayoutItem from '../Layout';
21
20
  import ArticleHeaderWrapper from './ArticleHeaderWrapper';
22
- import ArticleNotions, { NotionRelatedContent } from './ArticleNotions';
21
+ import ArticleNotions from './ArticleNotions';
23
22
  import ArticleAccessMessage from './ArticleAccessMessage';
24
23
  import MessageBox from '../Messages/MessageBox';
25
- import { ConceptNotionType } from '../Notion/ConceptNotion';
26
24
  import { Heading } from '../Typography';
27
25
 
28
26
  const classes = new BEMHelper({
@@ -124,22 +122,19 @@ type Props = {
124
122
  children?: ReactNode;
125
123
  messages: Messages;
126
124
  contentTransformed?: boolean;
127
- locale: Locale;
128
125
  messageBoxLinks?: [];
129
126
  competenceGoals?: ReactNode;
130
127
  id: string;
131
128
  renderMarkdown: (text: string) => string;
132
- notions?: { list: ConceptNotionType[]; related: NotionRelatedContent[] };
129
+ notions?: ReactNode;
133
130
  accessMessage?: string;
134
131
  };
135
132
 
136
- const getArticleContent = (content: any, locale: Locale, contentTransformed?: boolean) => {
133
+ const getArticleContent = (content: any, contentTransformed?: boolean) => {
137
134
  if (contentTransformed) {
138
135
  return content;
139
136
  }
140
137
  switch (typeof content) {
141
- case 'string':
142
- return <ArticleContent content={content} locale={locale} />;
143
138
  case 'function':
144
139
  return content();
145
140
  default:
@@ -157,7 +152,6 @@ export const Article = ({
157
152
  children,
158
153
  competenceGoals,
159
154
  id,
160
- locale,
161
155
  notions,
162
156
  renderMarkdown,
163
157
  accessMessage,
@@ -223,13 +217,9 @@ export const Article = ({
223
217
  </LayoutItem>
224
218
  <LayoutItem layout="center">
225
219
  {notions && showExplainNotions && (
226
- <ArticleNotions
227
- notions={notions.list}
228
- relatedContent={notions.related}
229
- buttonOffsetRight={articlePositionRight}
230
- />
220
+ <ArticleNotions buttonOffsetRight={articlePositionRight}>{notions}</ArticleNotions>
231
221
  )}
232
- {getArticleContent(content, locale, contentTransformed)}
222
+ {getArticleContent(content, contentTransformed)}
233
223
  </LayoutItem>
234
224
 
235
225
  <LayoutItem layout="center">
@@ -6,13 +6,12 @@
6
6
  *
7
7
  */
8
8
 
9
+ import { ReactNode } from 'react';
9
10
  import styled from '@emotion/styled';
10
11
  import { useTranslation } from 'react-i18next';
11
12
  import { ModalHeader, ModalCloseButton, ModalBody, Modal, ModalTitle, ModalTrigger, ModalContent } from '@ndla/modal';
12
13
  import { mq, breakpoints, fonts, colors } from '@ndla/core';
13
14
  import { Explanation, NotionFlip } from '@ndla/icons/common';
14
- import { ConceptNotion } from '../Notion';
15
- import { ConceptNotionType } from '../Notion/ConceptNotion';
16
15
 
17
16
  const ArticleNotionsContainer = styled.div`
18
17
  margin-bottom: 26px;
@@ -81,14 +80,6 @@ const NotionsTrigger = styled.div`
81
80
  const ModalHeadingContainer = styled.div`
82
81
  display: flex;
83
82
  align-items: center;
84
- padding-left: 1rem;
85
- padding-right: 1rem;
86
- margin-bottom: 2rem;
87
-
88
- ${mq.range({ from: breakpoints.tablet })} {
89
- padding-left: 3.5rem;
90
- padding-right: 3.5rem;
91
- }
92
83
 
93
84
  svg {
94
85
  display: block;
@@ -103,65 +94,20 @@ const ModalHeadingContainer = styled.div`
103
94
  margin: 0 1.5rem 0 -0.5rem;
104
95
  }
105
96
  }
106
-
107
- h1 {
108
- margin: 0;
109
- }
110
- `;
111
-
112
- const NotionsContainer = styled.div`
113
- padding: 0 20px;
114
-
115
- ${mq.range({ from: breakpoints.tablet })} {
116
- padding: 0 3.5rem;
117
- }
118
97
  `;
119
98
 
120
- const RelatedContentContainer = styled.ul`
99
+ const StyledModalHeader = styled(ModalHeader)`
121
100
  display: flex;
122
- flex-wrap: wrap;
123
- list-style: none;
124
- margin: 0 0 2rem;
125
- padding: 0 20px;
126
-
127
- ${mq.range({ from: breakpoints.tablet })} {
128
- padding: 0 3.5rem;
129
- }
130
-
131
- &:not(:last-child) {
132
- margin-bottom: 1.5rem;
133
- }
134
-
135
- > li {
136
- width: 100%;
137
-
138
- &:not(:nth-child(2n)) {
139
- margin-right: 0;
140
- }
141
-
142
- ${mq.range({ from: breakpoints.tablet })} {
143
- width: calc(100% * (1 / 3));
144
-
145
- &:not(:nth-child(2n)) {
146
- margin-right: 0.5rem;
147
- }
148
- }
149
- }
101
+ justify-content: space-between;
102
+ align-items: center;
150
103
  `;
151
104
 
152
- export type NotionRelatedContent = {
153
- url: string;
154
- label: string;
155
- };
156
-
157
105
  type ArticleNotionsProps = {
158
- notions: ConceptNotionType[];
159
- relatedContent?: NotionRelatedContent[];
106
+ children?: ReactNode;
160
107
  buttonOffsetRight: number;
161
- type?: 'image' | 'video' | 'h5p' | 'iframe' | 'external';
162
108
  };
163
109
 
164
- export const ArticleNotions = ({ notions, relatedContent = [], buttonOffsetRight, type }: ArticleNotionsProps) => {
110
+ export const ArticleNotions = ({ buttonOffsetRight, children }: ArticleNotionsProps) => {
165
111
  const { t } = useTranslation();
166
112
  const leftOffset = `${buttonOffsetRight - 32}px`;
167
113
 
@@ -176,29 +122,14 @@ export const ArticleNotions = ({ notions, relatedContent = [], buttonOffsetRight
176
122
  </NotionsTrigger>
177
123
  </ModalTrigger>
178
124
  <ModalContent size="large">
179
- <ModalHeader className="no-padding">
125
+ <StyledModalHeader>
180
126
  <ModalHeadingContainer>
181
127
  <Explanation />
182
128
  <ModalTitle>{t('article.notionsPrompt')}</ModalTitle>
183
129
  </ModalHeadingContainer>
184
130
  <ModalCloseButton />
185
- </ModalHeader>
186
- <ModalBody modifier="notions-modal-body no-padding">
187
- <NotionsContainer>
188
- {notions.map((notion) => (
189
- <ConceptNotion key={notion.id} concept={notion} type={type} />
190
- ))}
191
- </NotionsContainer>
192
- {relatedContent.length > 0 && (
193
- <RelatedContentContainer>
194
- {relatedContent.map((content, i) => (
195
- <li key={`notion-related-item-${i + 1}`}>
196
- <a href={content.url}>{content.label}</a>
197
- </li>
198
- ))}
199
- </RelatedContentContainer>
200
- )}
201
- </ModalBody>
131
+ </StyledModalHeader>
132
+ <ModalBody modifier="notions-modal-body no-padding">{children}</ModalBody>
202
133
  </ModalContent>
203
134
  </Modal>
204
135
  </ArticleNotionsContainer>
@@ -8,17 +8,8 @@
8
8
 
9
9
  import Article, { ArticleTitle, ArticleIntroduction, ArticleWrapper } from './Article';
10
10
  import ArticleByline from './ArticleByline';
11
- import ArticleContent from './ArticleContent';
12
11
  import ArticleFootNotes from './ArticleFootNotes';
13
12
  import ArticleHeaderWrapper from './ArticleHeaderWrapper';
14
13
 
15
- export {
16
- ArticleByline,
17
- ArticleContent,
18
- ArticleFootNotes,
19
- ArticleHeaderWrapper,
20
- ArticleTitle,
21
- ArticleIntroduction,
22
- ArticleWrapper,
23
- };
14
+ export { ArticleByline, ArticleFootNotes, ArticleHeaderWrapper, ArticleTitle, ArticleIntroduction, ArticleWrapper };
24
15
  export default Article;
@@ -33,6 +33,9 @@ const BlockWrapper = styled.div`
33
33
  border-radius: ${misc.borderRadius};
34
34
  border: 1px solid ${colors.brand.lighter};
35
35
  max-width: 348px;
36
+ & ~ & {
37
+ margin-top: ${spacing.medium};
38
+ }
36
39
  ${mq.range({ from: breakpoints.tabletWide })} {
37
40
  max-width: 773px;
38
41
  flex-direction: row;
@@ -73,14 +73,14 @@ const getSizes = (size?: string, align?: string) => {
73
73
  return '(min-width: 1024px) 1024px, 100vw';
74
74
  };
75
75
 
76
- const getFocalPoint = (data: ImageEmbedData) => {
76
+ export const getFocalPoint = (data: ImageEmbedData) => {
77
77
  if (typeof data.focalX === 'number' && typeof data.focalY === 'number') {
78
78
  return { x: data.focalX, y: data.focalY };
79
79
  }
80
80
  return undefined;
81
81
  };
82
82
 
83
- const getCrop = (data: ImageEmbedData) => {
83
+ export const getCrop = (data: ImageEmbedData) => {
84
84
  if (
85
85
  typeof data.lowerRightX === 'number' &&
86
86
  typeof data.lowerRightY === 'number' &&
package/src/Grid/Grid.tsx CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  import styled from '@emotion/styled';
10
10
  import { breakpoints, colors, misc, mq, spacing } from '@ndla/core';
11
- import { ReactNode } from 'react';
11
+ import { Children, ReactNode } from 'react';
12
12
 
13
13
  export interface GridProps {
14
14
  columns: '2' | '4' | '2x2';
@@ -87,4 +87,12 @@ const Grid = ({ columns, border, children, background, size, ...rest }: GridProp
87
87
  );
88
88
  };
89
89
 
90
+ export const GridParallaxItem = styled.div`
91
+ position: relative;
92
+ > div {
93
+ top: 0px;
94
+ position: sticky;
95
+ }
96
+ `;
97
+
90
98
  export default Grid;
package/src/Grid/index.ts CHANGED
@@ -6,5 +6,5 @@
6
6
  *
7
7
  */
8
8
 
9
- export { default as Grid } from './Grid';
9
+ export { default as Grid, GridParallaxItem } from './Grid';
10
10
  export type { GridProps as GridType } from './Grid';
@@ -6,9 +6,10 @@
6
6
  *
7
7
  */
8
8
 
9
- import { Meta, StoryObj } from '@storybook/react';
9
+ import { Meta, StoryObj, StoryFn } from '@storybook/react';
10
10
  import LinkBlock from './LinkBlock';
11
11
  import { defaultParameters } from '../../../../stories/defaults';
12
+ import { LinkBlockSection } from '.';
12
13
 
13
14
  export default {
14
15
  title: 'Components/Link Block',
@@ -20,11 +21,20 @@ export default {
20
21
  args: {},
21
22
  } as Meta<typeof LinkBlock>;
22
23
 
23
- export const AnnouncementExample: StoryObj<typeof LinkBlock> = {
24
- args: {
25
- title: 'Redaksjonell medarbeider i faget spansk 2',
26
- language: 'nb',
27
- date: '05. mars 2023',
28
- url: '',
29
- },
24
+ const args = {
25
+ title: 'Redaksjonell medarbeider i faget spansk 2',
26
+ language: 'nb',
27
+ date: '05. mars 2023',
28
+ url: '',
30
29
  };
30
+
31
+ export const AnnouncementExample: StoryObj<typeof LinkBlock> = { args };
32
+
33
+ export const LinkList: StoryFn<typeof LinkBlock> = () => (
34
+ <LinkBlockSection>
35
+ <LinkBlock {...args} />
36
+ <LinkBlock {...args} />
37
+ <LinkBlock {...args} />
38
+ <LinkBlock {...args} />
39
+ </LinkBlockSection>
40
+ );
@@ -6,19 +6,16 @@
6
6
  *
7
7
  */
8
8
 
9
+ import { format } from 'date-fns';
10
+ import { enGB, nb, nn } from 'date-fns/locale';
9
11
  import styled from '@emotion/styled';
10
12
  import SafeLink from '@ndla/safelink';
11
13
  import { Forward, CalendarEd } from '@ndla/icons/common';
12
14
  import { breakpoints, colors, spacing, mq } from '@ndla/core';
15
+ import { LinkBlockEmbedData } from '@ndla/types-embed';
16
+ import { useMemo } from 'react';
13
17
  import Heading from '../Typography/Heading';
14
18
 
15
- interface Props {
16
- title: string;
17
- language: string;
18
- date: string;
19
- url: string;
20
- }
21
-
22
19
  const StyledForward = styled(Forward)`
23
20
  margin: 0 ${spacing.nsmall};
24
21
  min-width: ${spacing.normal};
@@ -74,7 +71,13 @@ const StyledCalenderEd = styled(CalendarEd)`
74
71
  color: ${colors.icon.iconBlue};
75
72
  `;
76
73
 
77
- const LinkBlock = ({ title, language, date, url }: Props) => {
74
+ const LinkBlock = ({ title, language, date, url }: Omit<LinkBlockEmbedData, 'resource'>) => {
75
+ const formattedDate = useMemo(() => {
76
+ if (!date) return null;
77
+ const locale = language === 'nb' ? nb : language === 'nn' ? nn : enGB;
78
+ return format(new Date(date), 'dd. LLLL. yyyy', { locale });
79
+ }, [date, language]);
80
+
78
81
  return (
79
82
  <StyledSafeLink to={url}>
80
83
  <InfoWrapper>
@@ -84,7 +87,7 @@ const LinkBlock = ({ title, language, date, url }: Props) => {
84
87
  {date && (
85
88
  <StyledDateContainer>
86
89
  <StyledCalenderEd />
87
- {date}
90
+ {formattedDate}
88
91
  </StyledDateContainer>
89
92
  )}
90
93
  </InfoWrapper>
@@ -0,0 +1,42 @@
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
+ import styled from '@emotion/styled';
10
+ import { spacing } from '@ndla/core';
11
+ import { Children, HTMLAttributes, ReactNode } from 'react';
12
+
13
+ interface Props extends HTMLAttributes<HTMLElement> {
14
+ children: ReactNode;
15
+ }
16
+
17
+ const StyledList = styled.ul`
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: ${spacing.small};
21
+ list-style: none;
22
+ padding: 0px;
23
+ margin: 0px;
24
+ li {
25
+ padding: 0px;
26
+ margin: 0px;
27
+ }
28
+ `;
29
+
30
+ const LinkBlockSection = ({ children, ...rest }: Props) => {
31
+ return (
32
+ <nav {...rest}>
33
+ <StyledList>
34
+ {Children.map(children, (child) => (
35
+ <li>{child}</li>
36
+ ))}
37
+ </StyledList>
38
+ </nav>
39
+ );
40
+ };
41
+
42
+ export default LinkBlockSection;
@@ -7,3 +7,4 @@
7
7
  */
8
8
 
9
9
  export { default as LinkBlock } from './LinkBlock';
10
+ export { default as LinkBlockSection } from './LinkBlockSection';