@ndla/ui 13.2.1 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/es/Article/Article.js +22 -3
  2. package/es/Article/ArticleFavoritesButton.js +38 -0
  3. package/es/Article/index.js +2 -1
  4. package/es/Breadcrumb/ActionBreadcrumb.js +57 -0
  5. package/es/Breadcrumb/index.js +1 -0
  6. package/es/Footer/FooterAuth.js +15 -22
  7. package/es/InfoBlock/InfoBlock.js +55 -0
  8. package/es/InfoBlock/index.js +1 -0
  9. package/es/LearningPaths/LearningPathMenu.js +3 -4
  10. package/es/Masthead/MastheadAuthModal.js +2 -2
  11. package/es/MyNdla/Navigation/VerticalNavigation.js +51 -0
  12. package/es/MyNdla/Navigation/index.js +2 -0
  13. package/es/MyNdla/Resource/Folder.js +86 -0
  14. package/{lib/MyNdla/ResourceDash/ResourcesView.d.ts → es/MyNdla/Resource/index.js} +2 -3
  15. package/es/MyNdla/index.js +3 -4
  16. package/es/Notion/ConceptNotion.js +2 -1
  17. package/es/Notion/FigureNotion.js +13 -9
  18. package/es/Notion/NotionVisualElement.js +3 -2
  19. package/es/Resource/BlockResource.js +73 -0
  20. package/es/Resource/ListResource.js +66 -0
  21. package/es/Resource/index.js +10 -0
  22. package/es/Resource/resourceComponents.js +97 -0
  23. package/es/ResourceGroup/ResourceGroup.js +7 -5
  24. package/es/ResourceGroup/ResourceItem.js +28 -30
  25. package/es/ResourceGroup/ResourceList.js +18 -6
  26. package/es/Search/ActiveFilters.js +6 -7
  27. package/es/Search/ContentTypeResult.js +6 -8
  28. package/es/SearchTypeResult/ActiveFilters.js +6 -10
  29. package/es/SearchTypeResult/SearchViewType.js +5 -5
  30. package/es/SnackBar/SnackBar.js +117 -0
  31. package/es/SnackBar/index.js +9 -0
  32. package/es/TagSelector/SuggestionInput.js +240 -0
  33. package/es/TagSelector/Suggestions.js +93 -0
  34. package/es/TagSelector/TagSelector.js +137 -0
  35. package/es/TagSelector/index.js +9 -0
  36. package/es/TopicIntroductionList/TopicIntroduction.js +2 -4
  37. package/es/TopicIntroductionList/TopicShortcutItem.js +1 -3
  38. package/es/TreeStructure/FolderItem.js +130 -0
  39. package/es/TreeStructure/FolderItems.js +123 -0
  40. package/es/TreeStructure/FolderNameInput.js +112 -0
  41. package/es/TreeStructure/TreeStructure.js +254 -0
  42. package/es/TreeStructure/TreeStructure.types.js +0 -0
  43. package/es/TreeStructure/TreeStructureWrapper.js +13 -0
  44. package/es/TreeStructure/helperFunctions.js +92 -0
  45. package/es/TreeStructure/index.js +9 -0
  46. package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +182 -0
  47. package/es/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +0 -0
  48. package/es/User/AuthModal.js +15 -24
  49. package/es/User/UserInfo.js +70 -0
  50. package/es/User/apiTypes.js +0 -0
  51. package/es/User/index.js +2 -0
  52. package/es/User/parseUserObject.js +102 -0
  53. package/es/all.css +90 -0
  54. package/es/index.js +9 -3
  55. package/es/locale/messages-en.js +75 -8
  56. package/es/locale/messages-nb.js +74 -7
  57. package/es/locale/messages-nn.js +74 -7
  58. package/es/locale/messages-se.js +74 -7
  59. package/es/locale/messages-sma.js +74 -7
  60. package/lib/Article/Article.d.ts +3 -1
  61. package/lib/Article/Article.js +43 -23
  62. package/lib/Article/ArticleFavoritesButton.d.ts +15 -0
  63. package/lib/Article/ArticleFavoritesButton.js +56 -0
  64. package/lib/Article/index.d.ts +2 -1
  65. package/lib/Article/index.js +8 -0
  66. package/lib/Breadcrumb/ActionBreadcrumb.d.ts +16 -0
  67. package/lib/Breadcrumb/ActionBreadcrumb.js +72 -0
  68. package/lib/Breadcrumb/index.d.ts +1 -0
  69. package/lib/Breadcrumb/index.js +8 -0
  70. package/lib/Footer/FooterAuth.d.ts +1 -1
  71. package/lib/Footer/FooterAuth.js +17 -17
  72. package/lib/InfoBlock/InfoBlock.d.ts +8 -0
  73. package/lib/InfoBlock/InfoBlock.js +58 -0
  74. package/lib/InfoBlock/index.d.ts +1 -0
  75. package/lib/InfoBlock/index.js +13 -0
  76. package/lib/LearningPaths/LearningPathMenu.js +3 -4
  77. package/lib/Masthead/MastheadAuthModal.d.ts +3 -3
  78. package/lib/Masthead/MastheadAuthModal.js +3 -3
  79. package/lib/MyNdla/Navigation/VerticalNavigation.d.ts +10 -0
  80. package/lib/MyNdla/Navigation/VerticalNavigation.js +61 -0
  81. package/lib/MyNdla/Navigation/index.d.ts +2 -0
  82. package/lib/MyNdla/Navigation/index.js +15 -0
  83. package/lib/MyNdla/Resource/Folder.d.ts +20 -0
  84. package/lib/MyNdla/Resource/Folder.js +100 -0
  85. package/lib/MyNdla/Resource/index.d.ts +9 -0
  86. package/lib/MyNdla/Resource/index.js +15 -0
  87. package/lib/MyNdla/index.d.ts +3 -4
  88. package/lib/MyNdla/index.js +9 -11
  89. package/lib/Notion/ConceptNotion.js +2 -1
  90. package/lib/Notion/FigureNotion.d.ts +1 -1
  91. package/lib/Notion/FigureNotion.js +12 -8
  92. package/lib/Notion/NotionVisualElement.js +3 -2
  93. package/lib/Resource/BlockResource.d.ts +20 -0
  94. package/lib/Resource/BlockResource.js +84 -0
  95. package/lib/Resource/ListResource.d.ts +20 -0
  96. package/lib/Resource/ListResource.js +78 -0
  97. package/lib/Resource/index.d.ts +11 -0
  98. package/lib/Resource/index.js +29 -0
  99. package/lib/Resource/resourceComponents.d.ts +24 -0
  100. package/lib/Resource/resourceComponents.js +106 -0
  101. package/lib/ResourceGroup/ResourceGroup.d.ts +2 -1
  102. package/lib/ResourceGroup/ResourceGroup.js +7 -5
  103. package/lib/ResourceGroup/ResourceItem.d.ts +5 -1
  104. package/lib/ResourceGroup/ResourceItem.js +29 -30
  105. package/lib/ResourceGroup/ResourceList.d.ts +3 -1
  106. package/lib/ResourceGroup/ResourceList.js +18 -6
  107. package/lib/Search/ActiveFilters.js +6 -7
  108. package/lib/Search/ContentTypeResult.js +6 -8
  109. package/lib/SearchTypeResult/ActiveFilters.js +6 -10
  110. package/lib/SearchTypeResult/SearchViewType.js +5 -5
  111. package/lib/SnackBar/SnackBar.d.ts +23 -0
  112. package/lib/SnackBar/SnackBar.js +127 -0
  113. package/lib/SnackBar/index.d.ts +10 -0
  114. package/lib/SnackBar/index.js +15 -0
  115. package/lib/TagSelector/SuggestionInput.d.ts +19 -0
  116. package/lib/TagSelector/SuggestionInput.js +255 -0
  117. package/lib/TagSelector/Suggestions.d.ts +12 -0
  118. package/lib/TagSelector/Suggestions.js +96 -0
  119. package/lib/TagSelector/TagSelector.d.ts +16 -0
  120. package/lib/TagSelector/TagSelector.js +150 -0
  121. package/lib/TagSelector/index.d.ts +10 -0
  122. package/lib/TagSelector/index.js +19 -0
  123. package/lib/TopicIntroductionList/TopicIntroduction.js +2 -4
  124. package/lib/TopicIntroductionList/TopicShortcutItem.js +1 -3
  125. package/lib/TreeStructure/FolderItem.d.ts +27 -0
  126. package/lib/TreeStructure/FolderItem.js +140 -0
  127. package/lib/TreeStructure/FolderItems.d.ts +11 -0
  128. package/lib/TreeStructure/FolderItems.js +130 -0
  129. package/lib/TreeStructure/FolderNameInput.d.ts +15 -0
  130. package/lib/TreeStructure/FolderNameInput.js +125 -0
  131. package/lib/TreeStructure/TreeStructure.d.ts +12 -0
  132. package/lib/TreeStructure/TreeStructure.js +273 -0
  133. package/lib/TreeStructure/TreeStructure.types.d.ts +63 -0
  134. package/lib/TreeStructure/TreeStructure.types.js +1 -0
  135. package/lib/TreeStructure/TreeStructureWrapper.d.ts +12 -0
  136. package/lib/TreeStructure/TreeStructureWrapper.js +24 -0
  137. package/lib/TreeStructure/helperFunctions.d.ts +5 -0
  138. package/lib/TreeStructure/helperFunctions.js +103 -0
  139. package/lib/TreeStructure/index.d.ts +10 -0
  140. package/lib/TreeStructure/index.js +15 -0
  141. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.d.ts +11 -0
  142. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.js +186 -0
  143. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.d.ts +26 -0
  144. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +1 -0
  145. package/lib/User/AuthModal.d.ts +3 -3
  146. package/lib/User/AuthModal.js +16 -23
  147. package/lib/User/UserInfo.d.ts +13 -0
  148. package/lib/User/UserInfo.js +84 -0
  149. package/lib/User/apiTypes.d.ts +61 -0
  150. package/lib/User/apiTypes.js +1 -0
  151. package/lib/User/index.d.ts +4 -0
  152. package/lib/User/index.js +8 -0
  153. package/lib/User/parseUserObject.d.ts +32 -0
  154. package/lib/User/parseUserObject.js +105 -0
  155. package/lib/all.css +90 -0
  156. package/lib/index.d.ts +14 -3
  157. package/lib/index.js +76 -10
  158. package/lib/locale/messages-en.d.ts +67 -0
  159. package/lib/locale/messages-en.js +75 -8
  160. package/lib/locale/messages-nb.d.ts +67 -0
  161. package/lib/locale/messages-nb.js +74 -7
  162. package/lib/locale/messages-nn.d.ts +67 -0
  163. package/lib/locale/messages-nn.js +74 -7
  164. package/lib/locale/messages-se.d.ts +67 -0
  165. package/lib/locale/messages-se.js +74 -7
  166. package/lib/locale/messages-sma.d.ts +67 -0
  167. package/lib/locale/messages-sma.js +74 -7
  168. package/lib/types.d.ts +1 -1
  169. package/package.json +11 -11
  170. package/src/Article/Article.tsx +31 -0
  171. package/src/Article/ArticleFavoritesButton.tsx +40 -0
  172. package/src/Article/index.ts +2 -0
  173. package/src/Breadcrumb/ActionBreadcrumb.tsx +68 -0
  174. package/src/Breadcrumb/index.ts +2 -0
  175. package/src/Footer/FooterAuth.tsx +7 -9
  176. package/src/InfoBlock/InfoBlock.tsx +61 -0
  177. package/src/InfoBlock/index.ts +1 -0
  178. package/src/LearningPaths/LearningPathMenu.tsx +1 -1
  179. package/src/Masthead/MastheadAuthModal.tsx +4 -5
  180. package/src/MyNdla/Navigation/VerticalNavigation.tsx +93 -0
  181. package/src/MyNdla/Navigation/index.ts +2 -0
  182. package/src/MyNdla/Resource/Folder.tsx +143 -0
  183. package/src/MyNdla/Resource/index.ts +10 -0
  184. package/src/MyNdla/index.ts +3 -5
  185. package/src/Notion/ConceptNotion.tsx +1 -0
  186. package/src/Notion/FigureNotion.tsx +12 -5
  187. package/src/Notion/NotionVisualElement.tsx +1 -1
  188. package/src/Resource/BlockResource.tsx +101 -0
  189. package/src/Resource/ListResource.tsx +111 -0
  190. package/src/Resource/index.ts +12 -0
  191. package/src/Resource/resourceComponents.tsx +143 -0
  192. package/src/ResourceGroup/ResourceGroup.tsx +3 -0
  193. package/src/ResourceGroup/ResourceItem.tsx +20 -3
  194. package/src/ResourceGroup/ResourceList.tsx +16 -3
  195. package/src/Search/ActiveFilters.jsx +0 -1
  196. package/src/Search/ContentTypeResult.tsx +8 -9
  197. package/src/SearchTypeResult/ActiveFilters.tsx +1 -3
  198. package/src/SearchTypeResult/SearchViewType.tsx +1 -1
  199. package/src/SnackBar/SnackBar.tsx +183 -0
  200. package/src/SnackBar/index.ts +13 -0
  201. package/src/TagSelector/SuggestionInput.tsx +230 -0
  202. package/src/TagSelector/Suggestions.tsx +125 -0
  203. package/src/TagSelector/TagSelector.tsx +111 -0
  204. package/src/TagSelector/index.ts +13 -0
  205. package/src/TopicIntroductionList/TopicIntroduction.tsx +2 -2
  206. package/src/TopicIntroductionList/TopicShortcutItem.tsx +1 -5
  207. package/src/TreeStructure/FolderItem.tsx +160 -0
  208. package/src/TreeStructure/FolderItems.tsx +109 -0
  209. package/src/TreeStructure/FolderNameInput.tsx +109 -0
  210. package/src/TreeStructure/TreeStructure.tsx +184 -0
  211. package/src/TreeStructure/TreeStructure.types.ts +69 -0
  212. package/src/TreeStructure/TreeStructureWrapper.tsx +34 -0
  213. package/src/TreeStructure/helperFunctions.ts +52 -0
  214. package/src/TreeStructure/index.ts +11 -0
  215. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.ts +161 -0
  216. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.types.ts +28 -0
  217. package/src/User/AuthModal.tsx +5 -26
  218. package/src/User/UserInfo.tsx +80 -0
  219. package/src/User/__tests__/parseUserObject-test.ts +315 -0
  220. package/src/User/apiTypes.ts +74 -0
  221. package/src/User/index.ts +4 -0
  222. package/src/User/parseUserObject.ts +83 -0
  223. package/src/all.scss +2 -0
  224. package/src/index.ts +15 -4
  225. package/src/locale/messages-en.ts +69 -7
  226. package/src/locale/messages-nb.ts +68 -6
  227. package/src/locale/messages-nn.ts +68 -6
  228. package/src/locale/messages-se.ts +68 -6
  229. package/src/locale/messages-sma.ts +68 -6
  230. package/src/types.ts +1 -1
  231. package/es/MyNdla/ResourceDash/Breadcrumbs.js +0 -22
  232. package/es/MyNdla/ResourceDash/ResourceElement.js +0 -27
  233. package/es/MyNdla/ResourceDash/ResourcesView.js +0 -43
  234. package/es/MyNdla/ResourceDash/index.js +0 -4
  235. package/lib/MyNdla/ResourceDash/Breadcrumbs.d.ts +0 -15
  236. package/lib/MyNdla/ResourceDash/Breadcrumbs.js +0 -35
  237. package/lib/MyNdla/ResourceDash/ResourceElement.d.ts +0 -18
  238. package/lib/MyNdla/ResourceDash/ResourceElement.js +0 -38
  239. package/lib/MyNdla/ResourceDash/ResourcesView.js +0 -57
  240. package/lib/MyNdla/ResourceDash/index.d.ts +0 -4
  241. package/lib/MyNdla/ResourceDash/index.js +0 -31
  242. package/src/MyNdla/ResourceDash/Breadcrumbs.tsx +0 -31
  243. package/src/MyNdla/ResourceDash/ResourceElement.tsx +0 -50
  244. package/src/MyNdla/ResourceDash/ResourcesView.tsx +0 -42
  245. package/src/MyNdla/ResourceDash/index.ts +0 -5
