@ndla/ui 4.3.2 → 4.3.3-alpha.12

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 (184) hide show
  1. package/README.md +11 -11
  2. package/es/Article/Article.js +3 -7
  3. package/es/Article/ArticleByline.js +4 -4
  4. package/es/Article/ArticleNotions.js +37 -92
  5. package/es/ContentPlaceholder/ContentPlaceholder.js +51 -0
  6. package/es/ContentPlaceholder/index.js +9 -0
  7. package/es/ContentTypeBadge/ContentTypeBadge.js +10 -1
  8. package/es/FactBox/FactBox.js +36 -11
  9. package/es/Filter/FilterButtons.js +18 -17
  10. package/es/Filter/FilterCarousel.js +198 -0
  11. package/es/Masthead/Masthead.js +30 -7
  12. package/es/Masthead/index.js +2 -1
  13. package/es/Masthead/utils.js +11 -0
  14. package/es/MessageBox/MessageBox.js +12 -7
  15. package/es/Notion/ConceptNotion.js +57 -0
  16. package/es/Notion/FigureNotion.js +84 -0
  17. package/es/Notion/Notion.js +122 -48
  18. package/es/Notion/NotionImage.js +47 -0
  19. package/es/Notion/NotionVisualElement.js +38 -0
  20. package/es/Notion/index.js +2 -1
  21. package/es/SearchTypeResult/ActiveFilterContent.js +17 -2
  22. package/es/SearchTypeResult/ActiveFilters.js +64 -50
  23. package/es/SearchTypeResult/PopupFilter.js +28 -125
  24. package/es/SearchTypeResult/ResultNavigation.js +41 -14
  25. package/es/SearchTypeResult/SearchFieldHeader.js +20 -41
  26. package/es/SearchTypeResult/SearchFilterContent.js +61 -0
  27. package/es/SearchTypeResult/SearchHeader.js +51 -28
  28. package/es/SearchTypeResult/SearchItem.js +64 -190
  29. package/es/SearchTypeResult/SearchItemList.js +132 -0
  30. package/es/SearchTypeResult/SearchItems.js +17 -13
  31. package/es/SearchTypeResult/SearchNotionItem.js +13 -13
  32. package/es/SearchTypeResult/SearchNotionsResult.js +16 -21
  33. package/es/SearchTypeResult/SearchTypeHeader.js +43 -26
  34. package/es/SearchTypeResult/SearchTypeResult.js +7 -5
  35. package/es/SearchTypeResult/SearchViewType.js +93 -0
  36. package/es/SearchTypeResult/components/ItemContexts.js +125 -0
  37. package/es/SearchTypeResult/components/ItemResourceHeader.js +87 -0
  38. package/es/SearchTypeResult/components/ItemTopicHeader.js +56 -0
  39. package/es/SearchTypeResult/components/SubjectFilters.js +177 -0
  40. package/es/SearchTypeResult/index.js +2 -1
  41. package/es/all.css +1 -1
  42. package/es/index.js +5 -3
  43. package/es/locale/messages-en.js +20 -5
  44. package/es/locale/messages-nb.js +21 -6
  45. package/es/locale/messages-nn.js +21 -6
  46. package/es/model/ContentType.js +2 -1
  47. package/es/shapes.js +1 -1
  48. package/lib/Article/Article.d.ts +4 -5
  49. package/lib/Article/Article.js +3 -7
  50. package/lib/Article/ArticleByline.js +4 -4
  51. package/lib/Article/ArticleNotions.d.ts +3 -8
  52. package/lib/Article/ArticleNotions.js +41 -90
  53. package/lib/ContentPlaceholder/ContentPlaceholder.d.ts +9 -0
  54. package/lib/ContentPlaceholder/ContentPlaceholder.js +54 -0
  55. package/lib/ContentPlaceholder/index.d.ts +9 -0
  56. package/lib/ContentPlaceholder/index.js +20 -0
  57. package/lib/ContentTypeBadge/ContentTypeBadge.d.ts +1 -0
  58. package/lib/ContentTypeBadge/ContentTypeBadge.js +14 -2
  59. package/lib/FactBox/FactBox.js +41 -8
  60. package/lib/Filter/FilterButtons.d.ts +3 -10
  61. package/lib/Filter/FilterButtons.js +19 -17
  62. package/lib/Filter/FilterCarousel.d.ts +13 -0
  63. package/lib/Filter/FilterCarousel.js +207 -0
  64. package/lib/Masthead/Masthead.d.ts +3 -3
  65. package/lib/Masthead/Masthead.js +37 -7
  66. package/lib/Masthead/index.d.ts +2 -1
  67. package/lib/Masthead/index.js +8 -0
  68. package/lib/Masthead/utils.d.ts +8 -0
  69. package/lib/Masthead/utils.js +20 -0
  70. package/lib/MessageBox/MessageBox.d.ts +2 -1
  71. package/lib/MessageBox/MessageBox.js +12 -7
  72. package/lib/Notion/ConceptNotion.d.ts +25 -0
  73. package/lib/Notion/ConceptNotion.js +79 -0
  74. package/lib/Notion/FigureNotion.d.ts +23 -0
  75. package/lib/Notion/FigureNotion.js +99 -0
  76. package/lib/Notion/Notion.d.ts +24 -11
  77. package/lib/Notion/Notion.js +120 -48
  78. package/lib/Notion/NotionImage.d.ts +15 -0
  79. package/lib/Notion/NotionImage.js +63 -0
  80. package/lib/Notion/NotionVisualElement.d.ts +22 -0
  81. package/lib/Notion/NotionVisualElement.js +51 -0
  82. package/lib/Notion/index.d.ts +1 -0
  83. package/lib/Notion/index.js +8 -0
  84. package/lib/SearchTypeResult/ActiveFilterContent.js +16 -9
  85. package/lib/SearchTypeResult/ActiveFilters.d.ts +2 -1
  86. package/lib/SearchTypeResult/ActiveFilters.js +65 -50
  87. package/lib/SearchTypeResult/PopupFilter.d.ts +13 -19
  88. package/lib/SearchTypeResult/PopupFilter.js +27 -123
  89. package/lib/SearchTypeResult/ResultNavigation.d.ts +2 -2
  90. package/lib/SearchTypeResult/ResultNavigation.js +38 -14
  91. package/lib/SearchTypeResult/SearchFieldHeader.d.ts +3 -8
  92. package/lib/SearchTypeResult/SearchFieldHeader.js +18 -39
  93. package/lib/SearchTypeResult/SearchFilterContent.d.ts +16 -0
  94. package/lib/SearchTypeResult/SearchFilterContent.js +67 -0
  95. package/lib/SearchTypeResult/SearchHeader.d.ts +3 -7
  96. package/lib/SearchTypeResult/SearchHeader.js +59 -30
  97. package/lib/SearchTypeResult/SearchItem.d.ts +8 -12
  98. package/lib/SearchTypeResult/SearchItem.js +64 -187
  99. package/lib/SearchTypeResult/SearchItemList.d.ts +10 -0
  100. package/lib/SearchTypeResult/SearchItemList.js +139 -0
  101. package/lib/SearchTypeResult/SearchItems.d.ts +4 -3
  102. package/lib/SearchTypeResult/SearchItems.js +18 -13
  103. package/lib/SearchTypeResult/SearchNotionItem.js +13 -13
  104. package/lib/SearchTypeResult/SearchNotionsResult.d.ts +2 -4
  105. package/lib/SearchTypeResult/SearchNotionsResult.js +23 -23
  106. package/lib/SearchTypeResult/SearchTypeHeader.d.ts +2 -2
  107. package/lib/SearchTypeResult/SearchTypeHeader.js +40 -25
  108. package/lib/SearchTypeResult/SearchTypeResult.d.ts +7 -6
  109. package/lib/SearchTypeResult/SearchTypeResult.js +7 -5
  110. package/lib/SearchTypeResult/SearchViewType.d.ts +13 -0
  111. package/lib/SearchTypeResult/SearchViewType.js +99 -0
  112. package/lib/SearchTypeResult/components/ItemContexts.d.ts +19 -0
  113. package/lib/SearchTypeResult/components/ItemContexts.js +134 -0
  114. package/lib/SearchTypeResult/components/ItemResourceHeader.d.ts +16 -0
  115. package/lib/SearchTypeResult/components/ItemResourceHeader.js +98 -0
  116. package/lib/SearchTypeResult/components/ItemTopicHeader.d.ts +17 -0
  117. package/lib/SearchTypeResult/components/ItemTopicHeader.js +67 -0
  118. package/lib/SearchTypeResult/components/SubjectFilters.d.ts +32 -0
  119. package/lib/SearchTypeResult/components/SubjectFilters.js +192 -0
  120. package/lib/SearchTypeResult/index.d.ts +2 -1
  121. package/lib/SearchTypeResult/index.js +8 -0
  122. package/lib/all.css +1 -1
  123. package/lib/index.d.ts +4 -2
  124. package/lib/index.js +40 -1
  125. package/lib/locale/messages-en.d.ts +16 -1
  126. package/lib/locale/messages-en.js +20 -5
  127. package/lib/locale/messages-nb.d.ts +15 -0
  128. package/lib/locale/messages-nb.js +21 -6
  129. package/lib/locale/messages-nn.d.ts +16 -1
  130. package/lib/locale/messages-nn.js +21 -6
  131. package/lib/model/ContentType.d.ts +1 -0
  132. package/lib/model/ContentType.js +4 -2
  133. package/lib/shapes.js +1 -1
  134. package/lib/types.d.ts +1 -0
  135. package/package.json +14 -14
  136. package/src/Article/Article.tsx +8 -15
  137. package/src/Article/ArticleByline.tsx +1 -1
  138. package/src/Article/ArticleNotions.tsx +13 -33
  139. package/src/ContentPlaceholder/ContentPlaceholder.tsx +68 -0
  140. package/src/ContentPlaceholder/index.ts +11 -0
  141. package/src/ContentTypeBadge/ContentTypeBadge.tsx +8 -0
  142. package/src/ContentTypeBadge/component.content-type-badge.scss +5 -0
  143. package/src/FactBox/FactBox.tsx +22 -15
  144. package/src/Figure/component.figure.scss +1 -1
  145. package/src/Filter/FilterButtons.tsx +14 -15
  146. package/src/Filter/FilterCarousel.tsx +166 -0
  147. package/src/Masthead/Masthead.tsx +45 -20
  148. package/src/Masthead/component.masthead.scss +3 -62
  149. package/src/Masthead/index.ts +3 -1
  150. package/src/Masthead/utils.ts +12 -0
  151. package/src/MessageBox/MessageBox.tsx +4 -1
  152. package/src/Notion/ConceptNotion.tsx +80 -0
  153. package/src/Notion/FigureNotion.tsx +88 -0
  154. package/src/Notion/Notion.tsx +205 -75
  155. package/src/Notion/NotionImage.tsx +51 -0
  156. package/src/Notion/NotionVisualElement.tsx +50 -0
  157. package/src/Notion/index.ts +1 -0
  158. package/src/SearchTypeResult/ActiveFilterContent.tsx +7 -2
  159. package/src/SearchTypeResult/ActiveFilters.tsx +72 -38
  160. package/src/SearchTypeResult/PopupFilter.tsx +73 -146
  161. package/src/SearchTypeResult/ResultNavigation.tsx +54 -16
  162. package/src/SearchTypeResult/SearchFieldHeader.tsx +15 -40
  163. package/src/SearchTypeResult/SearchFilterContent.tsx +63 -0
  164. package/src/SearchTypeResult/SearchHeader.tsx +31 -31
  165. package/src/SearchTypeResult/SearchItem.tsx +145 -233
  166. package/src/SearchTypeResult/SearchItemList.tsx +167 -0
  167. package/src/SearchTypeResult/SearchItems.tsx +26 -19
  168. package/src/SearchTypeResult/SearchNotionItem.tsx +5 -12
  169. package/src/SearchTypeResult/SearchNotionsResult.tsx +29 -22
  170. package/src/SearchTypeResult/SearchTypeHeader.tsx +51 -33
  171. package/src/SearchTypeResult/SearchTypeResult.tsx +13 -12
  172. package/src/SearchTypeResult/SearchViewType.tsx +109 -0
  173. package/src/SearchTypeResult/components/ItemContexts.tsx +138 -0
  174. package/src/SearchTypeResult/components/ItemResourceHeader.tsx +133 -0
  175. package/src/SearchTypeResult/components/ItemTopicHeader.tsx +95 -0
  176. package/src/SearchTypeResult/components/SubjectFilters.tsx +152 -0
  177. package/src/SearchTypeResult/index.ts +9 -1
  178. package/src/index.ts +5 -1
  179. package/src/locale/messages-en.ts +18 -3
  180. package/src/locale/messages-nb.ts +19 -4
  181. package/src/locale/messages-nn.ts +19 -4
  182. package/src/model/ContentType.ts +1 -0
  183. package/src/shapes.js +1 -0
  184. package/src/types.ts +1 -0
