@ndla/ui 4.3.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) 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/ContentTypeBadge/ContentTypeBadge.js +10 -1
  6. package/es/FactBox/FactBox.js +36 -11
  7. package/es/Filter/FilterButtons.js +18 -17
  8. package/es/Filter/FilterCarousel.js +198 -0
  9. package/es/Masthead/Masthead.js +24 -2
  10. package/es/Notion/ConceptNotion.js +57 -0
  11. package/es/Notion/FigureNotion.js +84 -0
  12. package/es/Notion/Notion.js +122 -48
  13. package/es/Notion/NotionImage.js +47 -0
  14. package/es/Notion/NotionVisualElement.js +38 -0
  15. package/es/Notion/index.js +2 -1
  16. package/es/SearchTypeResult/ActiveFilterContent.js +17 -2
  17. package/es/SearchTypeResult/ActiveFilters.js +64 -50
  18. package/es/SearchTypeResult/PopupFilter.js +28 -125
  19. package/es/SearchTypeResult/ResultNavigation.js +41 -14
  20. package/es/SearchTypeResult/SearchFieldHeader.js +20 -41
  21. package/es/SearchTypeResult/SearchFilterContent.js +61 -0
  22. package/es/SearchTypeResult/SearchHeader.js +51 -28
  23. package/es/SearchTypeResult/SearchItem.js +64 -190
  24. package/es/SearchTypeResult/SearchItemList.js +132 -0
  25. package/es/SearchTypeResult/SearchItems.js +17 -13
  26. package/es/SearchTypeResult/SearchNotionItem.js +13 -13
  27. package/es/SearchTypeResult/SearchNotionsResult.js +16 -21
  28. package/es/SearchTypeResult/SearchTypeHeader.js +43 -26
  29. package/es/SearchTypeResult/SearchTypeResult.js +7 -5
  30. package/es/SearchTypeResult/SearchViewType.js +93 -0
  31. package/es/SearchTypeResult/components/ItemContexts.js +125 -0
  32. package/es/SearchTypeResult/components/ItemResourceHeader.js +87 -0
  33. package/es/SearchTypeResult/components/ItemTopicHeader.js +56 -0
  34. package/es/SearchTypeResult/components/SubjectFilters.js +177 -0
  35. package/es/SearchTypeResult/index.js +2 -1
  36. package/es/all.css +1 -1
  37. package/es/index.js +3 -2
  38. package/es/locale/messages-en.js +21 -6
  39. package/es/locale/messages-nb.js +21 -6
  40. package/es/locale/messages-nn.js +21 -6
  41. package/es/model/ContentType.js +2 -1
  42. package/es/shapes.js +1 -1
  43. package/lib/Article/Article.d.ts +4 -5
  44. package/lib/Article/Article.js +3 -7
  45. package/lib/Article/ArticleByline.js +4 -4
  46. package/lib/Article/ArticleNotions.d.ts +3 -8
  47. package/lib/Article/ArticleNotions.js +41 -90
  48. package/lib/ContentTypeBadge/ContentTypeBadge.d.ts +1 -0
  49. package/lib/ContentTypeBadge/ContentTypeBadge.js +14 -2
  50. package/lib/FactBox/FactBox.js +41 -8
  51. package/lib/Filter/FilterButtons.d.ts +3 -10
  52. package/lib/Filter/FilterButtons.js +19 -17
  53. package/lib/Filter/FilterCarousel.d.ts +13 -0
  54. package/lib/Filter/FilterCarousel.js +207 -0
  55. package/lib/Masthead/Masthead.js +30 -2
  56. package/lib/Notion/ConceptNotion.d.ts +25 -0
  57. package/lib/Notion/ConceptNotion.js +79 -0
  58. package/lib/Notion/FigureNotion.d.ts +23 -0
  59. package/lib/Notion/FigureNotion.js +99 -0
  60. package/lib/Notion/Notion.d.ts +24 -11
  61. package/lib/Notion/Notion.js +120 -48
  62. package/lib/Notion/NotionImage.d.ts +15 -0
  63. package/lib/Notion/NotionImage.js +63 -0
  64. package/lib/Notion/NotionVisualElement.d.ts +22 -0
  65. package/lib/Notion/NotionVisualElement.js +51 -0
  66. package/lib/Notion/index.d.ts +1 -0
  67. package/lib/Notion/index.js +8 -0
  68. package/lib/SearchTypeResult/ActiveFilterContent.js +16 -9
  69. package/lib/SearchTypeResult/ActiveFilters.d.ts +2 -1
  70. package/lib/SearchTypeResult/ActiveFilters.js +65 -50
  71. package/lib/SearchTypeResult/PopupFilter.d.ts +13 -19
  72. package/lib/SearchTypeResult/PopupFilter.js +27 -123
  73. package/lib/SearchTypeResult/ResultNavigation.d.ts +2 -2
  74. package/lib/SearchTypeResult/ResultNavigation.js +38 -14
  75. package/lib/SearchTypeResult/SearchFieldHeader.d.ts +3 -8
  76. package/lib/SearchTypeResult/SearchFieldHeader.js +18 -39
  77. package/lib/SearchTypeResult/SearchFilterContent.d.ts +16 -0
  78. package/lib/SearchTypeResult/SearchFilterContent.js +67 -0
  79. package/lib/SearchTypeResult/SearchHeader.d.ts +3 -7
  80. package/lib/SearchTypeResult/SearchHeader.js +59 -30
  81. package/lib/SearchTypeResult/SearchItem.d.ts +8 -12
  82. package/lib/SearchTypeResult/SearchItem.js +64 -187
  83. package/lib/SearchTypeResult/SearchItemList.d.ts +10 -0
  84. package/lib/SearchTypeResult/SearchItemList.js +139 -0
  85. package/lib/SearchTypeResult/SearchItems.d.ts +4 -3
  86. package/lib/SearchTypeResult/SearchItems.js +18 -13
  87. package/lib/SearchTypeResult/SearchNotionItem.js +13 -13
  88. package/lib/SearchTypeResult/SearchNotionsResult.d.ts +2 -4
  89. package/lib/SearchTypeResult/SearchNotionsResult.js +23 -23
  90. package/lib/SearchTypeResult/SearchTypeHeader.d.ts +2 -2
  91. package/lib/SearchTypeResult/SearchTypeHeader.js +40 -25
  92. package/lib/SearchTypeResult/SearchTypeResult.d.ts +7 -6
  93. package/lib/SearchTypeResult/SearchTypeResult.js +7 -5
  94. package/lib/SearchTypeResult/SearchViewType.d.ts +13 -0
  95. package/lib/SearchTypeResult/SearchViewType.js +99 -0
  96. package/lib/SearchTypeResult/components/ItemContexts.d.ts +19 -0
  97. package/lib/SearchTypeResult/components/ItemContexts.js +134 -0
  98. package/lib/SearchTypeResult/components/ItemResourceHeader.d.ts +16 -0
  99. package/lib/SearchTypeResult/components/ItemResourceHeader.js +98 -0
  100. package/lib/SearchTypeResult/components/ItemTopicHeader.d.ts +17 -0
  101. package/lib/SearchTypeResult/components/ItemTopicHeader.js +67 -0
  102. package/lib/SearchTypeResult/components/SubjectFilters.d.ts +32 -0
  103. package/lib/SearchTypeResult/components/SubjectFilters.js +192 -0
  104. package/lib/SearchTypeResult/index.d.ts +2 -1
  105. package/lib/SearchTypeResult/index.js +8 -0
  106. package/lib/all.css +1 -1
  107. package/lib/index.d.ts +2 -1
  108. package/lib/index.js +24 -1
  109. package/lib/locale/messages-en.d.ts +16 -1
  110. package/lib/locale/messages-en.js +21 -6
  111. package/lib/locale/messages-nb.d.ts +15 -0
  112. package/lib/locale/messages-nb.js +21 -6
  113. package/lib/locale/messages-nn.d.ts +16 -1
  114. package/lib/locale/messages-nn.js +21 -6
  115. package/lib/model/ContentType.d.ts +1 -0
  116. package/lib/model/ContentType.js +4 -2
  117. package/lib/shapes.js +1 -1
  118. package/lib/types.d.ts +1 -0
  119. package/package.json +14 -14
  120. package/src/Article/Article.tsx +8 -15
  121. package/src/Article/ArticleByline.tsx +1 -1
  122. package/src/Article/ArticleNotions.tsx +13 -33
  123. package/src/ContentTypeBadge/ContentTypeBadge.tsx +8 -0
  124. package/src/ContentTypeBadge/component.content-type-badge.scss +5 -0
  125. package/src/FactBox/FactBox.tsx +22 -15
  126. package/src/Figure/component.figure.scss +1 -1
  127. package/src/Filter/FilterButtons.tsx +14 -15
  128. package/src/Filter/FilterCarousel.tsx +166 -0
  129. package/src/Masthead/Masthead.tsx +42 -18
  130. package/src/Notion/ConceptNotion.tsx +80 -0
  131. package/src/Notion/FigureNotion.tsx +88 -0
  132. package/src/Notion/Notion.tsx +205 -75
  133. package/src/Notion/NotionImage.tsx +51 -0
  134. package/src/Notion/NotionVisualElement.tsx +50 -0
  135. package/src/Notion/index.ts +1 -0
  136. package/src/SearchTypeResult/ActiveFilterContent.tsx +7 -2
  137. package/src/SearchTypeResult/ActiveFilters.tsx +72 -38
  138. package/src/SearchTypeResult/PopupFilter.tsx +73 -146
  139. package/src/SearchTypeResult/ResultNavigation.tsx +54 -16
  140. package/src/SearchTypeResult/SearchFieldHeader.tsx +15 -40
  141. package/src/SearchTypeResult/SearchFilterContent.tsx +63 -0
  142. package/src/SearchTypeResult/SearchHeader.tsx +31 -31
  143. package/src/SearchTypeResult/SearchItem.tsx +145 -233
  144. package/src/SearchTypeResult/SearchItemList.tsx +167 -0
  145. package/src/SearchTypeResult/SearchItems.tsx +26 -19
  146. package/src/SearchTypeResult/SearchNotionItem.tsx +5 -12
  147. package/src/SearchTypeResult/SearchNotionsResult.tsx +29 -22
  148. package/src/SearchTypeResult/SearchTypeHeader.tsx +51 -33
  149. package/src/SearchTypeResult/SearchTypeResult.tsx +13 -12
  150. package/src/SearchTypeResult/SearchViewType.tsx +109 -0
  151. package/src/SearchTypeResult/components/ItemContexts.tsx +138 -0
  152. package/src/SearchTypeResult/components/ItemResourceHeader.tsx +133 -0
  153. package/src/SearchTypeResult/components/ItemTopicHeader.tsx +95 -0
  154. package/src/SearchTypeResult/components/SubjectFilters.tsx +152 -0
  155. package/src/SearchTypeResult/index.ts +9 -1
  156. package/src/index.ts +3 -0
  157. package/src/locale/messages-en.ts +19 -4
  158. package/src/locale/messages-nb.ts +19 -4
  159. package/src/locale/messages-nn.ts +19 -4
  160. package/src/model/ContentType.ts +1 -0
  161. package/src/shapes.js +1 -0
  162. package/src/types.ts +1 -0
