@ndla/ui 14.0.0 → 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 (189) 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/InfoBlock/InfoBlock.js +55 -0
  7. package/es/InfoBlock/index.js +1 -0
  8. package/es/MyNdla/Navigation/VerticalNavigation.js +51 -0
  9. package/es/MyNdla/Navigation/index.js +2 -0
  10. package/es/MyNdla/Resource/Folder.js +86 -0
  11. package/{lib/MyNdla/ResourceDash/ResourcesView.d.ts → es/MyNdla/Resource/index.js} +2 -3
  12. package/es/MyNdla/index.js +3 -4
  13. package/es/Resource/BlockResource.js +73 -0
  14. package/es/Resource/ListResource.js +66 -0
  15. package/es/Resource/index.js +10 -0
  16. package/es/Resource/resourceComponents.js +97 -0
  17. package/es/ResourceGroup/ResourceGroup.js +7 -5
  18. package/es/ResourceGroup/ResourceItem.js +25 -24
  19. package/es/ResourceGroup/ResourceList.js +18 -6
  20. package/es/SnackBar/SnackBar.js +117 -0
  21. package/es/SnackBar/index.js +9 -0
  22. package/es/TagSelector/SuggestionInput.js +240 -0
  23. package/es/TagSelector/Suggestions.js +93 -0
  24. package/es/TagSelector/TagSelector.js +137 -0
  25. package/es/TagSelector/index.js +9 -0
  26. package/es/TreeStructure/FolderItem.js +130 -0
  27. package/es/TreeStructure/FolderItems.js +123 -0
  28. package/es/TreeStructure/FolderNameInput.js +112 -0
  29. package/es/TreeStructure/TreeStructure.js +254 -0
  30. package/es/TreeStructure/TreeStructure.types.js +0 -0
  31. package/es/TreeStructure/TreeStructureWrapper.js +13 -0
  32. package/es/TreeStructure/helperFunctions.js +92 -0
  33. package/es/TreeStructure/index.js +9 -0
  34. package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +182 -0
  35. package/es/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +0 -0
  36. package/es/all.css +72 -0
  37. package/es/index.js +8 -3
  38. package/es/locale/messages-en.js +62 -4
  39. package/es/locale/messages-nb.js +61 -3
  40. package/es/locale/messages-nn.js +61 -3
  41. package/es/locale/messages-se.js +61 -3
  42. package/es/locale/messages-sma.js +61 -3
  43. package/lib/Article/Article.d.ts +3 -1
  44. package/lib/Article/Article.js +43 -23
  45. package/lib/Article/ArticleFavoritesButton.d.ts +15 -0
  46. package/lib/Article/ArticleFavoritesButton.js +56 -0
  47. package/lib/Article/index.d.ts +2 -1
  48. package/lib/Article/index.js +8 -0
  49. package/lib/Breadcrumb/ActionBreadcrumb.d.ts +16 -0
  50. package/lib/Breadcrumb/ActionBreadcrumb.js +72 -0
  51. package/lib/Breadcrumb/index.d.ts +1 -0
  52. package/lib/Breadcrumb/index.js +8 -0
  53. package/lib/InfoBlock/InfoBlock.d.ts +8 -0
  54. package/lib/InfoBlock/InfoBlock.js +58 -0
  55. package/lib/InfoBlock/index.d.ts +1 -0
  56. package/lib/InfoBlock/index.js +13 -0
  57. package/lib/MyNdla/Navigation/VerticalNavigation.d.ts +10 -0
  58. package/lib/MyNdla/Navigation/VerticalNavigation.js +61 -0
  59. package/lib/MyNdla/Navigation/index.d.ts +2 -0
  60. package/lib/MyNdla/Navigation/index.js +15 -0
  61. package/lib/MyNdla/Resource/Folder.d.ts +20 -0
  62. package/lib/MyNdla/Resource/Folder.js +100 -0
  63. package/lib/MyNdla/Resource/index.d.ts +9 -0
  64. package/lib/MyNdla/Resource/index.js +15 -0
  65. package/lib/MyNdla/index.d.ts +3 -4
  66. package/lib/MyNdla/index.js +9 -11
  67. package/lib/Resource/BlockResource.d.ts +20 -0
  68. package/lib/Resource/BlockResource.js +84 -0
  69. package/lib/Resource/ListResource.d.ts +20 -0
  70. package/lib/Resource/ListResource.js +78 -0
  71. package/lib/Resource/index.d.ts +11 -0
  72. package/lib/Resource/index.js +29 -0
  73. package/lib/Resource/resourceComponents.d.ts +24 -0
  74. package/lib/Resource/resourceComponents.js +106 -0
  75. package/lib/ResourceGroup/ResourceGroup.d.ts +2 -1
  76. package/lib/ResourceGroup/ResourceGroup.js +7 -5
  77. package/lib/ResourceGroup/ResourceItem.d.ts +5 -1
  78. package/lib/ResourceGroup/ResourceItem.js +26 -24
  79. package/lib/ResourceGroup/ResourceList.d.ts +3 -1
  80. package/lib/ResourceGroup/ResourceList.js +18 -6
  81. package/lib/SnackBar/SnackBar.d.ts +23 -0
  82. package/lib/SnackBar/SnackBar.js +127 -0
  83. package/lib/SnackBar/index.d.ts +10 -0
  84. package/lib/SnackBar/index.js +15 -0
  85. package/lib/TagSelector/SuggestionInput.d.ts +19 -0
  86. package/lib/TagSelector/SuggestionInput.js +255 -0
  87. package/lib/TagSelector/Suggestions.d.ts +12 -0
  88. package/lib/TagSelector/Suggestions.js +96 -0
  89. package/lib/TagSelector/TagSelector.d.ts +16 -0
  90. package/lib/TagSelector/TagSelector.js +150 -0
  91. package/lib/TagSelector/index.d.ts +10 -0
  92. package/lib/TagSelector/index.js +19 -0
  93. package/lib/TreeStructure/FolderItem.d.ts +27 -0
  94. package/lib/TreeStructure/FolderItem.js +140 -0
  95. package/lib/TreeStructure/FolderItems.d.ts +11 -0
  96. package/lib/TreeStructure/FolderItems.js +130 -0
  97. package/lib/TreeStructure/FolderNameInput.d.ts +15 -0
  98. package/lib/TreeStructure/FolderNameInput.js +125 -0
  99. package/lib/TreeStructure/TreeStructure.d.ts +12 -0
  100. package/lib/TreeStructure/TreeStructure.js +273 -0
  101. package/lib/TreeStructure/TreeStructure.types.d.ts +63 -0
  102. package/lib/TreeStructure/TreeStructure.types.js +1 -0
  103. package/lib/TreeStructure/TreeStructureWrapper.d.ts +12 -0
  104. package/lib/TreeStructure/TreeStructureWrapper.js +24 -0
  105. package/lib/TreeStructure/helperFunctions.d.ts +5 -0
  106. package/lib/TreeStructure/helperFunctions.js +103 -0
  107. package/lib/TreeStructure/index.d.ts +10 -0
  108. package/lib/TreeStructure/index.js +15 -0
  109. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.d.ts +11 -0
  110. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.js +186 -0
  111. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.d.ts +26 -0
  112. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +1 -0
  113. package/lib/User/apiTypes.d.ts +1 -1
  114. package/lib/User/index.d.ts +2 -2
  115. package/lib/all.css +72 -0
  116. package/lib/index.d.ts +13 -4
  117. package/lib/index.js +68 -9
  118. package/lib/locale/messages-en.d.ts +58 -0
  119. package/lib/locale/messages-en.js +62 -4
  120. package/lib/locale/messages-nb.d.ts +58 -0
  121. package/lib/locale/messages-nb.js +61 -3
  122. package/lib/locale/messages-nn.d.ts +58 -0
  123. package/lib/locale/messages-nn.js +61 -3
  124. package/lib/locale/messages-se.d.ts +58 -0
  125. package/lib/locale/messages-se.js +61 -3
  126. package/lib/locale/messages-sma.d.ts +58 -0
  127. package/lib/locale/messages-sma.js +61 -3
  128. package/lib/types.d.ts +1 -1
  129. package/package.json +11 -11
  130. package/src/Article/Article.tsx +31 -0
  131. package/src/Article/ArticleFavoritesButton.tsx +40 -0
  132. package/src/Article/index.ts +2 -0
  133. package/src/Breadcrumb/ActionBreadcrumb.tsx +68 -0
  134. package/src/Breadcrumb/index.ts +2 -0
  135. package/src/InfoBlock/InfoBlock.tsx +61 -0
  136. package/src/InfoBlock/index.ts +1 -0
  137. package/src/MyNdla/Navigation/VerticalNavigation.tsx +93 -0
  138. package/src/MyNdla/Navigation/index.ts +2 -0
  139. package/src/MyNdla/Resource/Folder.tsx +143 -0
  140. package/src/MyNdla/Resource/index.ts +10 -0
  141. package/src/MyNdla/index.ts +3 -5
  142. package/src/Resource/BlockResource.tsx +101 -0
  143. package/src/Resource/ListResource.tsx +111 -0
  144. package/src/Resource/index.ts +12 -0
  145. package/src/Resource/resourceComponents.tsx +143 -0
  146. package/src/ResourceGroup/ResourceGroup.tsx +3 -0
  147. package/src/ResourceGroup/ResourceItem.tsx +17 -0
  148. package/src/ResourceGroup/ResourceList.tsx +16 -3
  149. package/src/SnackBar/SnackBar.tsx +183 -0
  150. package/src/SnackBar/index.ts +13 -0
  151. package/src/TagSelector/SuggestionInput.tsx +230 -0
  152. package/src/TagSelector/Suggestions.tsx +125 -0
  153. package/src/TagSelector/TagSelector.tsx +111 -0
  154. package/src/TagSelector/index.ts +13 -0
  155. package/src/TreeStructure/FolderItem.tsx +160 -0
  156. package/src/TreeStructure/FolderItems.tsx +109 -0
  157. package/src/TreeStructure/FolderNameInput.tsx +109 -0
  158. package/src/TreeStructure/TreeStructure.tsx +184 -0
  159. package/src/TreeStructure/TreeStructure.types.ts +69 -0
  160. package/src/TreeStructure/TreeStructureWrapper.tsx +34 -0
  161. package/src/TreeStructure/helperFunctions.ts +52 -0
  162. package/src/TreeStructure/index.ts +11 -0
  163. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.ts +161 -0
  164. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.types.ts +28 -0
  165. package/src/User/apiTypes.ts +1 -1
  166. package/src/User/index.ts +2 -2
  167. package/src/all.scss +1 -0
  168. package/src/index.ts +14 -5
  169. package/src/locale/messages-en.ts +56 -3
  170. package/src/locale/messages-nb.ts +55 -2
  171. package/src/locale/messages-nn.ts +55 -2
  172. package/src/locale/messages-se.ts +55 -2
  173. package/src/locale/messages-sma.ts +55 -2
  174. package/src/types.ts +1 -1
  175. package/es/MyNdla/ResourceDash/Breadcrumbs.js +0 -22
  176. package/es/MyNdla/ResourceDash/ResourceElement.js +0 -27
  177. package/es/MyNdla/ResourceDash/ResourcesView.js +0 -43
  178. package/es/MyNdla/ResourceDash/index.js +0 -4
  179. package/lib/MyNdla/ResourceDash/Breadcrumbs.d.ts +0 -15
  180. package/lib/MyNdla/ResourceDash/Breadcrumbs.js +0 -35
  181. package/lib/MyNdla/ResourceDash/ResourceElement.d.ts +0 -18
  182. package/lib/MyNdla/ResourceDash/ResourceElement.js +0 -38
  183. package/lib/MyNdla/ResourceDash/ResourcesView.js +0 -57
  184. package/lib/MyNdla/ResourceDash/index.d.ts +0 -4
  185. package/lib/MyNdla/ResourceDash/index.js +0 -31
  186. package/src/MyNdla/ResourceDash/Breadcrumbs.tsx +0 -31
  187. package/src/MyNdla/ResourceDash/ResourceElement.tsx +0 -50
  188. package/src/MyNdla/ResourceDash/ResourcesView.tsx +0 -42
  189. package/src/MyNdla/ResourceDash/index.ts +0 -5
