@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,12 +6,11 @@
6
6
  *
7
7
  */
8
8
 
9
- import React, { useEffect, useRef, useState } from 'react';
9
+ import React, { useState } from 'react';
10
10
  import styled from '@emotion/styled';
11
11
  import Modal, { ModalCloseButton, ModalBody } from '@ndla/modal';
12
12
  import { breakpoints, fonts, mq, spacing } from '@ndla/core';
13
13
  import Button from '@ndla/button';
14
- import { Plus as PlusIcon } from '@ndla/icons/action';
15
14
  import { useTranslation } from 'react-i18next';
16
15
  // @ts-ignore
17
16
  import { ToggleItem } from '../Filter';
@@ -38,12 +37,6 @@ const ModalContent = styled.div`
38
37
  flex-grow: 1;
39
38
  `;
40
39
 
41
- const FilterButtonText = styled.span`
42
- display: inline-block;
43
- font-weight: 600;
44
- margin-right: 10px;
45
- `;
46
-
47
40
  const MainFilterButtonWrapper = styled.div`
48
41
  display: inline-block;
49
42
  margin-right: ${spacing.xsmall};
@@ -71,165 +64,99 @@ const StyledListItem = styled.li`
71
64
  const MENU_PROGRAMMES = 'programmes';
72
65
  const MENU_ALL_SUBJECTS = 'allSubjects';
73
66
 
74
- type messagesProps = {
75
- buttonText: string;
76
- closeButton: string;
77
- filterLabel: string;
78
- };
79
-
80
67
  type ProgrammeOptionProps = {
81
68
  name: string;
82
69
  id: string;
83
70
  };
84
- type ProgrammeProps = {
85
- options: ProgrammeOptionProps[];
86
- values: string[];
87
- onProgrammeValuesChange: (values: string[]) => void;
88
- };
89
-
90
- type SubjectCategoriesProps = {
91
- categories: subjectsProps['categories'];
92
- values: string[];
93
- onSubjectValuesChange: (values: string[]) => void;
94
- };
95
71
 
96
72
  export type PopupFilterProps = {
97
- messages: messagesProps;
98
- programmes?: ProgrammeProps;
99
- subjectCategories?: SubjectCategoriesProps;
73
+ programmes?: { options: ProgrammeOptionProps[] };
74
+ subjectCategories?: { categories: subjectsProps['categories'] };
75
+ subjectValues: string[];
76
+ programmesValues: string[];
77
+ onClose: () => void;
78
+ onToggleSubject: (value: string) => void;
79
+ onToggleProgramme: (value: string) => void;
80
+ isOpen: boolean;
100
81
  };
101
82
 