@@ -7,31 +7,39 @@
7
7
  */
8
8
 
9
9
  import React, { ReactElement } from 'react';
10
+ import { useTranslation } from 'react-i18next';
10
11
  import styled from '@emotion/styled';
11
12
  import css from '@emotion/css';
12
13
  import { spacing, mq, breakpoints } from '@ndla/core';
13
14
  import Tooltip from '@ndla/tooltip';
14
- import { useTranslation } from 'react-i18next';
15
+ import Button from '@ndla/button';
15
16
  import ActiveFilterContent, { FilterProps, StyledActiveFilterTitle } from './ActiveFilterContent';
16
17
 
17
18
  type StyledActiveFiltersProps = {
18
19
  showOnSmallScreen?: boolean;
19
20
  filterLength?: number;
20
21
  };
22
+
21
23
  const StyledActiveFilters = styled.ul<StyledActiveFiltersProps>`
22
24
  margin: 0;
23
25
  padding: 0;
24
26
  display: none;
25
-
26
- ${({ showOnSmallScreen }) =>
27
- showOnSmallScreen &&
28
- `
29
- ${mq.range({ until: breakpoints.desktop })} {
27
+ ${(props: StyledActiveFiltersProps) =>
28
+ props.showOnSmallScreen &&
29
+ css`
30
+ ${StyledActiveFilterTitle} {
31
+ text-overflow: ellipsis;
32
+ overflow: hidden;
33
+ padding-right: ${spacing.small};
34
+ display: block;
35
+ /* max-width: 200px; */
36
+ }
37
+ ${mq.range({ until: breakpoints.desktop })} {
30
38
  display: flex;
31
39
  flex-direction: row;
32
40
  flex-wrap: wrap;
33
41
  }
34
- ${mq.range({ until: breakpoints.tablet })} {
42
+ ${mq.range({ until: breakpoints.tablet })} {
35
43
  display: flex;
36
44
  flex-direction: column;
37
45
  align-items: stretch;
@@ -88,46 +96,72 @@ type Props = {
88
96
  onFilterRemove: (value: string, name: string) => void;
89
97
  showOnSmallScreen?: boolean;
90
98
  customElements?: ReactElement[];
99
+ onClickShowHiddenSubjects: () => void;
91
100
  };
92
101
 
93
- const ActiveFilters = ({ filters, onFilterRemove, showOnSmallScreen, customElements }: Props) => {
102
+ const ActiveFilters = ({
103
+ filters,
104
+ onFilterRemove,
105
+ showOnSmallScreen,
106
+ customElements,
107
+ onClickShowHiddenSubjects,
108
+ }: Props) => {
94
109
  const { t } = useTranslation();
95
- if (filters && filters.length > 0) {
96
- const filterLength = filters.length;
110
+ if (!filters) return null;
111
+ const showFilterCount = 3;
112
+ const filterLength = filters.length;
97
113
 
98
- const filterItems = filters.map((filter) => {
99
- const filterKey = filter.name ? `${filter.name}${filter.value}` : filter.value;
114
+ const visibleFilters = filterLength > showFilterCount ? filters.slice(0, showFilterCount - 1) : filters;
100
115
 
101
- return (
102
- <StyledActiveFilterWrapper key={filterKey}>
103
- {filterLength > 1 ? (
104
- <Tooltip
105
- delay={2000}
106
- align="bottom"
107
- tooltip={t('searchPage.searchFilterMessages.removeFilter', {
108
- filterName: filter.title,
109
- })}>
110
- <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
111
- </Tooltip>
112
- ) : (
113
- <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
114
- )}
115
- </StyledActiveFilterWrapper>
116
- );
117
- });
116
+ const filterItems = visibleFilters.map((filter) => {
117
+ const filterKey = filter.name ? `${filter.name}${filter.value}` : filter.value;
118
118
 
119
119
  return (
120
- <StyledActiveFilters showOnSmallScreen={showOnSmallScreen} filterLength={filterLength}>
121
- {filterItems}
122
- {customElements &&
123
- customElements.map((item, index) => (
124
- <StyledActiveFilterWrapper key={index}>{item}</StyledActiveFilterWrapper>
125
- ))}
126
- </StyledActiveFilters>
120
+ <StyledActiveFilterWrapper key={filterKey}>
121
+ {filterLength > 1 ? (
122
+ <Tooltip
123
+ delay={2000}
124
+ align="bottom"
125
+ tooltip={t('searchPage.searchFilterMessages.removeFilter', {
126
+ filterName: filter.title,
127
+ })}>
128
+ <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
129
+ </Tooltip>
130
+ ) : (
131
+ <ActiveFilterContent filter={filter} onFilterRemove={onFilterRemove} />
132
+ )}
133
+ </StyledActiveFilterWrapper>
127
134
  );
128
- }
135
+ });
129
136
 
130
- return null;
137
+ return (
138
+ <StyledActiveFilters showOnSmallScreen={showOnSmallScreen} filterLength={filterLength}>
139
+ {filterItems}
140
+ {filterLength > showFilterCount && (
141
+ <StyledActiveFilterWrapper>
142
+ <Tooltip delay={2000} align="bottom" tooltip={t('searchPage.searchFilterMessages.noValuesButtonText')}>
143
+ <Button
144
+ aria-label={t('searchPage.searchFilterMessages.additionalSubjectFilters', {
145
+ count: filterLength - showFilterCount + 1,
146
+ })}
147
+ type="button"
148
+ size="normal"
149
+ borderShape="rounded"
150
+ onClick={onClickShowHiddenSubjects}>
151
+ <StyledActiveFilterTitle>
152
+ {t('searchPage.searchFilterMessages.additionalSubjectFilters', {
153
+ count: filterLength - showFilterCount + 1,
154
+ })}
155
+ </StyledActiveFilterTitle>
156
+ </Button>
157
+ </Tooltip>
158
+ </StyledActiveFilterWrapper>
159
+ )}
160
+ {customElements?.map((item, index) => (
161
+ <StyledActiveFilterWrapper key={index}>{item}</StyledActiveFilterWrapper>
162
+ ))}
163
+ </StyledActiveFilters>
164
+ );
131
165
  };
132
166
 
133
167
  export default ActiveFilters;
@@ -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;