@@ -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 styled from '@emotion/styled';
10
+ import React, { ReactNode } from 'react';
11
+ import { FolderOutlined } from '@ndla/icons/contentType';
12
+ import { FileDocumentOutline } from '@ndla/icons/common';
13
+ import { fonts, spacing, colors } from '@ndla/core';
14
+ import { css } from '@emotion/core';
15
+ import { useTranslation } from 'react-i18next';
16
+ import SafeLink from '@ndla/safelink';
17
+ import { MenuButton } from '@ndla/button';
18
+
19
+ interface FolderIconWrapperProps {
20
+ type?: LayoutType;
21
+ }
22
+
23
+ const FolderIconWrapper = styled.div<FolderIconWrapperProps>`
24
+ display: flex;
25
+ border-radius: 100%;
26
+ padding: 11px;
27
+ background-color: ${colors.brand.greyLighter};
28
+ svg {
29
+ width: 18px;
30
+ height: 18px;
31
+ }
32
+ ${(p) =>
33
+ p.type === 'block' &&
34
+ css`
35
+ background-color: transparent;
36
+ ${FolderWrapper}:hover & {
37
+ background-color: ${colors.brand.light};
38
+ transition-duration 0.5s;
39
+ }
40
+ `};
41
+ `;
42
+
43
+ const FolderTitle = styled.h2`
44
+ ${fonts.sizes(18)};
45
+ font-weight: ${fonts.weight.normal};
46
+ margin: 0;
47
+ flex: 1;
48
+
49
+ overflow: hidden;
50
+ text-overflow: ellipsis;
51
+ // Unfortunate css needed for multi-line text overflow ellipsis.
52
+ display: -webkit-box;
53
+ -webkit-line-clamp: 1;
54
+ line-clamp: 1;
55
+ -webkit-box-orient: vertical;
56
+ `;
57
+
58
+ const FolderWrapper = styled(SafeLink)`
59
+ display: flex;
60
+ align-items: center;
61
+ padding: ${spacing.small};
62
+ border: 1px solid ${colors.brand.light};
63
+ border-radius: 2px;
64
+ box-shadow: none;
65
+ text-decoration: none;
66
+ color: ${colors.brand.greyDark};
67
+ font-family: ${fonts.sans};
68
+ transition-duration: 0.2s;
69
+ gap: ${spacing.small};
70
+ &:hover {
71
+ box-shadow: 1px 1px 6px 2px rgba(9, 55, 101, 0.08);
72
+ transition-duration: 0.2s;
73
+ ${FolderTitle} {
74
+ color: ${colors.brand.primary};
75
+ text-decoration: underline;
76
+ }
77
+ }
78
+ `;
79
+
80
+ interface Props {
81
+ title: string;
82
+ subFolders?: number;
83
+ subResources?: number;
84
+ description?: string;
85
+ link: string;
86
+ type: LayoutType;
87
+ actionMenu?: ReactNode;
88
+ }
89
+
90
+ interface IconCountProps {
91
+ type: 'resource' | 'folder';
92
+ count?: number;
93
+ layoutType: LayoutType;
94
+ }
95
+
96
+ interface IconCountWrapperProps {
97
+ type: LayoutType;
98
+ }
99
+ const IconCountWrapper = styled.div<IconCountWrapperProps>`
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 2px;
103
+ ${fonts.sizes(16)};
104
+ ${(p) =>
105
+ p.type === 'block' &&
106
+ css`
107
+ opacity: 0;
108
+ ${FolderWrapper}:hover & {
109
+ opacity: 1;
110
+ }
111
+ `};
112
+ `;
113
+ const IconCount = ({ type, count, layoutType }: IconCountProps) => {
114
+ const Icon = type === 'resource' ? FileDocumentOutline : FolderOutlined;
115
+ const { t } = useTranslation();
116
+ if (!count) return null;
117
+
118
+ return (
119
+ <IconCountWrapper type={layoutType}>
120
+ <Icon aria-label={t(`myNdla.${type}s`)} />
121
+ <span>{layoutType === 'block' ? count : t(`myNdla.${type}s`, { count: 3 })}</span>
122
+ </IconCountWrapper>
123
+ );
124
+ };
125
+
126
+ type LayoutType = 'list' | 'block';
127
+
128
+ const Folder = ({ link, title, subFolders, subResources, type = 'list', actionMenu }: Props) => {
129
+ const { t } = useTranslation();
130
+ return (
131
+ <FolderWrapper to={link}>
132
+ <FolderIconWrapper type={type}>
133
+ <FolderOutlined aria-label={t('myNdla.folder')} />
134
+ </FolderIconWrapper>
135
+ <FolderTitle>{title}</FolderTitle>
136
+ <IconCount layoutType={type} type={'folder'} count={subFolders} />
137
+ <IconCount layoutType={type} type={'resource'} count={subResources} />
138
+ <MenuButton size="small" />
139
+ </FolderWrapper>
140
+ );
141
+ };
142
+
143
+ export default Folder;
@@ -0,0 +1,10 @@
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 Folder from './Folder';
10
+ export { Folder };
@@ -1,5 +1,3 @@
1
- import { ResourceElement } from './ResourceDash';
2
- import { ResourcesView } from './ResourceDash';
3
- import { Breadcrumbs } from './ResourceDash';
4
-
5
- export { ResourceElement, ResourcesView, Breadcrumbs };
1
+ import Folder from './Resource/Folder';
2
+ import { VerticalNavigation } from './Navigation';
3
+ export { VerticalNavigation, Folder };
@@ -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;
@@ -291,6 +301,13 @@ const ResourceItem = ({
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')