@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
@@ -148,18 +148,11 @@ const SearchNotionItem = ({
148
148
  <Modal
149
149
  activateButton={
150
150
  <Button lighter size="xsmall" borderShape="rounded">
151
- {type === 'video' && (
152
- <>
153
- <Play />
154
- <ShowMediaButtonText>{t('searchPage.resultType.showVideo')}</ShowMediaButtonText>
155
- </>
156
- )}
157
- {type === 'other' && (
158
- <>
159
- <ArrowExpand />
160
- <ShowMediaButtonText>{t('searchPage.resultType.showNotion')}</ShowMediaButtonText>
161
- </>
162
- )}
151
+ <>
152
+ {type === 'video' && <Play />}
153
+ {type === 'other' && <ArrowExpand />}
154
+ <ShowMediaButtonText>{t('searchPage.resultType.showVideo')}</ShowMediaButtonText>
155
+ </>
163
156
  </Button>
164
157
  }
165
158
  animation="subtle"
@@ -7,11 +7,11 @@
7
7
  */
8
8
 
9
9
  import React, { ReactNode } from 'react';
10
+ import { useTranslation } from 'react-i18next';
10
11
  import styled from '@emotion/styled';
11
- import { fonts, mq, breakpoints, spacing } from '@ndla/core';
12
+ import { fonts, mq, breakpoints, spacing, colors } from '@ndla/core';
12
13
  import { ModalCloseButton } from '@ndla/modal';
13
14
  import { withTranslation, WithTranslation } from 'react-i18next';
14
- import SearchNotionItem, { SearchNotionItemProps } from './SearchNotionItem';
15
15
 
16
16
  const Wrapper = styled.div`
17
17
  display: flex;
@@ -19,12 +19,18 @@ const Wrapper = styled.div`
19
19
  padding: ${spacing.medium} 0;
20
20
  ${mq.range({ from: breakpoints.tablet })} {
21
21
  padding: ${spacing.medium};
22
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.24);
23
- border-radius: 5px;
22
+ border: 1px solid ${colors.brand.neutral7};
23
+ border-radius: 4px;
24
24
  margin: ${spacing.large} 0;
25
25
  }
26
26
  ${mq.range({ from: breakpoints.desktop })} {
27
- padding: ${spacing.medium} 66px;
27
+ padding: ${spacing.large};
28
+ }
29
+ & > .c-figure {
30
+ width: 100% !important;
31
+ padding: 0;
32
+ left: initial !important;
33
+ margin-bottom: 0;
28
34
  }
29
35
  `;
30
36
 
@@ -32,7 +38,8 @@ const HeadingWrapper = styled.div`
32
38
  display: flex;
33
39
  justify-content: space-between;
34
40
  align-items: center;
35
- margin-bottom: ${spacing.small};
41
+ border-bottom: 2px solid ${colors.brand.light};
42
+ padding-bottom: ${spacing.small};
36
43
  `;
37
44
  const Heading = styled.h2`
38
45
  margin: 0;
@@ -47,25 +54,25 @@ const HeadingCount = styled.span`
47
54
  `;
48
55
 
49
56
  type Props = {
50
- items: SearchNotionItemProps[];
51
57
  totalCount: number;
52
58
  onRemove: () => void;
53
- renderMarkdown: (text: ReactNode) => string;
59
+ children: ReactNode;
54
60
  };
55
61
 
56
- const SearchNotionsResult = ({ items, totalCount, onRemove, renderMarkdown, t }: Props & WithTranslation) => (
57
- <Wrapper>
58
- <HeadingWrapper>
59
- <Heading>
60
- {t(`searchPage.resultType.notionsHeading`)}
61
- <HeadingCount>{t(`searchPage.resultType.hits`, { count: totalCount })}</HeadingCount>
62
- </Heading>
63
- <ModalCloseButton onClick={onRemove} />
64
- </HeadingWrapper>
65
- {items.map((item) => (
66
- <SearchNotionItem key={item.id} {...item} renderMarkdown={renderMarkdown} />
67
- ))}
68
- </Wrapper>
69
- );
62
+ const SearchNotionsResult = ({ totalCount, onRemove, children }: Props & WithTranslation) => {
63
+ const { t } = useTranslation();
64
+ return (
65
+ <Wrapper>
66
+ <HeadingWrapper>
67
+ <Heading>
68
+ {t(`searchPage.resultType.notionsHeading`)}
69
+ <HeadingCount>{t(`searchPage.resultType.hits`, { count: totalCount })}</HeadingCount>
70
+ </Heading>
71
+ <ModalCloseButton onClick={onRemove} title={t(`searchPage.resultType.notionsRemove`)} />
72
+ </HeadingWrapper>
73
+ {children}
74
+ </Wrapper>
75
+ );
76
+ };
70
77
 
71
78
  export default withTranslation()(SearchNotionsResult);
@@ -9,17 +9,19 @@
9
9
  import React, { MouseEvent, memo } from 'react'; // useMemo , { Children }
10
10
  import styled from '@emotion/styled';
11
11
  import Button from '@ndla/button';
12
- import { breakpoints, mq, spacing } from '@ndla/core';
13
- import { WithTranslation, withTranslation } from 'react-i18next';
12
+ import { breakpoints, fonts, mq, spacing } from '@ndla/core';
13
+ import { withTranslation, WithTranslation } from 'react-i18next';
14
+ import { Cross } from '@ndla/icons/action';
14
15
  import ContentTypeBadge from '../ContentTypeBadge';
15
16
  import { ContentType } from './SearchTypeResult';
16
17
 
18
+ const Wrapper = styled.div`
19
+ margin: ${spacing.small} 0;
20
+ `;
17
21
  const HeaderWrapper = styled.div`
18
22
  display: flex;
19
23
  flex-direction: row;
20
- border-bottom: 2px solid #20588f;
21
24
  align-items: center;
22
- margin: ${spacing.small} 0;
23
25
  justify-content: space-between;
24
26
  padding: 0 2px;
25
27
  ${mq.range({ until: breakpoints.tablet })} {
@@ -36,36 +38,39 @@ const TypeWrapper = styled.div`
36
38
  margin-bottom: ${spacing.xsmall};
37
39
  `;
38
40
 
41
+ const BadgeWrapper = styled.span`
42
+ margin-right: ${spacing.small};
43
+ `;
44
+
39
45
  const SubjectName = styled.span`
40
- font-size: 18px;
41
- margin: 2px ${spacing.small};
46
+ ${fonts.sizes('18px', '24px')};
47
+ margin: 2px 0;
42
48
  b {
43
- font-size: 18px;
49
+ ${fonts.sizes('18px', '24px')};
44
50
  margin-right: 4px;
45
- font-weight: 600;
46
51
  }
47
52
  `;
48
53
 
49
54
  const Count = styled.span``;
50
55
 
51
- const CategoryTypeButtonWrapper = styled.div`
52
- margin: 4px;
53
- `;
54
-
55
56
  const CategoryItems = styled.div`
56
57
  position: relative;
57
- right: -4px;
58
58
  button {
59
59
  white-space: nowrap;
60
60
  max-height: 29px;
61
61
  }
62
62
  white-space: nowrap;
63
63
  display: flex;
64
- justify-content: flex-end;
65
64
  flex-wrap: wrap;
66
- ${mq.range({ until: breakpoints.tablet })} {
67
- justify-content: flex-start;
68
- }
65
+ margin: ${spacing.small} 0 0;
66
+ `;
67
+
68
+ const CategoryTypeButtonWrapper = styled.div`
69
+ margin: 4px;
70
+ `;
71
+
72
+ const CategoryTypeCrossWrapper = styled.span`
73
+ margin-left: ${spacing.xsmall};
69
74
  `;
70
75
 
71
76
  export type FilterOptionsType = {
@@ -75,39 +80,52 @@ export type FilterOptionsType = {
75
80
  };
76
81
 
77
82
  type Props = {
78
- filters: FilterOptionsType[];
79
- onFilterClick: (id: string) => void;
83
+ filters?: FilterOptionsType[];
84
+ onFilterClick?: (id: string) => void;
80
85
  totalCount: number;
81
86
  type?: ContentType;
82
87
  };
83
- const SearchTypeHeader = ({ filters, onFilterClick, totalCount, type, t }: Props & WithTranslation) => (
84
- <HeaderWrapper>
85
- <TypeWrapper>
86
- {type && <ContentTypeBadge type={type} title={t(`contentTypes.${type}`)} background size="large" />}
87
- <SubjectName>
88
- {type && <b>{t(`contentTypes.${type}`)}</b>}{' '}
89
- {totalCount && <Count>{t(`searchPage.resultType.hits`, { count: totalCount })}</Count>}
90
- </SubjectName>
91
- </TypeWrapper>
92
- {filters && (
88
+ const SearchTypeHeader = ({ filters = [], onFilterClick, totalCount, type, t }: Props & WithTranslation) => (
89
+ <Wrapper>
90
+ <HeaderWrapper>
91
+ <TypeWrapper>
92
+ {type && (
93
+ <BadgeWrapper>
94
+ <ContentTypeBadge type={type} title={t(`contentTypes.${type}`)} background border={false} size="large" />
95
+ </BadgeWrapper>
96
+ )}
97
+ <SubjectName>
98
+ <b>{type ? t(`contentTypes.${type}`) : t(`searchPage.resultType.allContentTypes`)}</b>{' '}
99
+ {totalCount && <Count>{t(`searchPage.resultType.hits`, { count: totalCount })}</Count>}
100
+ </SubjectName>
101
+ </TypeWrapper>
102
+ </HeaderWrapper>
103
+ {filters.length > 0 && (
93
104
  <CategoryItems>
94
105
  {filters.map((option: FilterOptionsType) => (
95
106
  <CategoryTypeButtonWrapper key={option.id}>
96
107
  <Button
97
- size="small"
98
- lighter={!option.active}
108
+ size="xsmall"
109
+ borderShape="rounded"
110
+ greyLighter={!option.active}
99
111
  onClick={(e: MouseEvent<HTMLButtonElement>) => {
100
112
  if (e.currentTarget && option.active) {
101
113
  e.currentTarget.blur();
102
114
  }
103
- onFilterClick(option.id);
115
+ onFilterClick?.(option.id);
104
116
  }}>
105
117
  {option.name}
118
+ {option.active && (
119
+ <CategoryTypeCrossWrapper>
120
+ <Cross />
121
+ </CategoryTypeCrossWrapper>
122
+ )}
106
123
  </Button>
107
124
  </CategoryTypeButtonWrapper>
108
125
  ))}
109
126
  </CategoryItems>
110
127
  )}
111
- </HeaderWrapper>
128
+ </Wrapper>
112
129
  );
130
+
113
131
  export default memo(withTranslation()(SearchTypeHeader));
@@ -13,7 +13,7 @@ import Spinner from '../Spinner';
13
13
  import constants from '../model';
14
14
  import SearchTypeHeader, { FilterOptionsType } from './SearchTypeHeader';
15
15
  import SearchItems from './SearchItems';
16
- import { SearchItemType } from './SearchItem';
16
+ import { SearchItemProps } from './SearchItem';
17
17
  import ResultNavigation, { PaginationType } from './ResultNavigation';
18
18
 
19
19
  const Wrapper = styled.div`
@@ -47,17 +47,19 @@ export type ContentType =
47
47
  | typeof constants.contentTypes.EXTERNAL_LEARNING_RESOURCES
48
48
  | typeof constants.contentTypes.SOURCE_MATERIAL
49
49
  | typeof constants.contentTypes.LEARNING_PATH
50
- | typeof constants.contentTypes.TOPIC;
50
+ | typeof constants.contentTypes.TOPIC
51
+ | typeof constants.contentTypes.MULTIDISCIPLINARY_TOPIC;
51
52
 
52
53
  type Props = {
53
- items: SearchItemType[];
54
- filters: FilterOptionsType[];
55
- onFilterClick: (id: string) => void;
54
+ items: SearchItemProps[];
55
+ filters?: FilterOptionsType[];
56
+ onFilterClick?: (id: string) => void;
56
57
  totalCount?: number;
57
58
  pagination?: PaginationType;
58
59
  type?: ContentType;
59
60
  loading?: boolean;
60
61
  children?: ReactNode;
62
+ viewType?: 'grid' | 'list';
61
63
  };
62
64
 
63
65
  const SearchTypeResult = ({
@@ -68,20 +70,19 @@ const SearchTypeResult = ({
68
70
  pagination,
69
71
  type,
70
72
  loading,
73
+ viewType,
71
74
  children,
72
75
  }: Props) => (
73
76
  <Wrapper>
74
77
  {loading && (
75
- <>
76
- <Overlay>
77
- <Spinner />
78
- </Overlay>
79
- </>
78
+ <Overlay>
79
+ <Spinner />
80
+ </Overlay>
80
81
  )}
81
82
  <SearchTypeHeader onFilterClick={onFilterClick} filters={filters} totalCount={totalCount} type={type} />
82
- <SearchItems items={items} type={type} />
83
+ <SearchItems items={items} type={type} viewType={viewType} />
83
84
  {pagination && <ResultNavigation {...pagination} />}
84
- {children && children}
85
+ {children}
85
86
  </Wrapper>
86
87
  );
87
88
 
@@ -0,0 +1,109 @@
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 styled from '@emotion/styled';
11
+ import Button from '@ndla/button';
12
+ import { Grid } from '@ndla/icons/common';
13
+ import { ListCircle } from '@ndla/icons/editor';
14
+ import { animations, breakpoints, colors, mq } from '@ndla/core';
15
+ import { useTranslation } from 'react-i18next';
16
+
17
+ const Wrapper = styled.div`
18
+ display: inline-flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ flex: 1;
22
+ ${mq.range({ until: breakpoints.tablet })} {
23
+ display: none;
24
+ }
25
+ padding-top: 8px;
26
+ `;
27
+
28
+ const ButtonContainer = styled.div`
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ width: 44px;
33
+ height: 44px;
34
+ `;
35
+ const ButtonElement = styled(Button)`
36
+ border-radius: 50%;
37
+ padding: 2px;
38
+ width: 100%;
39
+ height: 100%;
40
+ transition: all ${animations.durations.fast} ease-in-out;
41
+ &:hover {
42
+ padding: 0;
43
+ }
44
+ `;
45
+
46
+ type ButtonContentProps = {
47
+ selected?: boolean;
48
+ };
49
+
50
+ const ButtonContent = styled.span<ButtonContentProps>`
51
+ width: 100%;
52
+ height: 100%;
53
+ display: flex;
54
+ justify-content: center;
55
+ align-items: center;
56
+ color: ${colors.brand.primary};
57
+ ${(props) => props.selected && `background: ${colors.brand.greyLighter};`}
58
+ border-radius: 50%;
59
+ transition: all ${animations.durations.fast} ease-in-out;
60
+ ${ButtonElement}:hover & {
61
+ background: ${colors.brand.greyLighter};
62
+ }
63
+ svg {
64
+ width: 24px;
65
+ height: 24px;
66
+ }
67
+ `;
68
+
69
+ export type SearchViewTypeProps = {
70
+ viewType: 'grid' | 'list';
71
+ onChangeViewType: (viewType: SearchViewTypeProps['viewType']) => void;
72
+ };
73
+ const SearchViewType = ({ viewType, onChangeViewType }: SearchViewTypeProps) => {
74
+ const { t } = useTranslation();
75
+ const onClickHandler = (viewTypeClicked: SearchViewTypeProps['viewType']) => {
76
+ if (viewTypeClicked !== viewType) {
77
+ onChangeViewType(viewTypeClicked);
78
+ }
79
+ };
80
+ return (
81
+ <Wrapper>
82
+ <ButtonContainer>
83
+ <ButtonElement
84
+ stripped
85
+ type="button"
86
+ size="normal"
87
+ title={t('searchPage.resultType.gridView')}
88
+ onClick={() => onClickHandler('grid')}>
89
+ <ButtonContent selected={viewType === 'grid'}>
90
+ <Grid aria-hidden title="" />
91
+ </ButtonContent>
92
+ </ButtonElement>
93
+ </ButtonContainer>
94
+ <ButtonContainer>
95
+ <ButtonElement
96
+ stripped
97
+ size="normal"
98
+ title={t('searchPage.resultType.listView')}
99
+ onClick={() => onClickHandler('list')}>
100
+ <ButtonContent selected={viewType === 'list'}>
101
+ <ListCircle aria-hidden title="" />
102
+ </ButtonContent>
103
+ </ButtonElement>
104
+ </ButtonContainer>
105
+ </Wrapper>
106
+ );
107
+ };
108
+
109
+ export default SearchViewType;
@@ -0,0 +1,138 @@
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, { ReactNode, Fragment } from 'react';
10
+ import SafeLink from '@ndla/safelink';
11
+ import { Additional, Core } from '@ndla/icons/common';
12
+ import styled from '@emotion/styled';
13
+ import { breakpoints, colors, fonts, mq, spacing } from '@ndla/core';
14
+ import Button from '@ndla/button';
15
+ import Modal, { ModalCloseButton } from '@ndla/modal';
16
+ import { useTranslation } from 'react-i18next';
17
+
18
+ const BreadcrumbPath = styled.div`
19
+ color: ${colors.text.light};
20
+ ${fonts.sizes('14px', '20px')};
21
+
22
+ svg {
23
+ margin-left: ${spacing.small};
24
+ }
25
+ `;
26
+
27
+ const ModalButton = styled(Button)`
28
+ ${fonts.sizes('14px', '20px')};
29
+ box-shadow: none;
30
+ &:hover {
31
+ box-shadow: inset 0 -1px;
32
+ }
33
+ `;
34
+
35
+ const ModalHeader = styled.div`
36
+ display: flex;
37
+ justify-content: space-between;
38
+ align-items: center;
39
+ padding: ${spacing.normal} ${spacing.small};
40
+ ${mq.range({ from: breakpoints.tablet })} {
41
+ padding: ${spacing.normal} ${spacing.large};
42
+ }
43
+ `;
44
+
45
+ const ModalHeading = styled.h2`
46
+ margin: 0;
47
+ ${fonts.sizes('16px', '20px')};
48
+ font-weight: 600;
49
+ `;
50
+
51
+ const ModalContent = styled.div`
52
+ padding: 0 ${spacing.small} ${spacing.normal};
53
+ ${mq.range({ from: breakpoints.tablet })} {
54
+ padding: 0 ${spacing.large} ${spacing.normal};
55
+ }
56
+ `;
57
+
58
+ const ContextList = styled.ul`
59
+ margin: 0;
60
+ padding: 0;
61
+ list-style: none;
62
+ `;
63
+ const ContextListItem = styled.li`
64
+ margin-bottom: ${spacing.normal};
65
+ ${fonts.sizes('16px', '28px')};
66
+ `;
67
+
68
+ type context = {
69
+ breadcrumb: string[];
70
+ url: string;
71
+ isAdditional?: boolean;
72
+ };
73
+ export type ItemContextsType = {
74
+ contexts: context[];
75
+ id: string | number;
76
+ title: string;
77
+ };
78
+
79
+ const iconStyle = { width: '22px', height: '22px' };
80
+
81
+ const ItemContexts = ({ contexts, id, title }: ItemContextsType) => {
82
+ const { t } = useTranslation();
83
+ const mainContext = contexts[0];
84
+ const Breadcrumb = ({ breadcrumb, children }: { breadcrumb: string[]; children?: ReactNode }) => (
85
+ <BreadcrumbPath>
86
+ {breadcrumb.map((breadcrumbItem: string, i: number) => (
87
+ <Fragment key={`${breadcrumbItem}-${id}`}>
88
+ {i > 0 && <> &rsaquo; </>}
89
+ {breadcrumbItem}
90
+ </Fragment>
91
+ ))}
92
+ {children}
93
+ </BreadcrumbPath>
94
+ );
95
+
96
+ return (
97
+ <Breadcrumb breadcrumb={mainContext.breadcrumb}>
98
+ &nbsp;
99
+ {contexts.length > 1 && (
100
+ <Modal
101
+ activateButton={
102
+ <ModalButton link>
103
+ {t('searchPage.contextModal.button', {
104
+ count: contexts.length - 1,
105
+ })}
106
+ </ModalButton>
107
+ }
108
+ animation="subtle"
109
+ animationDuration={50}
110
+ backgroundColor="white"
111
+ size="medium">
112
+ {(onClose: () => void) => (
113
+ <>
114
+ <ModalHeader>
115
+ <ModalHeading>{t('searchPage.contextModal.heading')}</ModalHeading>
116
+ <ModalCloseButton onClick={onClose} title={t('searchPage.close')} />
117
+ </ModalHeader>
118
+ <ModalContent>
119
+ <ContextList>
120
+ {contexts.map((context) => (
121
+ <ContextListItem key={context.url}>
122
+ <SafeLink to={context.url}>{title}</SafeLink>
123
+ <Breadcrumb breadcrumb={context.breadcrumb}>
124
+ {context.isAdditional ? <Additional style={iconStyle} /> : <Core style={iconStyle} />}
125
+ </Breadcrumb>
126
+ </ContextListItem>
127
+ ))}
128
+ </ContextList>
129
+ </ModalContent>
130
+ </>
131
+ )}
132
+ </Modal>
133
+ )}
134
+ </Breadcrumb>
135
+ );
136
+ };
137
+
138
+ export default ItemContexts;