@ndla/ui 4.3.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/es/Article/Article.js +3 -7
- package/es/Article/ArticleByline.js +4 -4
- package/es/Article/ArticleNotions.js +37 -92
- package/es/ContentTypeBadge/ContentTypeBadge.js +10 -1
- package/es/FactBox/FactBox.js +36 -11
- package/es/Filter/FilterButtons.js +18 -17
- package/es/Filter/FilterCarousel.js +198 -0
- package/es/Masthead/Masthead.js +24 -2
- package/es/Notion/ConceptNotion.js +57 -0
- package/es/Notion/FigureNotion.js +82 -0
- package/es/Notion/Notion.js +122 -48
- package/es/Notion/NotionImage.js +47 -0
- package/es/Notion/NotionVisualElement.js +38 -0
- package/es/Notion/index.js +2 -1
- package/es/SearchTypeResult/ActiveFilterContent.js +17 -2
- package/es/SearchTypeResult/ActiveFilters.js +64 -50
- package/es/SearchTypeResult/PopupFilter.js +28 -125
- package/es/SearchTypeResult/ResultNavigation.js +41 -14
- package/es/SearchTypeResult/SearchFieldHeader.js +20 -41
- package/es/SearchTypeResult/SearchFilterContent.js +61 -0
- package/es/SearchTypeResult/SearchHeader.js +51 -28
- package/es/SearchTypeResult/SearchItem.js +64 -190
- package/es/SearchTypeResult/SearchItemList.js +132 -0
- package/es/SearchTypeResult/SearchItems.js +17 -13
- package/es/SearchTypeResult/SearchNotionItem.js +13 -13
- package/es/SearchTypeResult/SearchNotionsResult.js +16 -21
- package/es/SearchTypeResult/SearchTypeHeader.js +43 -26
- package/es/SearchTypeResult/SearchTypeResult.js +7 -5
- package/es/SearchTypeResult/SearchViewType.js +93 -0
- package/es/SearchTypeResult/components/ItemContexts.js +125 -0
- package/es/SearchTypeResult/components/ItemResourceHeader.js +87 -0
- package/es/SearchTypeResult/components/ItemTopicHeader.js +56 -0
- package/es/SearchTypeResult/components/SubjectFilters.js +177 -0
- package/es/SearchTypeResult/index.js +2 -1
- package/es/all.css +1 -1
- package/es/index.js +3 -2
- package/es/locale/messages-en.js +20 -5
- package/es/locale/messages-nb.js +21 -6
- package/es/locale/messages-nn.js +21 -6
- package/es/model/ContentType.js +2 -1
- package/es/shapes.js +1 -1
- package/lib/Article/Article.d.ts +4 -5
- package/lib/Article/Article.js +3 -7
- package/lib/Article/ArticleByline.js +4 -4
- package/lib/Article/ArticleNotions.d.ts +3 -8
- package/lib/Article/ArticleNotions.js +41 -90
- package/lib/ContentTypeBadge/ContentTypeBadge.d.ts +1 -0
- package/lib/ContentTypeBadge/ContentTypeBadge.js +14 -2
- package/lib/FactBox/FactBox.js +41 -8
- package/lib/Filter/FilterButtons.d.ts +3 -10
- package/lib/Filter/FilterButtons.js +19 -17
- package/lib/Filter/FilterCarousel.d.ts +13 -0
- package/lib/Filter/FilterCarousel.js +207 -0
- package/lib/Masthead/Masthead.js +30 -2
- package/lib/Notion/ConceptNotion.d.ts +25 -0
- package/lib/Notion/ConceptNotion.js +79 -0
- package/lib/Notion/FigureNotion.d.ts +23 -0
- package/lib/Notion/FigureNotion.js +97 -0
- package/lib/Notion/Notion.d.ts +24 -11
- package/lib/Notion/Notion.js +120 -48
- package/lib/Notion/NotionImage.d.ts +15 -0
- package/lib/Notion/NotionImage.js +63 -0
- package/lib/Notion/NotionVisualElement.d.ts +22 -0
- package/lib/Notion/NotionVisualElement.js +51 -0
- package/lib/Notion/index.d.ts +1 -0
- package/lib/Notion/index.js +8 -0
- package/lib/SearchTypeResult/ActiveFilterContent.js +16 -9
- package/lib/SearchTypeResult/ActiveFilters.d.ts +2 -1
- package/lib/SearchTypeResult/ActiveFilters.js +65 -50
- package/lib/SearchTypeResult/PopupFilter.d.ts +13 -19
- package/lib/SearchTypeResult/PopupFilter.js +27 -123
- package/lib/SearchTypeResult/ResultNavigation.d.ts +2 -2
- package/lib/SearchTypeResult/ResultNavigation.js +38 -14
- package/lib/SearchTypeResult/SearchFieldHeader.d.ts +3 -8
- package/lib/SearchTypeResult/SearchFieldHeader.js +18 -39
- package/lib/SearchTypeResult/SearchFilterContent.d.ts +16 -0
- package/lib/SearchTypeResult/SearchFilterContent.js +67 -0
- package/lib/SearchTypeResult/SearchHeader.d.ts +3 -7
- package/lib/SearchTypeResult/SearchHeader.js +59 -30
- package/lib/SearchTypeResult/SearchItem.d.ts +8 -12
- package/lib/SearchTypeResult/SearchItem.js +64 -187
- package/lib/SearchTypeResult/SearchItemList.d.ts +10 -0
- package/lib/SearchTypeResult/SearchItemList.js +139 -0
- package/lib/SearchTypeResult/SearchItems.d.ts +4 -3
- package/lib/SearchTypeResult/SearchItems.js +18 -13
- package/lib/SearchTypeResult/SearchNotionItem.js +13 -13
- package/lib/SearchTypeResult/SearchNotionsResult.d.ts +2 -4
- package/lib/SearchTypeResult/SearchNotionsResult.js +23 -23
- package/lib/SearchTypeResult/SearchTypeHeader.d.ts +2 -2
- package/lib/SearchTypeResult/SearchTypeHeader.js +40 -25
- package/lib/SearchTypeResult/SearchTypeResult.d.ts +7 -6
- package/lib/SearchTypeResult/SearchTypeResult.js +7 -5
- package/lib/SearchTypeResult/SearchViewType.d.ts +13 -0
- package/lib/SearchTypeResult/SearchViewType.js +99 -0
- package/lib/SearchTypeResult/components/ItemContexts.d.ts +19 -0
- package/lib/SearchTypeResult/components/ItemContexts.js +134 -0
- package/lib/SearchTypeResult/components/ItemResourceHeader.d.ts +16 -0
- package/lib/SearchTypeResult/components/ItemResourceHeader.js +98 -0
- package/lib/SearchTypeResult/components/ItemTopicHeader.d.ts +17 -0
- package/lib/SearchTypeResult/components/ItemTopicHeader.js +67 -0
- package/lib/SearchTypeResult/components/SubjectFilters.d.ts +32 -0
- package/lib/SearchTypeResult/components/SubjectFilters.js +192 -0
- package/lib/SearchTypeResult/index.d.ts +2 -1
- package/lib/SearchTypeResult/index.js +8 -0
- package/lib/all.css +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +24 -1
- package/lib/locale/messages-en.d.ts +16 -1
- package/lib/locale/messages-en.js +20 -5
- package/lib/locale/messages-nb.d.ts +15 -0
- package/lib/locale/messages-nb.js +21 -6
- package/lib/locale/messages-nn.d.ts +16 -1
- package/lib/locale/messages-nn.js +21 -6
- package/lib/model/ContentType.d.ts +1 -0
- package/lib/model/ContentType.js +4 -2
- package/lib/shapes.js +1 -1
- package/lib/types.d.ts +1 -0
- package/package.json +14 -14
- package/src/Article/Article.tsx +8 -15
- package/src/Article/ArticleByline.tsx +1 -1
- package/src/Article/ArticleNotions.tsx +13 -33
- package/src/ContentTypeBadge/ContentTypeBadge.tsx +8 -0
- package/src/ContentTypeBadge/component.content-type-badge.scss +5 -0
- package/src/FactBox/FactBox.tsx +22 -15
- package/src/Figure/component.figure.scss +1 -1
- package/src/Filter/FilterButtons.tsx +14 -15
- package/src/Filter/FilterCarousel.tsx +166 -0
- package/src/Masthead/Masthead.tsx +42 -18
- package/src/Notion/ConceptNotion.tsx +80 -0
- package/src/Notion/FigureNotion.tsx +86 -0
- package/src/Notion/Notion.tsx +205 -75
- package/src/Notion/NotionImage.tsx +51 -0
- package/src/Notion/NotionVisualElement.tsx +50 -0
- package/src/Notion/index.ts +1 -0
- package/src/SearchTypeResult/ActiveFilterContent.tsx +7 -2
- package/src/SearchTypeResult/ActiveFilters.tsx +72 -38
- package/src/SearchTypeResult/PopupFilter.tsx +73 -146
- package/src/SearchTypeResult/ResultNavigation.tsx +54 -16
- package/src/SearchTypeResult/SearchFieldHeader.tsx +15 -40
- package/src/SearchTypeResult/SearchFilterContent.tsx +63 -0
- package/src/SearchTypeResult/SearchHeader.tsx +31 -31
- package/src/SearchTypeResult/SearchItem.tsx +145 -233
- package/src/SearchTypeResult/SearchItemList.tsx +167 -0
- package/src/SearchTypeResult/SearchItems.tsx +26 -19
- package/src/SearchTypeResult/SearchNotionItem.tsx +5 -12
- package/src/SearchTypeResult/SearchNotionsResult.tsx +29 -22
- package/src/SearchTypeResult/SearchTypeHeader.tsx +51 -33
- package/src/SearchTypeResult/SearchTypeResult.tsx +13 -12
- package/src/SearchTypeResult/SearchViewType.tsx +109 -0
- package/src/SearchTypeResult/components/ItemContexts.tsx +138 -0
- package/src/SearchTypeResult/components/ItemResourceHeader.tsx +133 -0
- package/src/SearchTypeResult/components/ItemTopicHeader.tsx +95 -0
- package/src/SearchTypeResult/components/SubjectFilters.tsx +152 -0
- package/src/SearchTypeResult/index.ts +9 -1
- package/src/index.ts +3 -0
- package/src/locale/messages-en.ts +18 -3
- package/src/locale/messages-nb.ts +19 -4
- package/src/locale/messages-nn.ts +19 -4
- package/src/model/ContentType.ts +1 -0
- package/src/shapes.js +1 -0
- 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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
23
|
-
border-radius:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
children: ReactNode;
|
|
54
60
|
};
|
|
55
61
|
|
|
56
|
-
const SearchNotionsResult = ({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 {
|
|
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
|
-
|
|
41
|
-
margin: 2px
|
|
46
|
+
${fonts.sizes('18px', '24px')};
|
|
47
|
+
margin: 2px 0;
|
|
42
48
|
b {
|
|
43
|
-
|
|
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
|
-
${
|
|
67
|
-
|
|
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
|
|
79
|
-
onFilterClick
|
|
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
|
-
<
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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="
|
|
98
|
-
|
|
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
|
-
</
|
|
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 {
|
|
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:
|
|
54
|
-
filters
|
|
55
|
-
onFilterClick
|
|
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
|
-
<
|
|
77
|
-
|
|
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
|
|
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 && <> › </>}
|
|
89
|
+
{breadcrumbItem}
|
|
90
|
+
</Fragment>
|
|
91
|
+
))}
|
|
92
|
+
{children}
|
|
93
|
+
</BreadcrumbPath>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Breadcrumb breadcrumb={mainContext.breadcrumb}>
|
|
98
|
+
|
|
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;
|