@@ -6,23 +6,22 @@
6
6
  *
7
7
  */
8
8
 
9
- import React, { ReactNode } from 'react';
9
+ import React from 'react';
10
10
  import styled from '@emotion/styled';
11
11
  import parse from 'html-react-parser';
12
- import { ChevronRight, Additional, Core } from '@ndla/icons/common';
12
+
13
13
  import SafeLink from '@ndla/safelink';
14
- import Button from '@ndla/button';
15
- import Modal, { ModalCloseButton } from '@ndla/modal';
14
+ import { animations, colors, fonts, spacing } from '@ndla/core';
16
15
 
17
- import { breakpoints, colors, fonts, mq, spacing } from '@ndla/core';
18
- import { useTranslation } from 'react-i18next';
19
16
  import { ContentType } from './SearchTypeResult';
20
17
  import constants from '../model';
21
- import ContentTypeBadge from '../ContentTypeBadge';
18
+ import ItemContexts, { ItemContextsType } from './components/ItemContexts';
19
+ import ItemTopicHeader from './components/ItemTopicHeader';
20
+ import ItemResourceHeader from './components/ItemResourceHeader';
22
21
 
23
22
  const { contentTypes } = constants;
24
23
 
25
- const resourceTypeColor = (type: string) => {
24
+ export const resourceTypeColor = (type: string) => {
26
25
  switch (type) {
27
26
  case contentTypes.SUBJECT_MATERIAL:
28
27
  return colors.subjectMaterial.light;
@@ -39,274 +38,187 @@ const resourceTypeColor = (type: string) => {
39
38
  case contentTypes.LEARNING_PATH:
40
39
  return colors.learningPath.light;
41
40
  default:
42
- return null;
41
+ return '';
43
42
  }
44
43
  };
45
44
 
46
45
  type ItemTypeProps = {
47
- type?: ContentType;
46
+ contentType?: ContentType;
47
+ isTopic?: boolean;
48
48
  };
49
49
 
50
- const ItemWrapper = styled.div`
51
- flex-direction: column;
52
- `;
53
-
54
- const ItemHead = styled.div<ItemTypeProps>`
55
- height: 200px;
56
- position: relative;
57
- a {
58
- box-shadow: none;
59
- }
60
- ${mq.range({ from: breakpoints.tablet })} {
61
- height: 160px;
62
- }
63
- ${mq.range({ from: breakpoints.desktop })} {
64
- height: 100px;
65
- }
66
- border: 1px solid ${(props) => props.type && `${resourceTypeColor(props.type)};`};
67
- border-bottom: 0;
68
- border-top-left-radius: 5px;
69
- border-top-right-radius: 5px;
70
- img {
71
- border-top-left-radius: 5px;
72
- border-top-right-radius: 5px;
73
- width: 100%;
74
- height: 100%;
75
- object-fit: cover;
76
- }
77
- `;
78
-
79
- const ItemIcon = styled.div<ItemTypeProps>`
80
- height: 100%;
81
- background: #ccc;
82
- border-top-left-radius: 5px;
83
- border-top-right-radius: 5px;
50
+ const Container = styled.div`
84
51
  display: flex;
85
- justify-content: center;
52
+ height: 400px;
86
53
  align-items: center;
87
- ${(props) => props.type && `background: ${resourceTypeColor(props.type)};`}
88
- `;
89
-
90
- const ItemContent = styled.div<ItemTypeProps>`
91
- border: 1px solid ${(props) => props.type && `${resourceTypeColor(props.type)};`};
92
- border-top: 0;
93
- border-bottom-left-radius: 5px;
94
- border-bottom-right-radius: 5px;
95
- padding: ${spacing.small};
54
+ justify-content: center;
96
55
  `;
97
56
 
98
- const ItemPillWrapper = styled.div`
99
- margin-top: 8px;
57
+ const ItemWrapper = styled.div`
58
+ flex-direction: column;
59
+ overflow: hidden;
100
60
  display: flex;
101
- flex-wrap: wrap;
102
- `;
103
- const ItemPill = styled.div`
104
- display: inline-block;
105
- background: ${colors.brand.greyLightest};
106
- padding: 2px 4px;
107
- border-radius: 2px;
108
- ${fonts.sizes('12px', '20px')};
109
- font-weight: ${fonts.weight.semibold};
110
- margin: 8px 4px 4px 0;
61
+ width: 100%;
62
+ height: 100%;
63
+ border: 1px solid ${colors.brand.neutral7};
64
+ border-radius: 5px;
65
+ transition: all ${animations.durations.fast} ease-in-out;
66
+ &:hover {
67
+ height: calc(100% + 4px);
68
+ width: calc(100% + 4px);
69
+ .topic-header-image {
70
+ height: calc(100% + 6px);
71
+ width: calc(100% + 6px);
72
+ }
73
+ .topic-label {
74
+ svg {
75
+ transform: scale(1.2);
76
+ }
77
+ }
78
+ .topic-label-list {
79
+ margin-left: 2px;
80
+ }
81
+ .resource-type-wrapper {
82
+ padding: 0 calc(${spacing.normal} + 2px);
83
+ .resource-icon-wrapper {
84
+ left: 19px;
85
+ svg {
86
+ transform: scale(1.2);
87
+ }
88
+ }
89
+ }
90
+ .resource-no-image {
91
+ background-color: ${colors.brand.greyLightest};
92
+ .c-content-type-badge {
93
+ width: 26px;
94
+ height: 26px;
95
+ left: 45px;
96
+ margin-left: 0;
97
+ top: 100%;
98
+ margin-top: -13px;
99
+ opacity: 1;
100
+ }
101
+ }
102
+ }
111
103
  `;
112
104
 
113
- const ItemTitle = styled.h3`
114
- font-size: 18px;
115
- line-height: 24px;
116
- margin-top: ${spacing.small};
117
- font-weight: 600;
118
- overflow-wrap: anywhere;
119
- `;
120
- const ItemText = styled.p`
121
- font-size: 15px;
122
- line-height: 20px;
123
- margin-bottom: ${spacing.small};
124
- word-break: break-word;
125
- overflow-wrap: anywhere;
126
- `;
127
- const BreadcrumbPath = styled.div`
128
- color: ${colors.text.light};
129
- font-size: 16px;
130
- line-height: 24px;
105
+ const ItemLink = styled(SafeLink)`
106
+ box-shadow: none;
107
+ color: unset;
108
+ text-decoration: none;
131
109
  display: flex;
132
- align-items: center;
133
- flex-wrap: wrap;
110
+ flex-direction: column;
111
+ position: relative;
112
+ min-height: 0;
113
+ flex: 1;
134
114
  `;
135
115
 
136
- const BreadcrumbItem = styled.span`
137
- display: inline-flex;
138
- align-items: center;
139
- flex-wrap: wrap;
140
- overflow-wrap: anywhere;
116
+ const TextWrapper = styled.div`
117
+ padding: 0 ${spacing.normal} ${spacing.small};
118
+ transition: all ${animations.durations.fast} ease-in-out;
119
+ ${ItemWrapper}:hover & {
120
+ padding-left: calc(${spacing.normal} + 2px);
121
+ padding-right: calc(${spacing.normal} + 2px);
122
+ }
141
123
  `;
142
124
 
143
- const ContextsWrapper = styled.div`
125
+ const ItemTitleWrapper = styled.div<ItemTypeProps>`
126
+ transition: all ${animations.durations.fast} ease-in-out;
144
127
  margin-top: ${spacing.small};
145
- button {
146
- ${fonts.sizes('16px', '24px')};
147
- box-shadow: none;
148
- &:hover {
149
- box-shadow: inset 0 -1px;
150
- }
151
- }
128
+ ${(props) =>
129
+ props.isTopic &&
130
+ `${ItemWrapper}:hover & {
131
+ padding-left:2px; padding-right: 2px;}`};
152
132
  `;
153
133
 
154
- const ModalHeader = styled.div`
155
- display: flex;
156
- justify-content: space-between;
157
- align-items: center;
158
- padding: ${spacing.normal} ${spacing.small};
159
- ${mq.range({ from: breakpoints.tablet })} {
160
- padding: ${spacing.normal} ${spacing.large};
134
+ const ItemTitle = styled.h3<ItemTypeProps>`
135
+ ${fonts.sizes('24px', '28px')};
136
+ color: ${colors.brand.primary};
137
+ ${(props) => props.isTopic && `margin-bottom: ${spacing.small};`};
138
+ font-weight: ${fonts.weight.semibold};
139
+ overflow-wrap: anywhere;
140
+ display: inline;
141
+ ${ItemWrapper}:hover & {
142
+ box-shadow: inset 0 -1px;
143
+ background-color: transparent;
161
144
  }
162
145
  `;
163
-
164
- const ModalHeading = styled.h2`
165
- margin: 0;
166
- ${fonts.sizes('16px', '20px')};
167
- font-weight: 600;
168
- `;
169
-
170
- const ModalContent = styled.div`
171
- padding: 0 ${spacing.small} ${spacing.normal};
172
- ${mq.range({ from: breakpoints.tablet })} {
173
- padding: 0 ${spacing.large} ${spacing.normal};
146
+ const ItemText = styled.div<ItemTypeProps>`
147
+ overflow: hidden;
148
+ ${fonts.sizes('16px', '24px')};
149
+ word-break: break-word;
150
+ overflow-wrap: anywhere;
151
+ transition: all ${animations.durations.fast} ease-in-out;
152
+ margin-top: ${spacing.small};
153
+ ${(props) =>
154
+ props.isTopic &&
155
+ `
156
+ ${fonts.sizes('18px', '28px')};
157
+ ${ItemWrapper}:hover & {
158
+ padding-left: 2px;
159
+ padding-right: 2px;
160
+ }`};
161
+ `;
162
+
163
+ const ContextWrapper = styled.div`
164
+ background: white;
165
+ z-index: 1;
166
+ padding: 0 ${spacing.normal} ${spacing.small};
167
+ transition: all ${animations.durations.fast} ease-in-out;
168
+ ${ItemWrapper}:hover & {
169
+ padding-left: calc(${spacing.normal} + 2px);
170
+ padding-right: calc(${spacing.normal} + 2px);
171
+ padding-bottom: calc(${spacing.small} + 2px);
174
172
  }
175
173
  `;
176
174
 
177
- const ContextList = styled.ul`
178
- margin: 0;
179
- padding: 0;
180
- list-style: none;
181
- `;
182
- const ContextListItem = styled.li`
183
- margin-bottom: ${spacing.normal};
184
- ${fonts.sizes('16px', '28px')};
185
- `;
186
-
187
- const IconWrapper = styled.div`
188
- margin-left: ${spacing.small};
189
- display: flex;
190
- align-items: center;
191
- `;
192
-
193
- type context = {
194
- breadcrumb: string[];
195
- url: string;
196
- isAdditional?: boolean;
197
- };
198
-
199
- export type SearchItemType = {
175
+ export type SearchItemProps = {
200
176
  id: string | number;
201
177
  title: string;
202
178
  url: string;
203
179
  ingress: string;
204
- contexts?: context[];
180
+ contexts?: ItemContextsType['contexts'];
205
181
  img?: { url: string; alt: string };
206
182
  labels?: string[];
207
- children?: ReactNode;
183
+ type?: ContentType;
208
184
  };
209
- type Props = {
210
- item: SearchItemType;
185
+ export type SearchItemType = {
186
+ item: SearchItemProps;
211
187
  type?: ContentType;
212
188
  };
213
- const SearchItem = ({ item, type }: Props) => {
214
- const { t } = useTranslation();
189
+ const SearchItem = ({ item, type }: SearchItemType) => {
215
190
  const { title, url, ingress, contexts, img = null, labels = [] } = item;
216
- const mainContext = contexts?.[0];
217
191
 
218
- const Breadcrumb = ({ breadcrumb, children }: { breadcrumb: string[]; children?: ReactNode }) => (
219
- <BreadcrumbPath>
220
- {breadcrumb.map((breadcrumbItem: string, i: number) => {
221
- return (
222
- <BreadcrumbItem key={`${breadcrumbItem}-${item.id}`}>
223
- <span>{breadcrumbItem}</span>
224
- {i !== breadcrumb.length - 1 && <ChevronRight />}
225
- </BreadcrumbItem>
226
- );
227
- })}
228
- {children}
229
- </BreadcrumbPath>
230
- );
192
+ const isTopic = type === contentTypes.TOPIC || type === contentTypes.MULTIDISCIPLINARY_TOPIC;
231
193
 
232
194
  return (
233
- <>
195
+ <Container>
234
196
  <ItemWrapper>
235
- <ItemHead type={type}>
236
- {img ? (
237
- <SafeLink to={url}>
238
- <img src={img.url} alt={img.alt} />
239
- </SafeLink>
197
+ <ItemLink to={url}>
198
+ {isTopic ? (
199
+ <ItemTopicHeader image={img} type={type}>
200
+ <ItemTitleWrapper isTopic>
201
+ <ItemTitle isTopic>{title}</ItemTitle>
202
+ </ItemTitleWrapper>
203
+ <ItemText isTopic>{parse(ingress)}</ItemText>
204
+ </ItemTopicHeader>
240
205
  ) : (
241
- <SafeLink to={url}>
242
- {type && (
243
- <ItemIcon type={type}>
244
- <ContentTypeBadge type={type} size="small" border={false} />
245
- </ItemIcon>
246
- )}
247
- </SafeLink>
248
- )}
249
- </ItemHead>
250
- <ItemContent type={type}>
251
- {labels.length > 0 && (
252
- <ItemPillWrapper>
253
- {labels.map((label) => (
254
- <ItemPill key={label}>{label}</ItemPill>
255
- ))}
256
- </ItemPillWrapper>
257
- )}
258
- <ItemTitle>
259
- <SafeLink to={url}>{title}</SafeLink>
260
- </ItemTitle>
261
- {item.children}
262
- <ItemText>{parse(ingress)}</ItemText>
263
- {mainContext && <Breadcrumb breadcrumb={mainContext.breadcrumb} />}
264
- {contexts && contexts.length > 1 && (
265
- <ContextsWrapper>
266
- <Modal
267
- activateButton={
268
- <Button link>
269
- {t('searchPage.contextModal.button', {
270
- count: contexts.length - 1,
271
- })}
272
- </Button>
273
- }
274
- animation="subtle"
275
- animationDuration={50}
276
- backgroundColor="white"
277
- size="medium">
278
- {(onClose: () => void) => (
279
- <>
280
- <ModalHeader>
281
- <ModalHeading>{t('searchPage.contextModal.heading')}</ModalHeading>
282
- <ModalCloseButton onClick={onClose} title={t('searchPage.close')} />
283
- </ModalHeader>
284
- <ModalContent>
285
- <ContextList>
286
- {contexts.map((context) => (
287
- <ContextListItem key={context.url}>
288
- <SafeLink to={context.url}>{title}</SafeLink>
289
- <Breadcrumb breadcrumb={context.breadcrumb}>
290
- <IconWrapper>
291
- {context.isAdditional ? (
292
- <Additional style={{ width: '22px', height: '22px' }} />
293
- ) : (
294
- <Core style={{ width: '22px', height: '22px' }} />
295
- )}
296
- </IconWrapper>
297
- </Breadcrumb>
298
- </ContextListItem>
299
- ))}
300
- </ContextList>
301
- </ModalContent>
302
- </>
303
- )}
304
- </Modal>
305
- </ContextsWrapper>
206
+ <>
207
+ <ItemResourceHeader labels={labels} img={img} type={type} />
208
+ <TextWrapper>
209
+ <ItemTitleWrapper>
210
+ <ItemTitle>{title}</ItemTitle>
211
+ </ItemTitleWrapper>
212
+ <ItemText>{parse(ingress)}</ItemText>
213
+ </TextWrapper>
214
+ </>
306
215
  )}
307
- </ItemContent>
216
+ <ContextWrapper>
217
+ {contexts && contexts.length > 0 && <ItemContexts contexts={contexts} id={item.id} title={item.title} />}
218
+ </ContextWrapper>
219
+ </ItemLink>
308
220
  </ItemWrapper>
309
- </>
221
+ </Container>
310
222
  );
311
223
  };
312
224
 
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Copyright (c) 2021-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, { Fragment } from 'react';
10
+ import { useTranslation } from 'react-i18next';
11
+ import parse from 'html-react-parser';
12
+ import styled from '@emotion/styled';
13
+
14
+ import { animations, colors, fonts, spacing } from '@ndla/core';
15
+ import SafeLink from '@ndla/safelink';
16
+
17
+ import ContentTypeBadge from '../ContentTypeBadge';
18
+ import ItemContexts from './components/ItemContexts';
19
+ import { SearchItemType } from './SearchItem';
20
+ import constants from '../model';
21
+ const { contentTypes } = constants;
22
+
23
+ const Container = styled.div`
24
+ display: flex;
25
+ flex-direction: column;
26
+ min-height: 192px;
27
+ height: 100%;
28
+ border: 1px solid ${colors.brand.neutral7};
29
+ border-radius: 5px;
30
+ transition: all ${animations.durations.fast} ease-in-out;
31
+ &:hover {
32
+ margin: -2px;
33
+ min-height: 196px;
34
+ height: calc(100% + 4px);
35
+ }
36
+ `;
37
+
38
+ const ItemLink = styled(SafeLink)`
39
+ box-shadow: none;
40
+ color: unset;
41
+ text-decoration: none;
42
+ display: flex;
43
+ position: relative;
44
+ min-height: 0;
45
+ flex: 1;
46
+ justify-content: space-between;
47
+ `;
48
+
49
+ const TextWrapper = styled.div`
50
+ display: flex;
51
+ flex-direction: column;
52
+ flex: 1;
53
+ margin: ${spacing.small} ${spacing.small} ${spacing.small} ${spacing.normal};
54
+ max-width: 800px;
55
+ transition: all ${animations.durations.fast} ease-in-out;
56
+ ${Container}:hover & {
57
+ margin: calc(${spacing.small} + 2px) calc(${spacing.small} + 2px) calc(${spacing.small} + 2px)
58
+ calc(${spacing.normal} + 2px);
59
+ }
60
+ `;
61
+ const ContentTypeWrapper = styled.div`
62
+ display: flex;
63
+ align-items: center;
64
+ ${fonts.sizes('12px', '16px')};
65
+ font-weight: ${fonts.weight.semibold};
66
+ `;
67
+ const ContentTypeIconWrapper = styled.div`
68
+ margin-right: ${spacing.small};
69
+ `;
70
+
71
+ const ItemTitleWrapper = styled.div`
72
+ margin: ${spacing.small} 0 ${spacing.xsmall};
73
+ `;
74
+
75
+ const ItemTitle = styled.h3`
76
+ ${fonts.sizes('24px', '28px')};
77
+ color: ${colors.brand.primary};
78
+ font-weight: ${fonts.weight.semibold};
79
+ overflow-wrap: anywhere;
80
+ display: inline;
81
+ transition: all ${animations.durations.fast} ease-in-out;
82
+ ${Container}:hover & {
83
+ box-shadow: inset 0 -1px;
84
+ background-color: transparent;
85
+ }
86
+ `;
87
+
88
+ const ItemText = styled.div`
89
+ ${fonts.sizes('16px', '24px')};
90
+ flex: 1 auto;
91
+ `;
92
+
93
+ const ImageWrapper = styled.div<{ isTopic: boolean }>`
94
+ float: right;
95
+ position: relative;
96
+ width: ${(props) => (props.isTopic ? `164px` : `224px`)};
97
+ height: 164px;
98
+ display: flex;
99
+ justify-content: center;
100
+ align-items: center;
101
+ margin: ${spacing.small};
102
+ transition: all ${animations.durations.fast} ease-in-out;
103
+ ${Container}:hover & {
104
+ width: ${(props) => (props.isTopic ? `168px` : `228px`)};
105
+ height: 168px;
106
+ }
107
+ `;
108
+
109
+ const ImageElement = styled.img<{ isTopic: boolean }>`
110
+ border-radius: 2px;
111
+ width: 100%;
112
+ height: 100%;
113
+ object-fit: cover;
114
+ transition: all ${animations.durations.fast} ease-in-out;
115
+ max-width: unset;
116
+ ${(props) => props.isTopic && `border-radius: 50%;`};
117
+ `;
118
+
119
+ const ContextWrapper = styled.div`
120
+ transition: all ${animations.durations.fast} ease-in-out;
121
+ `;
122
+
123
+ const SearchItemList = ({ item, type }: SearchItemType) => {
124
+ const { t } = useTranslation();
125
+ const { id, title, url, ingress, contexts, img = null, labels = [] } = item;
126
+ const isTopic = type === contentTypes.TOPIC || type === contentTypes.MULTIDISCIPLINARY_TOPIC;
127
+ return (
128
+ <Container>
129
+ <ItemLink to={url}>
130
+ <TextWrapper>
131
+ {type && (
132
+ <ContentTypeWrapper>
133
+ <ContentTypeIconWrapper>
134
+ <ContentTypeBadge type={type} background border={false} />
135
+ </ContentTypeIconWrapper>
136
+ {t(`contentTypes.${type}`)}
137
+ {labels.length > 0 && (
138
+ <>
139
+ {labels.map((label) => (
140
+ <Fragment key={label}>
141
+ {' '}
142
+ <>&#8226;</> {label}
143
+ </Fragment>
144
+ ))}
145
+ </>
146
+ )}
147
+ </ContentTypeWrapper>
148
+ )}
149
+ <ItemTitleWrapper>
150
+ <ItemTitle>{title}</ItemTitle>
151
+ </ItemTitleWrapper>
152
+ <ItemText>{parse(ingress)}</ItemText>
153
+ <ContextWrapper>
154
+ {contexts && contexts.length > 0 && <ItemContexts contexts={contexts} id={id} title={title} />}
155
+ </ContextWrapper>
156
+ </TextWrapper>
157
+ {img && (
158
+ <ImageWrapper isTopic={isTopic}>
159
+ <ImageElement src={img.url} alt={img.alt} isTopic={isTopic} />
160
+ </ImageWrapper>
161
+ )}
162
+ </ItemLink>
163
+ </Container>
164
+ );
165
+ };
166
+
167
+ export default SearchItemList;
@@ -9,8 +9,9 @@
9
9
  import React, { memo } from 'react';
10
10
  import styled from '@emotion/styled';
11
11
  import { breakpoints, mq, spacing } from '@ndla/core';
12
- import SearchItem, { SearchItemType } from './SearchItem';
12
+ import SearchItem, { SearchItemProps } from './SearchItem';
13
13
  import { ContentType } from './SearchTypeResult';
14
+ import SearchItemList from './SearchItemList';
14
15
 
15
16
  const Wrapper = styled.div`
16
17
  width: 100%;
@@ -18,38 +19,44 @@ const Wrapper = styled.div`
18
19
  `;
19
20
 
20
21
  type ContainerProps = {
21
- itemCount: number;
22
- type?: string;
22
+ viewType: Props['viewType'];
23
23
  };
24
24
 
25
25
  const Container = styled.div<ContainerProps>`
26
26
  display: grid;
27
27
  row-gap: ${spacing.normal};
28
28
  grid-template-columns: repeat(1, 1fr);
29
- ${mq.range({ from: breakpoints.tablet })} {
29
+
30
+ ${(props) =>
31
+ props.viewType === 'grid' &&
32
+ `
33
+ ${mq.range({ from: breakpoints.tablet })} {
30
34
  column-gap: ${spacing.normal};
31
35
  grid-template-columns: repeat(2, 1fr);
32
36
  }
33
- ${mq.range({ from: breakpoints.tabletWide })} {
34
- grid-template-columns: repeat(3, 1fr);
35
- }
37
+
36
38
  ${mq.range({ from: breakpoints.desktop })} {
37
- grid-template-columns: repeat(4, 1fr);
38
- }
39
+ grid-template-columns: repeat(3, 1fr);
40
+ }`}
39
41
  `;
40
42
 
41
43
  type Props = {
42
- items: SearchItemType[];
44
+ items: SearchItemProps[];
43
45
  type?: ContentType;
46
+ viewType?: 'grid' | 'list';
47
+ };
48
+ const SearchItems = ({ items, type, viewType = 'grid' }: Props) => {
49
+ return (
50
+ <Wrapper>
51
+ <Container viewType={viewType}>
52
+ {items.map((item) => {
53
+ const contentType = type || item.type;
54
+ const Component = viewType === 'list' ? SearchItemList : SearchItem;
55
+ return <Component item={item} key={`${item.id}`} type={contentType} />;
56
+ })}
57
+ </Container>
58
+ </Wrapper>
59
+ );
44
60
  };
45
- const SearchItems = ({ items, type }: Props) => (
46
- <Wrapper>
47
- <Container itemCount={items.length} type={type}>
48
- {items.map((item: any) => (
49
- <SearchItem item={item} key={`${item.id}`} type={type} />
50
- ))}
51
- </Container>
52
- </Wrapper>
53
- );
54
61
 
55
62
  export default memo(SearchItems);