102
- const PopupFilter = ({ messages, programmes, subjectCategories }: PopupFilterProps) => {
83
+ const PopupFilter = ({
84
+ programmes,
85
+ subjectCategories,
86
+ subjectValues,
87
+ programmesValues,
88
+ onToggleSubject,
89
+ onToggleProgramme,
90
+ onClose,
91
+ isOpen,
92
+ }: PopupFilterProps) => {
103
93
  const { t } = useTranslation();
104
94
  const [selectedMenu, setSelectedMenu] = useState(MENU_ALL_SUBJECTS);
105
- const [subjectValues, setSubjectValues] = useState<Array<string>>([]);
106
- const [programmesValues, setProgrammesValues] = useState<Array<string>>([]);
107
- const [isOpen, setIsOpen] = useState(false);
108
-
109
- const prevIsOpenRef = useRef<boolean>();
110
- useEffect(() => {
111
- prevIsOpenRef.current = isOpen;
112
- });
113
- const prevIsOpen = prevIsOpenRef.current;
114
-
115
- useEffect(() => {
116
- if (isOpen && isOpen !== prevIsOpen) {
117
- if (subjectCategories) {
118
- setSubjectValues([...subjectCategories.values]);
119
- }
120
- if (programmes) {
121
- setProgrammesValues([...programmes.values]);
122
- }
123
- }
124
- }, [isOpen, prevIsOpen, subjectCategories, programmes]);
125
-
126
- const onToggleSubject = (subjectId: string) => {
127
- let updatedFilter = [...subjectValues];
128
- if (updatedFilter.includes(subjectId)) {
129
- updatedFilter = subjectValues.filter((option) => option !== subjectId);
130
- } else {
131
- updatedFilter.push(subjectId);
132
- }
133
- setSubjectValues(updatedFilter);
134
- if (subjectCategories) {
135
- subjectCategories.onSubjectValuesChange(updatedFilter);
136
- }
137
- };
138
-
139
- const onToggleProgramme = (programmeId: string) => {
140
- let updatedFilter = [...programmesValues];
141
- if (updatedFilter.includes(programmeId)) {
142
- updatedFilter = programmesValues.filter((option) => option !== programmeId);
143
- } else {
144
- updatedFilter.push(programmeId);
145
- }
146
- setProgrammesValues(updatedFilter);
147
- if (programmes) {
148
- programmes.onProgrammeValuesChange(updatedFilter);
149
- }
150
- };
151
-
152
- const buttonContent = (
153
- <Button type="button" size="normal" light borderShape="rounded">
154
- <FilterButtonText>{messages.buttonText}</FilterButtonText>
155
- <PlusIcon />
156
- </Button>
157
- );
158
95
 
159
96
  return (
160
97
  <Modal
161
- activateButton={buttonContent}
98
+ controllable
162
99
  backgroundColor="white"
163
100
  animation="subtle"
164
101
  animationDuration={50}
165
- onClick={() => setIsOpen(true)}
166
- onClose={() => setIsOpen(false)}
102
+ onClose={onClose}
103
+ isOpen={isOpen}
167
104
  size="fullscreen">
168
- {(onClose: () => void) => (
169
- <>
170
- <ModalBody>
171
- <ModalWrapper>
172
- <ModalContent>
173
- <ModalHeaderWrapper>
174
- <ModalHeading>{messages.filterLabel}</ModalHeading>
175
- <ModalCloseButton
176
- onClick={() => {
177
- setIsOpen(false);
178
- onClose();
179
- }}
180
- title={messages.closeButton}
181
- />
182
- </ModalHeaderWrapper>
183
- {subjectCategories && programmes && (
184
- <MainFilterButtonWrapper>
185
- <Button
186
- onClick={() => setSelectedMenu(MENU_ALL_SUBJECTS)}
187
- lighter={selectedMenu !== MENU_ALL_SUBJECTS}
188
- size="normal"
189
- borderShape="rounded">
190
- {t('frontpageMenu.allsubjects')}
191
- </Button>
192
- </MainFilterButtonWrapper>
193
- )}
194
- {programmes && (
105
+ {() => (
106
+ <ModalBody>
107
+ <ModalWrapper>
108
+ <ModalContent>
109
+ <ModalHeaderWrapper>
110
+ <ModalHeading>{t('searchPage.searchFilterMessages.filterLabel')}</ModalHeading>
111
+ <ModalCloseButton onClick={() => onClose()} title={t('searchPage.close')} />
112
+ </ModalHeaderWrapper>
113
+ {subjectCategories && programmes && (
114
+ <MainFilterButtonWrapper>
195
115
  <Button
196
- onClick={() => setSelectedMenu(MENU_PROGRAMMES)}
197
- lighter={selectedMenu !== MENU_PROGRAMMES}
116
+ onClick={() => setSelectedMenu(MENU_ALL_SUBJECTS)}
117
+ lighter={selectedMenu !== MENU_ALL_SUBJECTS}
198
118
  size="normal"
199
119
  borderShape="rounded">
200
- {t('frontpageMenu.program')}
120
+ {t('frontpageMenu.allsubjects')}
201
121
  </Button>
202
- )}
203
- {selectedMenu === MENU_ALL_SUBJECTS && subjectCategories && (
204
- <FrontpageAllSubjects
205
- categories={subjectCategories.categories}
206
- selectedSubjects={subjectValues}
207
- onToggleSubject={onToggleSubject}
208
- subjectViewType="checkbox"
209
- />
210
- )}
211
- {selectedMenu === MENU_PROGRAMMES && programmes && (
212
- <StyledList>
213
- {programmes.options.map((item: ProgrammeOptionProps) => (
214
- <StyledListItem key={item.id}>
215
- <ToggleItem
216
- id={item.id}
217
- value={item.id}
218
- checked={programmesValues.includes(item.id)}
219
- label={item.name}
220
- component="div"
221
- onChange={() => {
222
- onToggleProgramme(item.id);
223
- }}
224
- />
225
- </StyledListItem>
226
- ))}
227
- </StyledList>
228
- )}
229
- </ModalContent>
230
- </ModalWrapper>
231
- </ModalBody>
232
- </>
122
+ </MainFilterButtonWrapper>
123
+ )}
124
+ {programmes && (
125
+ <Button
126
+ onClick={() => setSelectedMenu(MENU_PROGRAMMES)}
127
+ lighter={selectedMenu !== MENU_PROGRAMMES}
128
+ size="normal"
129
+ borderShape="rounded">
130
+ {t('frontpageMenu.program')}
131
+ </Button>
132
+ )}
133
+ {selectedMenu === MENU_ALL_SUBJECTS && subjectCategories && (
134
+ <FrontpageAllSubjects
135
+ categories={subjectCategories.categories}
136
+ selectedSubjects={subjectValues}
137
+ onToggleSubject={onToggleSubject}
138
+ subjectViewType="checkbox"
139
+ />
140
+ )}
141
+ {selectedMenu === MENU_PROGRAMMES && programmes && (
142
+ <StyledList>
143
+ {programmes.options.map((item: ProgrammeOptionProps) => (
144
+ <StyledListItem key={item.id}>
145
+ <ToggleItem
146
+ id={item.id}
147
+ value={item.id}
148
+ checked={programmesValues.includes(item.id)}
149
+ label={item.name}
150
+ component="div"
151
+ onChange={() => onToggleProgramme(item.id)}
152
+ />
153
+ </StyledListItem>
154
+ ))}
155
+ </StyledList>
156
+ )}
157
+ </ModalContent>
158
+ </ModalWrapper>
159
+ </ModalBody>
233
160
  )}
234
161
  </Modal>
235
162
  );
@@ -8,19 +8,21 @@
8
8
 
9
9
  import React from 'react';
10
10
  import styled from '@emotion/styled';
11
- import { ChevronDown } from '@ndla/icons/common';
12
11
  import Button from '@ndla/button';
13
- import { spacing } from '@ndla/core';
12
+ import { fonts, spacing, colors } from '@ndla/core';
14
13
  import { useTranslation } from 'react-i18next';
15
14
 
16
15
  const ResultNav = styled.div`
17
16
  font-size: 14px;
18
17
  font-weight: 600;
19
- margin: ${spacing.normal} 0;
18
+ margin: ${spacing.medium} 0;
19
+ display: flex;
20
+ flex-direction: column;
21
+ justify-content: center;
22
+ align-items: center;
20
23
  button.nav {
21
- margin-right: ${spacing.xsmall};
22
- font-size: 18px;
23
24
  font-weight: 600;
25
+ border-width: 1px;
24
26
  &:disabled {
25
27
  opacity: 0.8;
26
28
  }
@@ -28,33 +30,69 @@ const ResultNav = styled.div`
28
30
  `;
29
31
 
30
32
  const NavInfo = styled.div`
31
- font-size: 16px;
33
+ ${fonts.sizes('16px', '24px')};
32
34
  font-weight: 400;
33
35
  margin-bottom: ${spacing.small};
34
36
  `;
35
37
 
38
+ const ProgressBar = styled.div`
39
+ width: 200px;
40
+ height: 2px;
41
+ background: ${colors.brand.tertiary};
42
+ margin: 0 0 ${spacing.small};
43
+ `;
44
+
45
+ type ProgressType = {
46
+ width: number;
47
+ };
48
+ const Progress = styled.span<ProgressType>`
49
+ display: block;
50
+ background: ${colors.brand.primary};
51
+ height: 2px;
52
+ width: ${(props) => props.width}%;
53
+ `;
54
+
36
55
  export type PaginationType = {
37
56
  onShowMore: () => void;
38
57
  totalCount: number;
39
- fromCount?: number;
40
58
  toCount: number;
59
+ contentType?: string;
41
60
  };
42
61
 
43
- const ResultNavigation = ({ onShowMore, totalCount, fromCount = 1, toCount }: PaginationType) => {
62
+ const ResultNavigation = ({ onShowMore, totalCount, toCount, contentType = '' }: PaginationType) => {
44
63
  const { t } = useTranslation();
45
64
  const isMore = toCount < totalCount;
65
+ const progress = Math.ceil((toCount / totalCount) * 100);
66
+
67
+ const onToTopHandler = () => {
68
+ window.scrollTo({
69
+ top: 0,
70
+ left: 0,
71
+ behavior: 'smooth',
72
+ });
73
+ };
74
+
46
75
  return (
47
76
  <ResultNav>
48
77
  <NavInfo>
49
- {t('searchPage.resultType.showing', {
50
- fromCount,
51
- toCount,
52
- totalCount,
53
- })}
78
+ {isMore
79
+ ? t('searchPage.resultType.showing', {
80
+ count: toCount,
81
+ totalCount,
82
+ contentType,
83
+ })
84
+ : t('searchPage.resultType.showingAll')}
54
85
  </NavInfo>
55
- {isMore && (
56
- <Button className="nav" link onClick={onShowMore}>
57
- {t('searchPage.resultType.showMore')} <ChevronDown />
86
+ <ProgressBar>
87
+ <Progress width={progress} />
88
+ </ProgressBar>
89
+ {isMore ? (
90
+ <Button className="nav" outline onClick={onShowMore}>
91
+ {t('searchPage.resultType.showMore')}
92
+ </Button>
93
+ ) : (
94
+ <Button className="nav" outline onClick={onToTopHandler}>
95
+ {t('searchPage.resultType.toTopOfPage')}
58
96
  </Button>
59
97
  )}
60
98
  </ResultNav>
@@ -5,18 +5,15 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  */
8
-
9
- import React, { FormEvent, useEffect, useRef, useState } from 'react';
8
+ import React, { FormEvent, useRef, useState } from 'react';
10
9
  import styled from '@emotion/styled';
11
- import { colors, breakpoints, mq } from '@ndla/core';
10
+ import { colors } from '@ndla/core';
12
11
  import { Search as SearchIcon } from '@ndla/icons/common';
13
12
  import { spacing } from '@ndla/core';
14
13
  import { Cross as CrossIcon } from '@ndla/icons/action';
15
14
 
16
15
  import { useTranslation } from 'react-i18next';
17
- import ActiveFilters from './ActiveFilters';
18
- import PopupFilter, { PopupFilterProps } from './PopupFilter';
19
- import { FilterProps } from './ActiveFilterContent';
16
+ import SubjectFilters, { SubjectFilterProps } from './components/SubjectFilters';
20
17
 
21
18
  type StyledProps = {
22
19
  inputHasFocus?: boolean;
@@ -42,13 +39,6 @@ const StyledForm = styled.form<StyledProps>`
42
39
  }
43
40
  `;
44
41
 
45
- const HideOnNarrowScreen = styled.div`
46
- display: none;
47
- ${mq.range({ from: breakpoints.desktop })} {
48
- display: block;
49
- }
50
- `;
51
-
52
42
  const SearchButton = styled.button`
53
43
  width: 40px;
54
44
  height: 40px;
@@ -82,39 +72,26 @@ type Props = {
82
72
  onSubmit: (event: FormEvent<HTMLFormElement>) => void;
83
73
  value?: string;
84
74
  onChange: (value: string) => void;
85
- filters?: PopupFilterProps;
86
- activeFilters?: {
87
- filters: FilterProps[];
88
- onFilterRemove: (value: string, name: string) => void;
89
- };
75
+ isNarrowScreen?: boolean;
90
76
  };
91
77
 
92
- const SearchFieldHeader = ({ value, onSubmit, onChange, filters, activeFilters }: Props) => {
78
+ const SearchFieldHeader = ({
79
+ value,
80
+ onSubmit,
81
+ onChange,
82
+ filters,
83
+ activeFilters,
84
+ isNarrowScreen,
85
+ }: Props & SubjectFilterProps) => {
93
86
  const { t } = useTranslation();
94
87
  const [hasFocus, setHasFocus] = useState(false);
95
- const [isNarrowScreen, setIsNarrowScreen] = useState(false);
96
88
  const inputRef = useRef<HTMLInputElement>(null);
97
89
 
98
- useEffect(() => {
99
- const isNarrowScreenMatch = window.matchMedia(`(max-width: ${breakpoints.tablet})`);
100
- const handleChange = (e: MediaQueryListEvent | MediaQueryList) => {
101
- setIsNarrowScreen(e.matches);
102
- };
103
- isNarrowScreenMatch.addEventListener('change', handleChange);
104
- handleChange(isNarrowScreenMatch);
105
- return () => {
106
- isNarrowScreenMatch.removeEventListener('change', handleChange);
107
- };
108
- }, []);
109
-
110
90
  return (
111
91
  <StyledForm action="/search/" inputHasFocus={hasFocus} onSubmit={onSubmit}>
112
- {filters && (
113
- <HideOnNarrowScreen>
114
- <PopupFilter {...filters} />
115
- </HideOnNarrowScreen>
92
+ {!isNarrowScreen && (
93
+ <SubjectFilters filters={filters} activeFilters={activeFilters} isNarrowScreen={isNarrowScreen} />
116
94
  )}
117
- {activeFilters && <ActiveFilters {...activeFilters} />}
118
95
  <SearchInput
119
96
  ref={inputRef}
120
97
  type="search"
@@ -136,9 +113,7 @@ const SearchFieldHeader = ({ value, onSubmit, onChange, filters, activeFilters }
136
113
  value={t<string>('welcomePage.resetSearch')}
137
114
  onClick={() => {
138
115
  onChange('');
139
- if (inputRef && inputRef.current) {
140
- inputRef.current.focus();
141
- }
116
+ inputRef.current?.focus();
142
117
  }}>
143
118
  <CrossIcon style={iconStyle} title={t<string>('welcomePage.resetSearch')} />
144
119
  </ClearButton>
@@ -0,0 +1,63 @@
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 from 'react';
10
+ import { useTranslation } from 'react-i18next';
11
+ import styled from '@emotion/styled';
12
+ import { spacing } from '@ndla/core';
13
+
14
+ import SearchViewType, { SearchViewTypeProps } from './SearchViewType';
15
+ // @ts-ignore
16
+ import { FilterButtons } from '../Filter';
17
+ import { FilterButtonsProps } from '../Filter/FilterButtons';
18
+
19
+ const Container = styled.div`
20
+ display: flex;
21
+ align-items: flex-start;
22
+ box-sizing: border-box;
23
+ max-width: 100%;
24
+ `;
25
+
26
+ const FilterButtonsWrapper = styled.div`
27
+ padding-top: 2px;
28
+ margin-right: ${spacing.small};
29
+ overflow-x: hidden;
30
+ margin-left: -13px;
31
+ `;
32
+
33
+ type Props = {
34
+ items: FilterButtonsProps['items'];
35
+ onFilterToggle: FilterButtonsProps['onFilterToggle'];
36
+ onRemoveAllFilters: FilterButtonsProps['onRemoveAllFilters'];
37
+ };
38
+ const SearchFilterContent = ({
39
+ viewType,
40
+ onChangeViewType,
41
+ items,
42
+ onFilterToggle,
43
+ onRemoveAllFilters,
44
+ }: SearchViewTypeProps & Props) => {
45
+ const { t } = useTranslation();
46
+ return (
47
+ <Container>
48
+ <FilterButtonsWrapper>
49
+ <FilterButtons
50
+ items={items}
51
+ onFilterToggle={onFilterToggle}
52
+ onRemoveAllFilters={onRemoveAllFilters}
53
+ labels={{
54
+ openFilter: t('searchPage.searchFilterMessages.resourceTypeFilter.button'),
55
+ }}
56
+ />
57
+ </FilterButtonsWrapper>
58
+ <SearchViewType viewType={viewType} onChangeViewType={onChangeViewType} />
59
+ </Container>
60
+ );
61
+ };
62
+
63
+ export default SearchFilterContent;
@@ -5,19 +5,16 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  */
8
-
9
- import React, { FormEvent } from 'react';
8
+ import React, { FormEvent, useEffect, useState } from 'react';
9
+ import { WithTranslation, withTranslation } from 'react-i18next';
10
10
  import styled from '@emotion/styled';
11
11
  import { breakpoints, fonts, mq, spacing } from '@ndla/core';
12
-
13
12
  import Button from '@ndla/button';
14
- import { WithTranslation, withTranslation } from 'react-i18next';
15
- import { FilterProps } from './ActiveFilterContent';
16
- import ActiveFilters from './ActiveFilters';
13
+
17
14
  import SearchFieldHeader from './SearchFieldHeader';
18
- import PopupFilter, { PopupFilterProps } from './PopupFilter';
19
15
  import { CompetenceGoalsItemType } from '../types';
20
16
  import CompetenceGoalItem from '../CompetenceGoalTab/CompetenceGoalItem';
17
+ import SubjectFilters, { SubjectFilterProps } from './components/SubjectFilters';
21
18
 
22
19
  const Wrapper = styled.div`
23
20
  margin-top: ${spacing.normal};
@@ -58,13 +55,6 @@ const PhraseText = styled.div`
58
55
  `;
59
56
  const PhraseSuggestionText = styled.div``;
60
57
 
61
- const HideOnDesktopWrapper = styled.div`
62
- display: none;
63
- ${mq.range({ until: breakpoints.desktop })} {
64
- display: block;
65
- }
66
- `;
67
-
68
58
  const GoalsLabel = styled.div`
69
59
  ${fonts.sizes('16px', '32px')};
70
60
  text-transform: uppercase;
@@ -75,11 +65,8 @@ type Props = {
75
65
  searchPhraseSuggestion?: string;
76
66
  searchPhraseSuggestionOnClick?: () => void;
77
67
  searchValue?: string;
78
- filters?: PopupFilterProps;
79
- activeFilters?: {
80
- filters: FilterProps[];
81
- onFilterRemove: (value: string, name: string) => void;
82
- };
68
+ filters: SubjectFilterProps['filters'];
69
+ activeFilters?: SubjectFilterProps['activeFilters'];
83
70
  competenceGoals?: CompetenceGoalsItemType[];
84
71
  onSearchValueChange: (value: string) => void;
85
72
  onSubmit: (event: FormEvent<HTMLFormElement>) => void;
@@ -99,9 +86,27 @@ const SearchHeader = ({
99
86
  noResults,
100
87
  t,
101
88
  }: Props & WithTranslation) => {
102
- const phraseText = noResults
103
- ? t('searchPage.noHitsShort', { query: searchPhrase })
104
- : `${t('searchPage.resultType.showingSearchPhrase')} ${searchPhrase}`;
89
+ const [isNarrowScreen, setIsNarrowScreen] = useState<boolean | undefined>();
90
+
91
+ useEffect(() => {
92
+ const isNarrowScreenMatch = window.matchMedia(`(max-width: ${breakpoints.desktop})`);
93
+ const handleChange = (e: MediaQueryListEvent | MediaQueryList) => {
94
+ setIsNarrowScreen(e.matches);
95
+ };
96
+ isNarrowScreenMatch.addEventListener('change', handleChange);
97
+ handleChange(isNarrowScreenMatch);
98
+ return () => {
99
+ isNarrowScreenMatch.removeEventListener('change', handleChange);
100
+ };
101
+ }, []);
102
+
103
+ const phraseText = noResults ? (
104
+ t('searchPage.noHitsShort', { query: searchPhrase })
105
+ ) : (
106
+ <>
107
+ {t('searchPage.resultType.showingSearchPhrase')} <b>&ldquo;{searchPhrase}&rdquo;</b>
108
+ </>
109
+ );
105
110
  const removeFilterSuggestion =
106
111
  noResults && activeFilters?.filters.length ? t('searchPage.removeFilterSuggestion') : undefined;
107
112
  return (
@@ -113,6 +118,7 @@ const SearchHeader = ({
113
118
  onSubmit={onSubmit}
114
119
  activeFilters={activeFilters}
115
120
  filters={filters}
121
+ isNarrowScreen={isNarrowScreen}
116
122
  />
117
123
  </SearchInputWrapper>
118
124
  <PhraseWrapper>
@@ -126,7 +132,7 @@ const SearchHeader = ({
126
132
  <PhraseSuggestionText>
127
133
  {t('searchPage.resultType.searchPhraseSuggestion')}{' '}
128
134
  <Button link onClick={searchPhraseSuggestionOnClick}>
129
- {searchPhraseSuggestion}
135
+ [{searchPhraseSuggestion}]
130
136
  </Button>
131
137
  </PhraseSuggestionText>
132
138
  )}
@@ -145,14 +151,8 @@ const SearchHeader = ({
145
151
  </CompetenceGoalsWrapper>
146
152
  )}
147
153
  </PhraseWrapper>
148
- {activeFilters && (
149
- <HideOnDesktopWrapper>
150
- <ActiveFilters
151
- {...activeFilters}
152
- showOnSmallScreen
153
- customElements={filters ? [<PopupFilter {...filters} />] : []}
154
- />
155
- </HideOnDesktopWrapper>
154
+ {isNarrowScreen && (
155
+ <SubjectFilters filters={filters} activeFilters={activeFilters} isNarrowScreen={isNarrowScreen} />
156
156
  )}
157
157
  </Wrapper>
158
158
  );