@@ -7,7 +7,7 @@
7
7
 
8
8
  import styled from '@emotion/styled';
9
9
  import { colors, spacing } from '@ndla/core';
10
- import { getLicenseByAbbreviation, getLicenseCredits } from '@ndla/licenses';
10
+ import { getGroupedContributorDescriptionList, getLicenseByAbbreviation, getLicenseCredits } from '@ndla/licenses';
11
11
  import React, { ReactNode } from 'react';
12
12
  import { useTranslation } from 'react-i18next';
13
13
  import { Figure, FigureCaption, FigureLicenseDialog, FigureType } from '../Figure';
@@ -23,7 +23,7 @@ interface Props {
23
23
  figureId: string;
24
24
  children: ReactNode | ((params: { typeClass: string }) => ReactNode);
25
25
  id: string;
26
- title?: string;
26
+ title: string;
27
27
  copyright?: Partial<Copyright>;
28
28
  licenseString: string;
29
29
  type: 'video' | 'h5p' | 'image' | 'concept' | 'other';
@@ -47,10 +47,16 @@ const FigureNotion = ({
47
47
  }: Props) => {
48
48
  const { t, i18n } = useTranslation();
49
49
  const license = getLicenseByAbbreviation(licenseString, i18n.language);
50
- const { creators, rightsholders, processors } = getLicenseCredits(copyright);
50
+ const licenseCredits = getLicenseCredits(copyright);
51
+ const { creators, rightsholders, processors } = licenseCredits;
51
52
 
52
53
  const authors = creators.length || rightsholders.length ? [...creators, ...rightsholders] : [...processors];
53
54
 
55
+ const groupedAuthors = getGroupedContributorDescriptionList(licenseCredits, i18n.language).map((item) => ({
56
+ name: item.description,
57
+ type: item.label,
58
+ }));
59
+
54
60
  return (
55
61
  <Figure resizeIframe={resizeIframe} id={figureId} type={figureType || 'full-column'}>
56
62
  {({ typeClass }) => (
@@ -67,7 +73,7 @@ const FigureNotion = ({
67
73
  hideIconsAndAuthors={hideIconsAndAuthors}>
68
74
  <FigureLicenseDialog
69
75
  id={id}
70
- authors={authors}
76
+ authors={groupedAuthors}
71
77
  locale={i18n.language}
72
78
  title={title}
73
79
  origin={copyright?.origin}
@@ -78,7 +84,8 @@ const FigureNotion = ({
78
84
  source: t('source'),
79
85
  learnAboutLicenses: t('license.learnMore'),
80
86
  title: t('title'),
81
- }}></FigureLicenseDialog>
87
+ }}
88
+ />
82
89
  </FigureCaption>
83
90
  ) : (
84
91
  <BottomBorder />
@@ -68,7 +68,7 @@ const NotionVisualElement = ({ visualElement, id, figureId }: Props) => {
68
68
  {visualElement.image?.src ? (
69
69
  <img src={visualElement.image?.src} alt={visualElement.image.alt} />
70
70
  ) : (
71
- <StyledIframe type={type} src={visualElement.url} title={visualElement.title} />
71
+ <StyledIframe allowFullScreen type={type} src={visualElement.url} title={visualElement.title} />
72
72
  )}
73
73
  </FigureNotion>
74
74
  );
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Copyright (c) 2022-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 React, { ReactNode } from 'react';
11
+ import SafeLink from '@ndla/safelink';
12
+ import { colors, fonts, spacing } from '@ndla/core';
13
+ import Image from '../Image';
14
+ import { CompressTagsLength, ResourceImageProps, ResourceTitle, Row, TopicList } from './resourceComponents';
15
+
16
+ interface BlockResourceProps {
17
+ link: string;
18
+ title: string;
19
+ resourceImage: ResourceImageProps;
20
+ topics: string[];
21
+ tags?: string[];
22
+ description?: string;
23
+ actionMenu?: ReactNode;
24
+ }
25
+
26
+ const BlockElementWrapper = styled(SafeLink)`
27
+ display: flex;
28
+ text-decoration: none;
29
+ box-shadow: none;
30
+ flex-direction: column;
31
+ max-width: 300px;
32
+ max-height: 240px;
33
+ border: 1px solid ${colors.brand.light};
34
+ border-radius: 2px;
35
+ color: ${colors.brand.greyDark};
36
+ `;
37
+
38
+ const BlockDescription = styled.p`
39
+ display: -webkit-box;
40
+ line-clamp: 2;
41
+ ${fonts.sizes(16)};
42
+ height: 0em;
43
+ margin: 0;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ transition: height 0.2s ease-out;
47
+ ${() => BlockElementWrapper}:hover & {
48
+ // Unfortunate css needed for multi-line text overflow ellipsis.
49
+ height: 3.1em;
50
+ -webkit-line-clamp: 2;
51
+ line-clamp: 2;
52
+ -webkit-box-orient: vertical;
53
+ }
54
+ `;
55
+
56
+ const RightRow = styled(Row)`
57
+ justify-content: flex-end;
58
+ `;
59
+
60
+ const BlockInfoWrapper = styled.div`
61
+ display: flex;
62
+ flex-direction: column;
63
+ padding: ${spacing.small};
64
+ gap: ${spacing.xxsmall};
65
+ `;
66
+
67
+ const ImageWrapper = styled.div`
68
+ display: flex;
69
+ width: 100%;
70
+ overflow: hidden;
71
+ align-items: center;
72
+ div {
73
+ min-width: 100%;
74
+ }
75
+ img {
76
+ min-width: 100%;
77
+ }
78
+ `;
79
+
80
+ const BlockResource = ({ link, title, tags, resourceImage, topics, description, actionMenu }: BlockResourceProps) => {
81
+ return (
82
+ <BlockElementWrapper to={link}>
83
+ <ImageWrapper>
84
+ <Image alt={resourceImage.alt} src={resourceImage.src} />
85
+ </ImageWrapper>
86
+ <BlockInfoWrapper>
87
+ <div>
88
+ <ResourceTitle>{title}</ResourceTitle>
89
+ </div>
90
+ <TopicList topics={topics} />
91
+ <BlockDescription>{description}</BlockDescription>
92
+ <RightRow>
93
+ {tags && CompressTagsLength(tags)}
94
+ {actionMenu}
95
+ </RightRow>
96
+ </BlockInfoWrapper>
97
+ </BlockElementWrapper>
98
+ );
99
+ };
100
+
101
+ export default BlockResource;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Copyright (c) 2022-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 React, { ReactNode } from 'react';
11
+ import SafeLink from '@ndla/safelink';
12
+ import { fonts, spacing, colors } from '@ndla/core';
13
+ import Image from '../Image';
14
+ import { CompressTagsLength, ResourceImageProps, ResourceTitle, Row, TopicList } from './resourceComponents';
15
+
16
+ const ResourceDescription = styled.p`
17
+ line-clamp: 2;
18
+ line-height: 1em;
19
+ height: 3.1em;
20
+ margin: 0;
21
+ overflow: hidden;
22
+ ${fonts.sizes(16)};
23
+ text-overflow: ellipsis;
24
+ // Unfortunate css needed for multi-line text overflow ellipsis.
25
+ display: -webkit-box;
26
+ -webkit-line-clamp: 2;
27
+ line-clamp: 2;
28
+ -webkit-box-orient: vertical;
29
+ `;
30
+
31
+ const ResourceWrapper = styled(SafeLink)`
32
+ display: grid;
33
+ grid-template-columns: auto 1fr;
34
+ text-decoration: none;
35
+ box-shadow: none;
36
+ padding: ${spacing.small};
37
+ border: 1px solid ${colors.brand.light};
38
+ border-radius: 2px;
39
+ color: ${colors.brand.greyDark};
40
+ gap: ${spacing.small};
41
+ &:hover {
42
+ box-shadow: 1px 1px 6px 2px rgba(9, 55, 101, 0.08);
43
+ transition-duration: 0.2s;
44
+ ${ResourceTitle} {
45
+ color: ${colors.brand.primary};
46
+ text-decoration: underline;
47
+ }
48
+ a {
49
+ display: flex;
50
+ align-items: center;
51
+ }
52
+ }
53
+ `;
54
+
55
+ const ResourceInfoWrapper = styled.div`
56
+ flex: 1;
57
+ display: flex;
58
+ flex-direction: column;
59
+ max-width: 100%;
60
+ overflow: hidden;
61
+ `;
62
+
63
+ interface StyledImageProps {
64
+ imageSize: 'normal' | 'compact';
65
+ }
66
+
67
+ const StyledImage = styled(Image)<StyledImageProps>`
68
+ display: flex;
69
+ border-radius: 2px;
70
+ object-fit: cover;
71
+ width: ${(p) => (p.imageSize === 'normal' ? '136px' : '56px')};
72
+ min-width: ${(p) => (p.imageSize === 'normal' ? '136px' : '56px')};
73
+ height: ${(p) => (p.imageSize === 'normal' ? '96px' : '40px')};
74
+ `;
75
+
76
+ export interface ListResourceProps {
77
+ link: string;
78
+ title: string;
79
+ resourceImage: ResourceImageProps;
80
+ topics: string[];
81
+ tags?: string[];
82
+ description?: string;
83
+ actionMenu?: ReactNode;
84
+ }
85
+
86
+ const ListResource = ({ link, title, tags, resourceImage, topics, description, actionMenu }: ListResourceProps) => {
87
+ const showDescription = description !== undefined;
88
+
89
+ return (
90
+ <ResourceWrapper to={link}>
91
+ <StyledImage alt={resourceImage.alt} src={resourceImage.src} imageSize={showDescription ? 'normal' : 'compact'} />
92
+ <ResourceInfoWrapper>
93
+ <Row>
94
+ <ResourceTitle>{title}</ResourceTitle>
95
+ {tags && CompressTagsLength(tags)}
96
+ {actionMenu}
97
+ </Row>
98
+ <Row>
99
+ <TopicList topics={topics} />
100
+ </Row>
101
+ {showDescription && (
102
+ <Row>
103
+ <ResourceDescription>{description}</ResourceDescription>
104
+ </Row>
105
+ )}
106
+ </ResourceInfoWrapper>
107
+ </ResourceWrapper>
108
+ );
109
+ };
110
+
111
+ export default ListResource;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright (c) 2022-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 { ListResourceProps } from './ListResource';
10
+ export type { ListResourceProps };
11
+ export { default as ListResource } from './ListResource';
12
+ export { default as BlockResource } from './BlockResource';
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Copyright (c) 2022-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 React from 'react';
10
+ import styled from '@emotion/styled';
11
+ import { fonts, colors, spacing } from '@ndla/core';
12
+
13
+ import { MenuButton } from '@ndla/button';
14
+
15
+ export interface ResourceImageProps {
16
+ alt: string;
17
+ src: string;
18
+ }
19
+
20
+ export const ResourceTitle = styled.h2`
21
+ ${fonts.sizes(18)};
22
+ min-width: 50px;
23
+ font-weight: ${fonts.weight.bold};
24
+ margin: 0;
25
+ flex: 1;
26
+ overflow: hidden;
27
+ text-overflow: ellipsis;
28
+ // Unfortunate css needed for multi-line text overflow ellipsis.
29
+ line-height: 1;
30
+ display: -webkit-box;
31
+ -webkit-line-clamp: 1;
32
+ line-clamp: 1;
33
+ -webkit-box-orient: vertical;
34
+ `;
35
+
36
+ const StyledTagList = styled.ul`
37
+ list-style: none;
38
+ display: flex;
39
+ margin: 0;
40
+ padding: 0;
41
+ gap: ${spacing.xsmall};
42
+ overflow: hidden;
43
+ `;
44
+
45
+ const StyledTagListElement = styled.li`
46
+ color: ${colors.brand.grey};
47
+ margin: 0;
48
+ ${fonts.sizes(14)};
49
+ ::before {
50
+ content: '#';
51
+ }
52
+ `;
53
+
54
+ const StyledTopicList = styled.ul`
55
+ list-style: none;
56
+ display: flex;
57
+ margin: 0;
58
+ padding: 0;
59
+ overflow: hidden;
60
+ `;
61
+
62
+ const StyledTopicListElement = styled.li`
63
+ ${fonts.sizes(12)};
64
+ margin: 0;
65
+ line-height: 1.5;
66
+ padding: 0;
67
+ `;
68
+
69
+ const StyledTopicDivider = styled.span`
70
+ margin: 0;
71
+ padding: 0 ${spacing.xxsmall};
72
+ `;
73
+
74
+ export const Row = styled.div`
75
+ display: flex;
76
+ align-items: center;
77
+ gap: ${spacing.xsmall};
78
+ `;
79
+
80
+ const TagCounterWrapper = styled.p`
81
+ color: ${colors.brand.primary};
82
+ box-shadow: none;
83
+ margin: 0;
84
+ font-weight: ${fonts.weight.semibold};
85
+ ${fonts.sizes(16)}
86
+ `;
87
+
88
+ interface TagListProps {
89
+ tags?: string[];
90
+ }
91
+
92
+ export const TagList = ({ tags }: TagListProps) => {
93
+ if (!tags) return null;
94
+ return (
95
+ <StyledTagList>
96
+ {tags.map((tag, i) => (
97
+ <StyledTagListElement key={`tag-${i}`}>{tag}</StyledTagListElement>
98
+ ))}
99
+ </StyledTagList>
100
+ );
101
+ };
102
+
103
+ export const CompressTagsLength = (tags: string[]) => {
104
+ const tagCounter = tags?.length - 3;
105
+ const slicedTags = tags.slice(0, 3);
106
+ const remainingTags = tags.slice(3, tags.length).map((tag) => {
107
+ return {
108
+ text: '#' + tag,
109
+ onClick: () => {},
110
+ };
111
+ });
112
+
113
+ return (
114
+ <>
115
+ <TagList tags={slicedTags} />
116
+ {remainingTags.length > 0 && (
117
+ <MenuButton hideMenuIcon={true} menuItems={remainingTags}>
118
+ <TagCounterWrapper>+ {tagCounter}</TagCounterWrapper>
119
+ </MenuButton>
120
+ )}
121
+ </>
122
+ );
123
+ };
124
+
125
+ interface TopicListProps {
126
+ topics?: string[];
127
+ }
128
+
129
+ export const TopicList = ({ topics }: TopicListProps) => {
130
+ if (!topics) return null;
131
+ return (
132
+ <StyledTopicList>
133
+ {topics.map((topic, i) => (
134
+ <StyledTopicListElement key={topic}>
135
+ <>
136
+ {topic}
137
+ {i !== topics.length - 1 && <StyledTopicDivider>•</StyledTopicDivider>}
138
+ </>
139
+ </StyledTopicListElement>
140
+ ))}
141
+ </StyledTopicList>
142
+ );
143
+ };
@@ -36,6 +36,7 @@ const StyledHeading = styled.h1`
36
36
  type Props = {
37
37
  invertedStyle?: boolean;
38
38
  toggleAdditionalResources: () => void;
39
+ onToggleAddToFavorites: (id: string | number, add: string) => void;
39
40
  };
40
41
 
41
42
  const ResourceGroup = ({
@@ -45,6 +46,7 @@ const ResourceGroup = ({
45
46
  toggleAdditionalResources,
46
47
  contentType,
47
48
  invertedStyle,
49
+ onToggleAddToFavorites,
48
50
  }: Props & ResourceListProps) => (
49
51
  <Wrapper>
50
52
  {title && (
@@ -59,6 +61,7 @@ const ResourceGroup = ({
59
61
  showAdditionalResources={showAdditionalResources}
60
62
  contentType={contentType}
61
63
  resources={resources}
64
+ onToggleAddToFavorites={onToggleAddToFavorites}
62
65
  />
63
66
  ) : null}
64
67
  </Wrapper>
@@ -15,6 +15,7 @@ import SafeLink from '@ndla/safelink';
15
15
  import { Additional, Core, HumanMaleBoard } from '@ndla/icons/common';
16
16
  import { breakpoints, colors, fonts, mq, spacing } from '@ndla/core';
17
17
  import Tooltip from '@ndla/tooltip';
18
+ import { ArticleFavoritesButton } from '../Article';
18
19
  import { Resource } from '../types';
19
20
  import ContentTypeBadge from '../ContentTypeBadge';
20
21
  import * as contentTypes from '../model/ContentType';
@@ -208,6 +209,7 @@ const IconWrapper = styled.div`
208
209
  const TypeWrapper = styled.div`
209
210
  display: flex;
210
211
  align-items: center;
212
+ gap: ${spacing.xsmall};
211
213
  `;
212
214
 
213
215
  const ContentTypeName = styled.span`
@@ -219,15 +221,20 @@ const ContentTypeName = styled.span`
219
221
  `;
220
222
 
221
223
  type Props = {
224
+ id: string;
222
225
  showContentTypeDescription?: boolean;
223
226
  contentTypeName?: string;
224
227
  contentTypeDescription?: string;
225
228
  extraBottomMargin?: boolean;
226
229
  showAdditionalResources?: boolean;
227
230
  access?: 'teacher';
231
+ isFavorite?: boolean;
232
+ onToggleAddToFavorites: (id: string, add: boolean) => void;
233
+ hideAddToFavoriteButton?: boolean;
228
234
  };
229
235
 
230
236
  const ResourceItem = ({
237
+ id,
231
238
  contentTypeName,
232
239
  contentTypeDescription,
233
240
  name,
@@ -238,6 +245,9 @@ const ResourceItem = ({
238
245
  extraBottomMargin,
239
246
  showAdditionalResources,
240
247
  access,
248
+ onToggleAddToFavorites,
249
+ isFavorite,
250
+ hideAddToFavoriteButton,
241
251
  }: Props & Resource) => {
242
252
  const { t } = useTranslation();
243
253
  const hidden = additional ? !showAdditionalResources : false;
@@ -273,24 +283,31 @@ const ResourceItem = ({
273
283
  <TypeWrapper>
274
284
  {contentTypeName && <ContentTypeName>{contentTypeName}</ContentTypeName>}
275
285
  {access && access === 'teacher' && (
276
- <Tooltip tooltip={t('article.access.onlyTeacher')} align="left">
286
+ <Tooltip tooltip={t('article.access.onlyTeacher')}>
277
287
  <HumanMaleBoard className="c-icon--20 u-margin-left-tiny c-topic-resource__list__additional-icons" />
278
288
  </Tooltip>
279
289
  )}
280
290
  {showAdditionalResources && contentTypeDescription && (
281
291
  <>
282
292
  {additional && (
283
- <Tooltip tooltip={contentTypeDescription} align="left">
293
+ <Tooltip tooltip={contentTypeDescription}>
284
294
  <Additional className="c-icon--20 u-margin-left-tiny c-topic-resource__list__additional-icons" />
285
295
  </Tooltip>
286
296
  )}
287
297
  {!additional && (
288
- <Tooltip tooltip={contentTypeDescription} align="left">
298
+ <Tooltip tooltip={contentTypeDescription}>
289
299
  <Core className="c-icon--20 u-margin-left-tiny c-topic-resource__list__additional-icons" />
290
300
  </Tooltip>
291
301
  )}
292
302
  </>
293
303
  )}
304
+ {!hideAddToFavoriteButton && (
305
+ <ArticleFavoritesButton
306
+ isFavorite={isFavorite}
307
+ articleId={id}
308
+ onToggleAddToFavorites={() => onToggleAddToFavorites(id, true)}
309
+ />
310
+ )}
294
311
  </TypeWrapper>
295
312
  </ListElement>
296
313
  );
@@ -48,9 +48,19 @@ export type ResourceListProps = {
48
48
  contentType?: string;
49
49
  title?: string;
50
50
  showAdditionalResources?: boolean;
51
+ onToggleAddToFavorites: (id: string, add: boolean) => void;
52
+ hideAddToFavoriteButton?: boolean;
51
53
  };
52
54
 
53
- const ResourceList = ({ resources, onClick, contentType, title, showAdditionalResources }: ResourceListProps) => {
55
+ const ResourceList = ({
56
+ resources,
57
+ onClick,
58
+ onToggleAddToFavorites,
59
+ contentType,
60
+ title,
61
+ showAdditionalResources,
62
+ hideAddToFavoriteButton,
63
+ }: ResourceListProps) => {
54
64
  const { t } = useTranslation();
55
65
  const renderAdditionalResourceTrigger =
56
66
  !showAdditionalResources &&
@@ -60,11 +70,14 @@ const ResourceList = ({ resources, onClick, contentType, title, showAdditionalRe
60
70
  return (
61
71
  <div>
62
72
  <StyledResourceList showAdditionalResources={showAdditionalResources}>
63
- {resources.map((resource) => (
73
+ {resources.map(({ id, ...resource }) => (
64
74
  <ResourceItem
65
- key={resource.id}
75
+ id={id}
76
+ key={id}
66
77
  contentType={contentType}
67
78
  showAdditionalResources={showAdditionalResources}
79
+ hideAddToFavoriteButton={hideAddToFavoriteButton}
80
+ onToggleAddToFavorites={onToggleAddToFavorites}
68
81
  {...resource}
69
82
  contentTypeDescription={
70
83
  resource.additional ? t('resource.tooltipAdditionalTopic') : t('resource.tooltipCoreTopic')
@@ -85,7 +85,6 @@ const ActiveFilters = ({ filters, onFilterRemove, showOnSmallScreen }) => {
85
85
  <StyledActiveFilterWrapper key={filterKey}>
86
86
  {filterLength > 1 ? (
87
87
  <Tooltip
88
- align="top"
89
88
  tooltip={t('searchPage.searchFilterMessages.removeFilter', {
90
89
  filterName: filter.title,
91
90
  })}>
@@ -1,29 +1,28 @@
1
- import React, { useState, useEffect, useRef, ReactElement } from 'react';
2
1
  import Button from '@ndla/button';
3
- import Tooltip from '@ndla/tooltip';
4
- import { Additional, ChevronUp, ChevronDown } from '@ndla/icons/common';
2
+ import { Additional, ChevronDown, ChevronUp } from '@ndla/icons/common';
5
3
  import SafeLink from '@ndla/safelink';
4
+ import Tooltip from '@ndla/tooltip';
5
+ import React, { ReactElement, useEffect, useRef, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import ContentTypeBadge from '../ContentTypeBadge';
8
8
  import { ContentTypeResultType, Resource } from '../types';
9
9
  import {
10
10
  highlightStyle,
11
+ noWidthhighlightStyle,
11
12
  showAllButtonStyle,
12
- tooltipStyle,
13
- StyledNoHit,
14
- StyledWrapper,
15
13
  StyledHeader,
16
- StyledListItem,
17
14
  StyledList,
15
+ StyledListItem,
16
+ StyledNoHit,
18
17
  StyledTag,
19
- noWidthhighlightStyle,
18
+ StyledWrapper,
20
19
  } from './ContentTypeResultStyles';
21
20
  import { isPathToHighlight } from './IsPathToHighlight';
22
21
 
23
22
  const renderAdditionalIcon = (label: string, isAdditional?: boolean): ReactElement | null => {
24
23
  if (isAdditional && label) {
25
24
  return (
26
- <Tooltip tooltip={label} align="top" css={tooltipStyle}>
25
+ <Tooltip tooltip={label}>
27
26
  <Additional className="c-icon--20" />
28
27
  </Tooltip>
29
28
  );
@@ -120,8 +120,6 @@ const ActiveFilters = ({
120
120
  <StyledActiveFilterWrapper key={filterKey}>
121
121
  {filterLength > 1 ? (
122
122
  <Tooltip
123
- delay={2000}
124
- align="bottom"
125
123
  tooltip={t('searchPage.searchFilterMessages.removeFilter', {
126
124
  filterName: filter.title,
127
125
  })}>
@@ -139,7 +137,7 @@ const ActiveFilters = ({
139
137
  {filterItems}
140
138
  {filterLength > showFilterCount && (
141
139
  <StyledActiveFilterWrapper>
142
- <Tooltip delay={2000} align="bottom" tooltip={t('searchPage.searchFilterMessages.noValuesButtonText')}>
140
+ <Tooltip tooltip={t('searchPage.searchFilterMessages.noValuesButtonText')}>
143
141
  <Button
144
142
  aria-label={t('searchPage.searchFilterMessages.additionalSubjectFilters', {
145
143
  count: filterLength - showFilterCount + 1,
@@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
16
16
 
17
17
  const Wrapper = styled.div`
18
18
  display: inline-flex;
19
- justify-content: center;
19
+ justify-content: right;
20
20
  align-items: center;
21
21
  flex: 1;
22
22
  ${mq.range({ until: breakpoints.tablet })} {