@ndla/ui 23.0.0 → 24.1.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.
- package/es/Article/ArticleAuthorContent.js +2 -4
- package/es/Article/ArticleFavoritesButton.js +2 -2
- package/es/AuthorInfo/AuthorInfo.js +29 -16
- package/es/ContentCard/ContentCard.js +66 -25
- package/es/FileList/File.js +34 -8
- package/es/FileList/FileList.js +29 -3
- package/es/InfoBox/InfoBox.js +10 -3
- package/es/InfoWidget/InfoWidget.js +67 -22
- package/es/MyNdla/Resource/FolderInput.js +19 -6
- package/es/NoContentBox/NoContentBox.js +1 -6
- package/es/Portrait/Portrait.js +19 -13
- package/es/Resource/BlockResource.js +7 -6
- package/es/Resource/ListResource.js +8 -7
- package/es/Search/ActiveFilterContent.js +4 -14
- package/es/Search/ActiveFilters.js +8 -19
- package/es/Search/SearchField.js +31 -52
- package/es/Search/SearchResult.js +113 -136
- package/es/Search/ToggleSearchButton.js +34 -43
- package/es/Search/index.js +2 -8
- package/es/TagSelector/SuggestionInput.js +9 -19
- package/es/TreeStructure/FolderItems.js +3 -3
- package/es/TreeStructure/FolderNameInput.js +33 -14
- package/es/TreeStructure/TreeStructure.js +3 -2
- package/es/all.css +1 -1
- package/es/index-javascript.js +0 -1
- package/es/index.js +2 -1
- package/es/locale/messages-en.js +11 -5
- package/es/locale/messages-nb.js +9 -3
- package/es/locale/messages-nn.js +11 -5
- package/es/locale/messages-se.js +29 -23
- package/es/locale/messages-sma.js +43 -37
- package/lib/Article/ArticleAuthorContent.js +9 -4
- package/lib/Article/ArticleFavoritesButton.js +2 -2
- package/lib/AuthorInfo/AuthorInfo.d.ts +1 -11
- package/lib/AuthorInfo/AuthorInfo.js +36 -20
- package/lib/ContentCard/ContentCard.d.ts +1 -15
- package/lib/ContentCard/ContentCard.js +60 -28
- package/lib/FileList/File.js +36 -12
- package/lib/FileList/FileList.js +28 -5
- package/lib/InfoBox/InfoBox.js +12 -5
- package/lib/InfoWidget/InfoWidget.js +61 -25
- package/lib/MediaList/MediaList.d.ts +1 -1
- package/lib/MyNdla/Resource/FolderInput.js +18 -5
- package/lib/NoContentBox/NoContentBox.js +1 -8
- package/lib/Portrait/Portrait.js +19 -14
- package/lib/Resource/BlockResource.js +7 -6
- package/lib/Resource/ListResource.js +8 -7
- package/lib/Search/ActiveFilterContent.d.ts +13 -0
- package/lib/Search/ActiveFilterContent.js +4 -15
- package/lib/Search/ActiveFilters.d.ts +13 -0
- package/lib/Search/ActiveFilters.js +8 -20
- package/lib/Search/SearchField.d.ts +19 -0
- package/lib/Search/SearchField.js +32 -56
- package/lib/Search/SearchResult.d.ts +36 -0
- package/lib/Search/SearchResult.js +116 -159
- package/lib/Search/ToggleSearchButton.d.ts +16 -0
- package/lib/Search/ToggleSearchButton.js +36 -46
- package/lib/Search/index.d.ts +12 -0
- package/lib/Search/index.js +0 -54
- package/lib/SearchTypeResult/SearchTypeHeader.d.ts +1 -1
- package/lib/TagSelector/SuggestionInput.js +9 -19
- package/lib/TreeStructure/FolderItems.js +3 -3
- package/lib/TreeStructure/FolderNameInput.js +35 -14
- package/lib/TreeStructure/TreeStructure.js +3 -2
- package/lib/TreeStructure/types.d.ts +1 -1
- package/lib/all.css +1 -1
- package/lib/index-javascript.js +0 -74
- package/lib/index.d.ts +1 -0
- package/lib/index.js +38 -1
- package/lib/locale/messages-en.d.ts +7 -1
- package/lib/locale/messages-en.js +11 -5
- package/lib/locale/messages-nb.d.ts +6 -0
- package/lib/locale/messages-nb.js +9 -3
- package/lib/locale/messages-nn.d.ts +7 -1
- package/lib/locale/messages-nn.js +11 -5
- package/lib/locale/messages-se.d.ts +7 -1
- package/lib/locale/messages-se.js +29 -23
- package/lib/locale/messages-sma.d.ts +12 -6
- package/lib/locale/messages-sma.js +43 -37
- package/package.json +10 -10
- package/src/Article/ArticleAuthorContent.tsx +1 -1
- package/src/Article/ArticleFavoritesButton.tsx +2 -2
- package/src/AuthorInfo/AuthorInfo.tsx +53 -19
- package/src/ContentCard/ContentCard.tsx +127 -35
- package/src/FileList/File.tsx +47 -17
- package/src/FileList/FileList.tsx +37 -8
- package/src/InfoBox/InfoBox.tsx +24 -4
- package/src/InfoWidget/InfoWidget.tsx +83 -34
- package/src/MediaList/MediaList.tsx +1 -1
- package/src/MyNdla/Resource/FolderInput.tsx +18 -3
- package/src/NoContentBox/NoContentBox.tsx +2 -7
- package/src/Portrait/Portrait.tsx +25 -10
- package/src/Resource/BlockResource.tsx +1 -1
- package/src/Resource/ListResource.tsx +3 -1
- package/src/Search/{ActiveFilterContent.jsx → ActiveFilterContent.tsx} +11 -12
- package/src/Search/{ActiveFilters.jsx → ActiveFilters.tsx} +20 -17
- package/src/Search/{SearchField.jsx → SearchField.tsx} +58 -68
- package/src/Search/SearchResult.tsx +360 -0
- package/src/Search/ToggleSearchButton.tsx +73 -0
- package/src/Search/component.search.scss +0 -4
- package/src/Search/index.ts +16 -0
- package/src/SectionHeading/SectionHeading.tsx +1 -0
- package/src/TagSelector/SuggestionInput.tsx +0 -9
- package/src/TreeStructure/FolderItems.tsx +1 -1
- package/src/TreeStructure/FolderNameInput.tsx +34 -9
- package/src/TreeStructure/TreeStructure.tsx +1 -0
- package/src/TreeStructure/types.ts +1 -1
- package/src/all.scss +0 -1
- package/src/index-javascript.js +0 -15
- package/src/index.ts +2 -0
- package/src/locale/messages-en.ts +10 -4
- package/src/locale/messages-nb.ts +9 -3
- package/src/locale/messages-nn.ts +10 -4
- package/src/locale/messages-se.ts +29 -23
- package/src/locale/messages-sma.ts +41 -35
- package/src/main.scss +0 -7
- package/es/Search/SearchFilter.js +0 -72
- package/es/Search/SearchFilterList.js +0 -115
- package/es/Search/SearchOverlay.js +0 -39
- package/es/Search/SearchPage.js +0 -178
- package/es/Search/SearchPopoverFilter.js +0 -152
- package/es/Search/SearchResultAuthor.js +0 -51
- package/lib/Search/SearchFilter.js +0 -88
- package/lib/Search/SearchFilterList.js +0 -137
- package/lib/Search/SearchOverlay.js +0 -62
- package/lib/Search/SearchPage.js +0 -207
- package/lib/Search/SearchPopoverFilter.js +0 -172
- package/lib/Search/SearchResultAuthor.js +0 -60
- package/src/AuthorInfo/component.author-info.scss +0 -54
- package/src/ContentCard/component.content-card.scss +0 -109
- package/src/FileList/component.file-list.scss +0 -102
- package/src/InfoBox/component.info-box.scss +0 -21
- package/src/InfoWidget/component.info-widget.scss +0 -52
- package/src/NoContentBox/component.no-content-box.scss +0 -17
- package/src/Portrait/component.portrait.scss +0 -29
- package/src/Search/SearchFilter.jsx +0 -82
- package/src/Search/SearchFilterList.jsx +0 -110
- package/src/Search/SearchOverlay.jsx +0 -38
- package/src/Search/SearchPage.jsx +0 -178
- package/src/Search/SearchPopoverFilter.jsx +0 -109
- package/src/Search/SearchResult.jsx +0 -239
- package/src/Search/SearchResultAuthor.jsx +0 -54
- package/src/Search/ToggleSearchButton.jsx +0 -64
- package/src/Search/component.search-filter.scss +0 -67
- package/src/Search/component.search-overlay.scss +0 -103
- package/src/Search/component.search-page.scss +0 -125
- package/src/Search/component.search-result-author.scss +0 -65
- package/src/Search/index.js +0 -34
|
@@ -6,27 +6,30 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import
|
|
9
|
+
import React, { FocusEvent, MouseEvent, RefObject } from 'react';
|
|
10
|
+
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import BEMHelper from 'react-bem-helper';
|
|
12
|
-
import { Search as SearchIcon } from '@ndla/icons/common';
|
|
13
12
|
import { css } from '@emotion/core';
|
|
13
|
+
import styled from '@emotion/styled';
|
|
14
|
+
import { Search as SearchIcon } from '@ndla/icons/common';
|
|
14
15
|
import { colors, spacing, mq, breakpoints, misc, fonts } from '@ndla/core';
|
|
15
|
-
import { useTranslation } from 'react-i18next';
|
|
16
16
|
|
|
17
17
|
import ActiveFilters from './ActiveFilters';
|
|
18
18
|
import LoadingWrapper from './LoadingWrapper';
|
|
19
19
|
|
|
20
|
-
import { ContentTypeResultShape } from '../shapes';
|
|
21
|
-
|
|
22
20
|
const classes = new BEMHelper('c-search-field');
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
interface StyledInputProps {
|
|
23
|
+
frontPageSearch?: boolean;
|
|
24
|
+
hasFilters?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const StyledInput = styled.input<StyledInputProps>`
|
|
25
28
|
width: 100%;
|
|
26
29
|
height: 48px;
|
|
27
30
|
line-height: 28px;
|
|
28
31
|
border: 1px solid ${colors.brand.greyLight};
|
|
29
|
-
border-radius: ${frontPageSearch ? '100px' : misc.borderRadius};
|
|
32
|
+
border-radius: ${(p) => (p.frontPageSearch ? '100px' : misc.borderRadius)};
|
|
30
33
|
padding-right: ${spacing.large};
|
|
31
34
|
padding-left: ${spacing.normal};
|
|
32
35
|
flex-grow: 1;
|
|
@@ -41,61 +44,71 @@ const inputStyle = (frontPageSearch) => css`
|
|
|
41
44
|
line-height: 58px;
|
|
42
45
|
${fonts.sizes('18px', '24px')};
|
|
43
46
|
}
|
|
44
|
-
`;
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
${(p) =>
|
|
49
|
+
p.hasFilters &&
|
|
50
|
+
css`
|
|
51
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
52
|
+
padding-left: ${spacing.normal};
|
|
53
|
+
}
|
|
54
|
+
padding-left: 0;
|
|
55
|
+
border-left: 0;
|
|
56
|
+
border-top-left-radius: 0;
|
|
57
|
+
border-bottom-left-radius: 0;
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
&:focus {
|
|
60
|
+
border: 1px solid ${colors.brand.primary};
|
|
61
|
+
border-left: 0;
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
& + .c-search-field__filters {
|
|
64
|
+
border: 1px solid ${colors.brand.primary};
|
|
65
|
+
border-right: 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`};
|
|
64
69
|
`;
|
|
65
70
|
|
|
71
|
+
interface Props {
|
|
72
|
+
value: string;
|
|
73
|
+
placeholder: string;
|
|
74
|
+
onChange: (value: string) => void;
|
|
75
|
+
filters?: { value: string; title: string }[];
|
|
76
|
+
onFilterRemove?: (value: string, filterName?: string) => void;
|
|
77
|
+
onFocus?: (event?: FocusEvent<HTMLInputElement>) => void;
|
|
78
|
+
onBlur?: () => void;
|
|
79
|
+
onClick?: (event: MouseEvent<HTMLInputElement>) => void;
|
|
80
|
+
loading?: boolean;
|
|
81
|
+
inputRef?: RefObject<HTMLInputElement>;
|
|
82
|
+
frontPageSearch?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
const SearchField = ({
|
|
67
86
|
placeholder,
|
|
68
87
|
value,
|
|
69
88
|
onChange,
|
|
70
89
|
filters,
|
|
71
|
-
small,
|
|
72
90
|
onClick,
|
|
73
|
-
onFocus
|
|
74
|
-
onBlur
|
|
91
|
+
onFocus,
|
|
92
|
+
onBlur,
|
|
75
93
|
loading,
|
|
76
94
|
onFilterRemove,
|
|
77
95
|
inputRef,
|
|
78
96
|
frontPageSearch = false,
|
|
79
|
-
}) => {
|
|
97
|
+
}: Props) => {
|
|
80
98
|
const { t } = useTranslation();
|
|
81
|
-
const handleOnFilterRemove = (value, filterName) => {
|
|
82
|
-
onFilterRemove(value, filterName);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
onFocus();
|
|
99
|
+
const handleOnFilterRemove = (value: string, filterName?: string) => {
|
|
100
|
+
onFilterRemove?.(value, filterName);
|
|
101
|
+
inputRef?.current?.focus();
|
|
102
|
+
onFocus?.();
|
|
87
103
|
};
|
|
88
|
-
const hasFilters = filters && filters.length > 0;
|
|
89
104
|
return (
|
|
90
105
|
<div {...classes('input-wrapper')}>
|
|
91
106
|
{loading && <LoadingWrapper value={value} />}
|
|
92
|
-
<
|
|
107
|
+
<StyledInput
|
|
108
|
+
frontPageSearch={frontPageSearch}
|
|
109
|
+
hasFilters={!!filters?.length}
|
|
93
110
|
ref={inputRef}
|
|
94
111
|
type="search"
|
|
95
|
-
css={css`
|
|
96
|
-
${inputStyle(frontPageSearch)};
|
|
97
|
-
${hasFilters && filterStyle};
|
|
98
|
-
`}
|
|
99
112
|
aria-autocomplete="list"
|
|
100
113
|
autoComplete="off"
|
|
101
114
|
id="search"
|
|
@@ -108,7 +121,7 @@ const SearchField = ({
|
|
|
108
121
|
onFocus={onFocus}
|
|
109
122
|
onClick={onClick}
|
|
110
123
|
/>
|
|
111
|
-
{
|
|
124
|
+
{filters && filters.length > 0 && (
|
|
112
125
|
<div {...classes('filters')}>
|
|
113
126
|
<ActiveFilters filters={filters} onFilterRemove={handleOnFilterRemove} />
|
|
114
127
|
</div>
|
|
@@ -119,41 +132,18 @@ const SearchField = ({
|
|
|
119
132
|
type="button"
|
|
120
133
|
onClick={() => {
|
|
121
134
|
onChange('');
|
|
122
|
-
onFocus();
|
|
123
|
-
|
|
124
|
-
inputRef.current.focus();
|
|
125
|
-
}
|
|
135
|
+
onFocus?.();
|
|
136
|
+
inputRef?.current?.focus();
|
|
126
137
|
}}
|
|
127
138
|
onBlur={onBlur}>
|
|
128
139
|
{t('welcomePage.resetSearch')}
|
|
129
140
|
</button>
|
|
130
141
|
)}
|
|
131
|
-
<button tabIndex=
|
|
142
|
+
<button tabIndex={-1} {...classes('button', 'searchIcon')} type="submit" value="Search">
|
|
132
143
|
<SearchIcon />
|
|
133
144
|
</button>
|
|
134
145
|
</div>
|
|
135
146
|
);
|
|
136
147
|
};
|
|
137
148
|
|
|
138
|
-
SearchField.propTypes = {
|
|
139
|
-
value: PropTypes.string.isRequired,
|
|
140
|
-
placeholder: PropTypes.string.isRequired,
|
|
141
|
-
onChange: PropTypes.func.isRequired,
|
|
142
|
-
filters: PropTypes.arrayOf(
|
|
143
|
-
PropTypes.shape({
|
|
144
|
-
value: PropTypes.string.isRequired,
|
|
145
|
-
title: PropTypes.string.isRequired,
|
|
146
|
-
}),
|
|
147
|
-
),
|
|
148
|
-
searchResult: PropTypes.arrayOf(ContentTypeResultShape),
|
|
149
|
-
allResultUrl: PropTypes.string,
|
|
150
|
-
onFilterRemove: PropTypes.func,
|
|
151
|
-
small: PropTypes.bool,
|
|
152
|
-
onNavigate: PropTypes.func,
|
|
153
|
-
onFocus: PropTypes.func,
|
|
154
|
-
onBlur: PropTypes.func,
|
|
155
|
-
onClick: PropTypes.func,
|
|
156
|
-
loading: PropTypes.bool,
|
|
157
|
-
};
|
|
158
|
-
|
|
159
149
|
export default SearchField;
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import React, { ElementType, Fragment, ReactNode } from 'react';
|
|
2
|
+
import { ChevronRight } from '@ndla/icons/common';
|
|
3
|
+
import { uuid } from '@ndla/util';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import Tooltip from '@ndla/tooltip';
|
|
6
|
+
import SafeLink from '@ndla/safelink';
|
|
7
|
+
import { Spinner } from '@ndla/icons';
|
|
8
|
+
import styled from '@emotion/styled';
|
|
9
|
+
import { fonts, mq, spacing, breakpoints, colors, spacingUnit } from '@ndla/core';
|
|
10
|
+
|
|
11
|
+
interface ItemType {
|
|
12
|
+
id: string | number;
|
|
13
|
+
title?: string;
|
|
14
|
+
url?: string | { href: string };
|
|
15
|
+
breadcrumb?: string[];
|
|
16
|
+
subjects?: {
|
|
17
|
+
title: string;
|
|
18
|
+
url?: string | { href: string };
|
|
19
|
+
breadcrumb?: string[];
|
|
20
|
+
}[];
|
|
21
|
+
additional?: boolean;
|
|
22
|
+
image?: ReactNode;
|
|
23
|
+
ingress: string;
|
|
24
|
+
contentTypeIcon?: string;
|
|
25
|
+
contentTypeLabel?: string;
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
type?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SearchResultItemProps {
|
|
31
|
+
children?: ReactNode;
|
|
32
|
+
subjectsLabel: string;
|
|
33
|
+
additionalContentTooltip: string;
|
|
34
|
+
item: ItemType;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const StyledHeader = styled.header`
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-wrap: wrap;
|
|
40
|
+
align-items: center;
|
|
41
|
+
|
|
42
|
+
> * {
|
|
43
|
+
margin: 0 ${spacing.small} ${spacing.small} 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
${mq.range({ until: breakpoints.tablet })} {
|
|
47
|
+
h1 {
|
|
48
|
+
width: 100%;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const ContentTypeWrapper = styled.div`
|
|
54
|
+
margin: 0 ${spacing.small} ${spacing.small} 0;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const PillsWrapper = styled.div`
|
|
60
|
+
background: ${colors.brand.greyLightest};
|
|
61
|
+
margin-right: ${spacing.small};
|
|
62
|
+
padding: 0 ${spacingUnit / 3}px;
|
|
63
|
+
border-radius: 50%;
|
|
64
|
+
${fonts.sizes('12px', '20px')};
|
|
65
|
+
font-weight: ${fonts.weight.semibold};
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
const StyledContent = styled.div`
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: flex-start;
|
|
71
|
+
justify-content: space-between;
|
|
72
|
+
& > img,
|
|
73
|
+
& > picture > img {
|
|
74
|
+
width: 75px;
|
|
75
|
+
height: auto;
|
|
76
|
+
flex-shrink: 0;
|
|
77
|
+
margin: 0 0 ${spacing.small} ${spacing.small};
|
|
78
|
+
|
|
79
|
+
${mq.range({ from: breakpoints.tabletWide })} {
|
|
80
|
+
width: 80px;
|
|
81
|
+
margin-left: ${spacing.normal};
|
|
82
|
+
margin-right: ${spacing.small};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
const StyledIngress = styled.p`
|
|
88
|
+
margin: 0 0 ${spacing.normal} 0;
|
|
89
|
+
width: 100%;
|
|
90
|
+
flex-grow: 1;
|
|
91
|
+
${fonts.sizes('14px', '22px')};
|
|
92
|
+
|
|
93
|
+
${mq.range({ from: breakpoints.tablet })} {
|
|
94
|
+
${fonts.sizes('16px', '24px')};
|
|
95
|
+
max-width: 550px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
99
|
+
${fonts.sizes('18px', '26px')};
|
|
100
|
+
max-width: 600px;
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
const StyledSubjects = styled.div`
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
flex-wrap: wrap;
|
|
108
|
+
margin-bottom: ${spacing.normal};
|
|
109
|
+
${fonts.sizes('14px', '16px')};
|
|
110
|
+
|
|
111
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
112
|
+
${fonts.sizes('16px', '18px')};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
& > span {
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
color: ${colors.text.light};
|
|
118
|
+
padding-right: ${spacing.small};
|
|
119
|
+
${fonts.sizes('14px', '16px')};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ul {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
flex-wrap: wrap;
|
|
126
|
+
list-style: none;
|
|
127
|
+
margin: 0;
|
|
128
|
+
padding: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
li {
|
|
132
|
+
margin: 0;
|
|
133
|
+
position: relative;
|
|
134
|
+
line-height: 1rem;
|
|
135
|
+
|
|
136
|
+
padding: 0 ${spacing.xsmall} 0 0;
|
|
137
|
+
margin-right: ${spacing.xsmall};
|
|
138
|
+
|
|
139
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
140
|
+
padding: 0 ${spacing.small} 0 0;
|
|
141
|
+
margin-right: ${spacing.small};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&::after {
|
|
145
|
+
content: '';
|
|
146
|
+
width: 1px;
|
|
147
|
+
height: 15px;
|
|
148
|
+
background: ${colors.brand.greyLight};
|
|
149
|
+
display: block;
|
|
150
|
+
position: absolute;
|
|
151
|
+
right: 0;
|
|
152
|
+
top: 3px;
|
|
153
|
+
}
|
|
154
|
+
&:first-child {
|
|
155
|
+
font-weight: 600;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&:last-child {
|
|
159
|
+
&:after {
|
|
160
|
+
display: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
margin-right: 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
const StyledSearchResultItem = styled.li`
|
|
169
|
+
border-bottom: 1px solid ${colors.brand.greyLight};
|
|
170
|
+
padding: 0 ${spacing.normal} 0 ${spacing.normal};
|
|
171
|
+
|
|
172
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
173
|
+
padding: 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
margin: 0 0 ${spacing.normal};
|
|
177
|
+
|
|
178
|
+
&:first-child {
|
|
179
|
+
margin-top: ${spacing.normal};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
&:last-child {
|
|
183
|
+
border-bottom: 0;
|
|
184
|
+
margin-bottom: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
h1 {
|
|
188
|
+
font-weight: 600;
|
|
189
|
+
${fonts.sizes('16px', '20px')};
|
|
190
|
+
|
|
191
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
192
|
+
${fonts.sizes('22px', '28px')};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
a {
|
|
196
|
+
color: ${colors.brand.dark};
|
|
197
|
+
|
|
198
|
+
&:hover,
|
|
199
|
+
&:focus,
|
|
200
|
+
&:active {
|
|
201
|
+
color: ${colors.brand.dark};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
const StyledBreadcrumb = styled.div`
|
|
208
|
+
list-style: none;
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
flex-wrap: wrap;
|
|
212
|
+
color: ${colors.text.light};
|
|
213
|
+
margin-bottom: ${spacing.normal};
|
|
214
|
+
${fonts.sizes('14px', '18px')};
|
|
215
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
216
|
+
${fonts.sizes('16px', '24px')};
|
|
217
|
+
}
|
|
218
|
+
.c-icon {
|
|
219
|
+
width: 12px;
|
|
220
|
+
height: 12px;
|
|
221
|
+
margin: 0 ${spacingUnit / 3}px;
|
|
222
|
+
|
|
223
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
224
|
+
margin: 0 ${spacing.xsmall};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
export const SearchResultItem = ({
|
|
230
|
+
item,
|
|
231
|
+
subjectsLabel,
|
|
232
|
+
additionalContentTooltip,
|
|
233
|
+
children,
|
|
234
|
+
}: SearchResultItemProps) => {
|
|
235
|
+
const itemBreadcrumb = (breadcrumbs: string[] = [], itemBreadcrumb?: boolean) => {
|
|
236
|
+
const Breadcrumb: ElementType = itemBreadcrumb ? StyledBreadcrumb : 'div';
|
|
237
|
+
if (breadcrumbs.length > 0) {
|
|
238
|
+
return (
|
|
239
|
+
<Breadcrumb>
|
|
240
|
+
{breadcrumbs.map((breadcrumbItem, index) => {
|
|
241
|
+
let icon = null;
|
|
242
|
+
if (index !== (item.breadcrumb?.length || 0) - 1) {
|
|
243
|
+
icon = <ChevronRight />;
|
|
244
|
+
}
|
|
245
|
+
return (
|
|
246
|
+
<Fragment key={uuid()}>
|
|
247
|
+
<span>{breadcrumbItem}</span>
|
|
248
|
+
{icon}
|
|
249
|
+
</Fragment>
|
|
250
|
+
);
|
|
251
|
+
})}
|
|
252
|
+
</Breadcrumb>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
return (
|
|
257
|
+
<StyledSearchResultItem key={item.id}>
|
|
258
|
+
<article>
|
|
259
|
+
<StyledHeader>
|
|
260
|
+
<h1>
|
|
261
|
+
{item.url && typeof item.url !== 'string' ? (
|
|
262
|
+
<a {...item.url}>{item.title}</a>
|
|
263
|
+
) : (
|
|
264
|
+
<SafeLink to={item.url ?? ''}>{item.title}</SafeLink>
|
|
265
|
+
)}
|
|
266
|
+
</h1>
|
|
267
|
+
<ContentTypeWrapper>{item.contentTypeIcon}</ContentTypeWrapper>
|
|
268
|
+
{item.contentTypeLabel && <PillsWrapper>{item.contentTypeLabel}</PillsWrapper>}
|
|
269
|
+
{item.type && <PillsWrapper>{item.type}</PillsWrapper>}
|
|
270
|
+
{item.additional && <PillsWrapper>{additionalContentTooltip}</PillsWrapper>}
|
|
271
|
+
{children}
|
|
272
|
+
</StyledHeader>
|
|
273
|
+
<StyledContent>
|
|
274
|
+
<StyledIngress dangerouslySetInnerHTML={{ __html: item.ingress }} />
|
|
275
|
+
{item.image}
|
|
276
|
+
</StyledContent>
|
|
277
|
+
{(!item.subjects || item.subjects.length === 0) && itemBreadcrumb(item.breadcrumb, true)}
|
|
278
|
+
{item.subjects && item.subjects.length !== 0 && (
|
|
279
|
+
<StyledSubjects>
|
|
280
|
+
<span>{subjectsLabel}</span>
|
|
281
|
+
<ul>
|
|
282
|
+
{item.subjects.map((subject) => (
|
|
283
|
+
<li key={uuid()}>
|
|
284
|
+
<Tooltip tooltip={itemBreadcrumb(subject.breadcrumb)}>
|
|
285
|
+
{subject.url && typeof subject.url !== 'string' ? (
|
|
286
|
+
<a {...subject.url}>{subject.title}</a>
|
|
287
|
+
) : (
|
|
288
|
+
<SafeLink to={subject.url ?? ''}>{subject.title}</SafeLink>
|
|
289
|
+
)}
|
|
290
|
+
</Tooltip>
|
|
291
|
+
</li>
|
|
292
|
+
))}
|
|
293
|
+
</ul>
|
|
294
|
+
</StyledSubjects>
|
|
295
|
+
)}
|
|
296
|
+
</article>
|
|
297
|
+
</StyledSearchResultItem>
|
|
298
|
+
);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
interface SearchResultListProps {
|
|
302
|
+
loading?: boolean;
|
|
303
|
+
results?: ItemType[];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const EmptyResultList = styled.article`
|
|
307
|
+
margin-top: ${spacing.large};
|
|
308
|
+
h1 {
|
|
309
|
+
${fonts.sizes('22px', '32px')};
|
|
310
|
+
font-weight: ${fonts.weight.bold};
|
|
311
|
+
margin: 0 0 ${spacing.small};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
p {
|
|
315
|
+
${fonts.sizes('16px', '22px')};
|
|
316
|
+
margin: 0;
|
|
317
|
+
font-family: ${fonts.serif};
|
|
318
|
+
|
|
319
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
320
|
+
${fonts.sizes('18px', '32px')};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
`;
|
|
324
|
+
|
|
325
|
+
const ResultList = styled.ul`
|
|
326
|
+
list-style: none;
|
|
327
|
+
margin: 0;
|
|
328
|
+
padding: 0;
|
|
329
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
330
|
+
margin: ${spacing.medium} 0 0 0;
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
333
|
+
|
|
334
|
+
export const SearchResultList = ({ results, loading }: SearchResultListProps) => {
|
|
335
|
+
const { t } = useTranslation();
|
|
336
|
+
if (loading) {
|
|
337
|
+
return <Spinner />;
|
|
338
|
+
}
|
|
339
|
+
if (!results) {
|
|
340
|
+
return <EmptyResultList />;
|
|
341
|
+
}
|
|
342
|
+
return results.length === 0 ? (
|
|
343
|
+
<EmptyResultList>
|
|
344
|
+
<h1>{t('searchPage.searchResultListMessages.noResultHeading')}</h1>
|
|
345
|
+
<p>{t('searchPage.searchResultListMessages.noResultDescription')}</p>
|
|
346
|
+
</EmptyResultList>
|
|
347
|
+
) : (
|
|
348
|
+
<ResultList>
|
|
349
|
+
{results.map((item) => (
|
|
350
|
+
<SearchResultItem
|
|
351
|
+
key={`search_result_item_${typeof item.url === 'object' ? item.url.href : item.url}`}
|
|
352
|
+
item={item}
|
|
353
|
+
additionalContentTooltip={t('resource.tooltipAdditionalTopic')}
|
|
354
|
+
subjectsLabel={t('searchPage.searchResultListMessages.subjectsLabel')}>
|
|
355
|
+
{item.children}
|
|
356
|
+
</SearchResultItem>
|
|
357
|
+
))}
|
|
358
|
+
</ResultList>
|
|
359
|
+
);
|
|
360
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2018-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 { spacing, spacingUnit, breakpoints, mq, misc, fonts, colors } from '@ndla/core';
|
|
11
|
+
import { Search } from '@ndla/icons/common';
|
|
12
|
+
import Button, { ButtonProps } from '@ndla/button';
|
|
13
|
+
import styled from '@emotion/styled';
|
|
14
|
+
|
|
15
|
+
interface Props extends ButtonProps {
|
|
16
|
+
hideOnNarrowScreen?: boolean;
|
|
17
|
+
hideOnWideScreen?: boolean;
|
|
18
|
+
ndlaFilm?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface StyledButtonProps {
|
|
22
|
+
hideOnNarrowScreen?: boolean;
|
|
23
|
+
hideOnWideScreen?: boolean;
|
|
24
|
+
ndlaFilm?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const StyledButton = styled(Button)<StyledButtonProps>`
|
|
28
|
+
background: ${(p) => (p.ndlaFilm ? colors.ndlaFilm.filmColorBright : colors.brand.greyLighter)};
|
|
29
|
+
border-radius: ${misc.borderRadius};
|
|
30
|
+
border: 0;
|
|
31
|
+
display: ${(p) => (p.hideOnNarrowScreen ? 'none' : 'flex')};
|
|
32
|
+
color: ${(p) => (p.ndlaFilm ? '#fff' : colors.brand.primary)};
|
|
33
|
+
padding: ${spacing.small} ${spacingUnit * 0.75}px ${spacing.small} ${spacing.normal};
|
|
34
|
+
align-items: center;
|
|
35
|
+
margin-left: ${spacing.normal};
|
|
36
|
+
|
|
37
|
+
.c-icon {
|
|
38
|
+
height: 24px;
|
|
39
|
+
width: 24px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
${fonts.sizes('16px', '32px')};
|
|
43
|
+
|
|
44
|
+
${mq.range({ from: breakpoints.desktop })} {
|
|
45
|
+
display: ${(p) => (p.hideOnWideScreen ? 'none' : 'flex')};
|
|
46
|
+
margin-right: ${spacing.medium};
|
|
47
|
+
padding: ${spacing.small} ${spacing.normal};
|
|
48
|
+
}
|
|
49
|
+
&:hover,
|
|
50
|
+
&:focus,
|
|
51
|
+
&:active {
|
|
52
|
+
border: 0;
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const StyledSpan = styled.span`
|
|
57
|
+
margin-right: ${spacing.normal};
|
|
58
|
+
font-weight: ${fonts.weight.normal};
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const ToggleSearchButton = ({ children, ndlaFilm, hideOnNarrowScreen, hideOnWideScreen, ...rest }: Props) => (
|
|
62
|
+
<StyledButton
|
|
63
|
+
ndlaFilm={ndlaFilm}
|
|
64
|
+
hideOnNarrowScreen={hideOnNarrowScreen}
|
|
65
|
+
hideOnWideScreen={hideOnWideScreen}
|
|
66
|
+
type="button"
|
|
67
|
+
{...rest}>
|
|
68
|
+
<StyledSpan>{children}</StyledSpan>
|
|
69
|
+
<Search />
|
|
70
|
+
</StyledButton>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export default ToggleSearchButton;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2017-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 SearchField from './SearchField';
|
|
10
|
+
|
|
11
|
+
import { SearchResultList, SearchResultItem } from './SearchResult';
|
|
12
|
+
|
|
13
|
+
import ActiveFilters from './ActiveFilters';
|
|
14
|
+
export { default as ToggleSearchButton } from './ToggleSearchButton';
|
|
15
|
+
|
|
16
|
+
export { SearchField, SearchResultItem, SearchResultList, ActiveFilters };
|
|
@@ -9,6 +9,7 @@ interface Props {
|
|
|
9
9
|
large?: boolean;
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
|
+
|
|
12
13
|
const SectionHeading = ({ children, large = false, className }: Props) => {
|
|
13
14
|
const Wrapper: ElementType = large ? 'h1' : 'h2';
|
|
14
15
|
return <Wrapper {...classes('', { large: !!large }, className)}>{children}</Wrapper>;
|
|
@@ -128,7 +128,6 @@ const SuggestionInput = ({
|
|
|
128
128
|
const { t } = useTranslation();
|
|
129
129
|
const [currentHighlightedIndex, setCurrentHighlightedIndex] = useState(0);
|
|
130
130
|
const [hasFocus, setHasFocus] = useState(false);
|
|
131
|
-
const initalRender = useRef(true);
|
|
132
131
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
133
132
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
134
133
|
const suggestionIdRef = useRef<string>(uuid());
|
|
@@ -138,14 +137,6 @@ const SuggestionInput = ({
|
|
|
138
137
|
setCurrentHighlightedIndex(0);
|
|
139
138
|
}, [suggestions]);
|
|
140
139
|
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
if (!initalRender.current) {
|
|
143
|
-
inputRef.current?.focus();
|
|
144
|
-
} else {
|
|
145
|
-
initalRender.current = false;
|
|
146
|
-
}
|
|
147
|
-
}, [addedTags]);
|
|
148
|
-
|
|
149
140
|
useEffect(() => {
|
|
150
141
|
const selectedSuggestionElement = document
|
|
151
142
|
.getElementById(suggestionIdRef.current)
|
|
@@ -22,7 +22,7 @@ const StyledUL = styled.ul<{ firstLevel?: boolean }>`
|
|
|
22
22
|
list-style: none;
|
|
23
23
|
margin: 0;
|
|
24
24
|
padding: 0;
|
|
25
|
-
margin-left: ${({ firstLevel }) => (firstLevel ? `-${spacing.xsmall}` : spacing.
|
|
25
|
+
margin-left: ${({ firstLevel }) => (firstLevel ? `-${spacing.xsmall}` : spacing.small)};
|
|
26
26
|
`;
|
|
27
27
|
|
|
28
28
|
const StyledLI = styled.li`
|