@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.
Files changed (148) hide show
  1. package/es/Article/ArticleAuthorContent.js +2 -4
  2. package/es/Article/ArticleFavoritesButton.js +2 -2
  3. package/es/AuthorInfo/AuthorInfo.js +29 -16
  4. package/es/ContentCard/ContentCard.js +66 -25
  5. package/es/FileList/File.js +34 -8
  6. package/es/FileList/FileList.js +29 -3
  7. package/es/InfoBox/InfoBox.js +10 -3
  8. package/es/InfoWidget/InfoWidget.js +67 -22
  9. package/es/MyNdla/Resource/FolderInput.js +19 -6
  10. package/es/NoContentBox/NoContentBox.js +1 -6
  11. package/es/Portrait/Portrait.js +19 -13
  12. package/es/Resource/BlockResource.js +7 -6
  13. package/es/Resource/ListResource.js +8 -7
  14. package/es/Search/ActiveFilterContent.js +4 -14
  15. package/es/Search/ActiveFilters.js +8 -19
  16. package/es/Search/SearchField.js +31 -52
  17. package/es/Search/SearchResult.js +113 -136
  18. package/es/Search/ToggleSearchButton.js +34 -43
  19. package/es/Search/index.js +2 -8
  20. package/es/TagSelector/SuggestionInput.js +9 -19
  21. package/es/TreeStructure/FolderItems.js +3 -3
  22. package/es/TreeStructure/FolderNameInput.js +33 -14
  23. package/es/TreeStructure/TreeStructure.js +3 -2
  24. package/es/all.css +1 -1
  25. package/es/index-javascript.js +0 -1
  26. package/es/index.js +2 -1
  27. package/es/locale/messages-en.js +11 -5
  28. package/es/locale/messages-nb.js +9 -3
  29. package/es/locale/messages-nn.js +11 -5
  30. package/es/locale/messages-se.js +29 -23
  31. package/es/locale/messages-sma.js +43 -37
  32. package/lib/Article/ArticleAuthorContent.js +9 -4
  33. package/lib/Article/ArticleFavoritesButton.js +2 -2
  34. package/lib/AuthorInfo/AuthorInfo.d.ts +1 -11
  35. package/lib/AuthorInfo/AuthorInfo.js +36 -20
  36. package/lib/ContentCard/ContentCard.d.ts +1 -15
  37. package/lib/ContentCard/ContentCard.js +60 -28
  38. package/lib/FileList/File.js +36 -12
  39. package/lib/FileList/FileList.js +28 -5
  40. package/lib/InfoBox/InfoBox.js +12 -5
  41. package/lib/InfoWidget/InfoWidget.js +61 -25
  42. package/lib/MediaList/MediaList.d.ts +1 -1
  43. package/lib/MyNdla/Resource/FolderInput.js +18 -5
  44. package/lib/NoContentBox/NoContentBox.js +1 -8
  45. package/lib/Portrait/Portrait.js +19 -14
  46. package/lib/Resource/BlockResource.js +7 -6
  47. package/lib/Resource/ListResource.js +8 -7
  48. package/lib/Search/ActiveFilterContent.d.ts +13 -0
  49. package/lib/Search/ActiveFilterContent.js +4 -15
  50. package/lib/Search/ActiveFilters.d.ts +13 -0
  51. package/lib/Search/ActiveFilters.js +8 -20
  52. package/lib/Search/SearchField.d.ts +19 -0
  53. package/lib/Search/SearchField.js +32 -56
  54. package/lib/Search/SearchResult.d.ts +36 -0
  55. package/lib/Search/SearchResult.js +116 -159
  56. package/lib/Search/ToggleSearchButton.d.ts +16 -0
  57. package/lib/Search/ToggleSearchButton.js +36 -46
  58. package/lib/Search/index.d.ts +12 -0
  59. package/lib/Search/index.js +0 -54
  60. package/lib/SearchTypeResult/SearchTypeHeader.d.ts +1 -1
  61. package/lib/TagSelector/SuggestionInput.js +9 -19
  62. package/lib/TreeStructure/FolderItems.js +3 -3
  63. package/lib/TreeStructure/FolderNameInput.js +35 -14
  64. package/lib/TreeStructure/TreeStructure.js +3 -2
  65. package/lib/TreeStructure/types.d.ts +1 -1
  66. package/lib/all.css +1 -1
  67. package/lib/index-javascript.js +0 -74
  68. package/lib/index.d.ts +1 -0
  69. package/lib/index.js +38 -1
  70. package/lib/locale/messages-en.d.ts +7 -1
  71. package/lib/locale/messages-en.js +11 -5
  72. package/lib/locale/messages-nb.d.ts +6 -0
  73. package/lib/locale/messages-nb.js +9 -3
  74. package/lib/locale/messages-nn.d.ts +7 -1
  75. package/lib/locale/messages-nn.js +11 -5
  76. package/lib/locale/messages-se.d.ts +7 -1
  77. package/lib/locale/messages-se.js +29 -23
  78. package/lib/locale/messages-sma.d.ts +12 -6
  79. package/lib/locale/messages-sma.js +43 -37
  80. package/package.json +10 -10
  81. package/src/Article/ArticleAuthorContent.tsx +1 -1
  82. package/src/Article/ArticleFavoritesButton.tsx +2 -2
  83. package/src/AuthorInfo/AuthorInfo.tsx +53 -19
  84. package/src/ContentCard/ContentCard.tsx +127 -35
  85. package/src/FileList/File.tsx +47 -17
  86. package/src/FileList/FileList.tsx +37 -8
  87. package/src/InfoBox/InfoBox.tsx +24 -4
  88. package/src/InfoWidget/InfoWidget.tsx +83 -34
  89. package/src/MediaList/MediaList.tsx +1 -1
  90. package/src/MyNdla/Resource/FolderInput.tsx +18 -3
  91. package/src/NoContentBox/NoContentBox.tsx +2 -7
  92. package/src/Portrait/Portrait.tsx +25 -10
  93. package/src/Resource/BlockResource.tsx +1 -1
  94. package/src/Resource/ListResource.tsx +3 -1
  95. package/src/Search/{ActiveFilterContent.jsx → ActiveFilterContent.tsx} +11 -12
  96. package/src/Search/{ActiveFilters.jsx → ActiveFilters.tsx} +20 -17
  97. package/src/Search/{SearchField.jsx → SearchField.tsx} +58 -68
  98. package/src/Search/SearchResult.tsx +360 -0
  99. package/src/Search/ToggleSearchButton.tsx +73 -0
  100. package/src/Search/component.search.scss +0 -4
  101. package/src/Search/index.ts +16 -0
  102. package/src/SectionHeading/SectionHeading.tsx +1 -0
  103. package/src/TagSelector/SuggestionInput.tsx +0 -9
  104. package/src/TreeStructure/FolderItems.tsx +1 -1
  105. package/src/TreeStructure/FolderNameInput.tsx +34 -9
  106. package/src/TreeStructure/TreeStructure.tsx +1 -0
  107. package/src/TreeStructure/types.ts +1 -1
  108. package/src/all.scss +0 -1
  109. package/src/index-javascript.js +0 -15
  110. package/src/index.ts +2 -0
  111. package/src/locale/messages-en.ts +10 -4
  112. package/src/locale/messages-nb.ts +9 -3
  113. package/src/locale/messages-nn.ts +10 -4
  114. package/src/locale/messages-se.ts +29 -23
  115. package/src/locale/messages-sma.ts +41 -35
  116. package/src/main.scss +0 -7
  117. package/es/Search/SearchFilter.js +0 -72
  118. package/es/Search/SearchFilterList.js +0 -115
  119. package/es/Search/SearchOverlay.js +0 -39
  120. package/es/Search/SearchPage.js +0 -178
  121. package/es/Search/SearchPopoverFilter.js +0 -152
  122. package/es/Search/SearchResultAuthor.js +0 -51
  123. package/lib/Search/SearchFilter.js +0 -88
  124. package/lib/Search/SearchFilterList.js +0 -137
  125. package/lib/Search/SearchOverlay.js +0 -62
  126. package/lib/Search/SearchPage.js +0 -207
  127. package/lib/Search/SearchPopoverFilter.js +0 -172
  128. package/lib/Search/SearchResultAuthor.js +0 -60
  129. package/src/AuthorInfo/component.author-info.scss +0 -54
  130. package/src/ContentCard/component.content-card.scss +0 -109
  131. package/src/FileList/component.file-list.scss +0 -102
  132. package/src/InfoBox/component.info-box.scss +0 -21
  133. package/src/InfoWidget/component.info-widget.scss +0 -52
  134. package/src/NoContentBox/component.no-content-box.scss +0 -17
  135. package/src/Portrait/component.portrait.scss +0 -29
  136. package/src/Search/SearchFilter.jsx +0 -82
  137. package/src/Search/SearchFilterList.jsx +0 -110
  138. package/src/Search/SearchOverlay.jsx +0 -38
  139. package/src/Search/SearchPage.jsx +0 -178
  140. package/src/Search/SearchPopoverFilter.jsx +0 -109
  141. package/src/Search/SearchResult.jsx +0 -239
  142. package/src/Search/SearchResultAuthor.jsx +0 -54
  143. package/src/Search/ToggleSearchButton.jsx +0 -64
  144. package/src/Search/component.search-filter.scss +0 -67
  145. package/src/Search/component.search-overlay.scss +0 -103
  146. package/src/Search/component.search-page.scss +0 -125
  147. package/src/Search/component.search-result-author.scss +0 -65
  148. package/src/Search/index.js +0 -34
@@ -1,178 +0,0 @@
1
- import React, { Component, Fragment, createRef } from 'react';
2
- import BEMHelper from 'react-bem-helper';
3
- import PropTypes from 'prop-types';
4
- import { Back } from '@ndla/icons/common';
5
- import { debounce } from 'lodash';
6
- import { getCurrentBreakpoint, breakpoints } from '@ndla/util';
7
- import Modal, { ModalHeader, ModalBody, ModalCloseButton } from '@ndla/modal';
8
- import Button from '@ndla/button';
9
- import { withTranslation } from 'react-i18next';
10
-
11
- import SearchField from './SearchField';
12
- import ActiveFilters from './ActiveFilters';
13
- import { SearchFieldForm } from './SearchFieldForm';
14
-
15
- const classes = BEMHelper('c-search-page');
16
- const filterClasses = BEMHelper('c-filter');
17
-
18
- class SearchPage extends Component {
19
- constructor(props) {
20
- super(props);
21
- this.state = {
22
- isNarrowScreen: false,
23
- };
24
-
25
- this.filterCloseButton = null;
26
- this.inputRef = createRef();
27
- this.checkScreenSize = this.checkScreenSize.bind(this);
28
- this.checkScreenSizeDebounce = debounce(() => this.checkScreenSize(), 100);
29
- }
30
-
31
- componentDidMount() {
32
- window.addEventListener('resize', this.checkScreenSizeDebounce);
33
- this.checkScreenSize();
34
- }
35
-
36
- componentWillUnmount() {
37
- window.removeEventListener('resize', this.checkScreenSizeDebounce);
38
- }
39
-
40
- checkScreenSize() {
41
- const currentBreakpoint = getCurrentBreakpoint();
42
- const isNarrowScreen = currentBreakpoint === breakpoints.mobile || currentBreakpoint === breakpoints.tablet;
43
-
44
- /* eslint react/no-did-mount-set-state: 0 */
45
- if (isNarrowScreen !== this.state.isNarrowScreen) {
46
- this.setState({
47
- isNarrowScreen,
48
- });
49
- }
50
- }
51
-
52
- render() {
53
- const {
54
- searchString,
55
- onSearchFieldChange,
56
- onSearchFieldFilterRemove,
57
- searchFieldFilters,
58
- onSearch,
59
- // only on narrow screen
60
- activeFilters,
61
- resourceToLinkProps,
62
- filters,
63
- children,
64
- messages,
65
- author,
66
- t,
67
- } = this.props;
68
-
69
- return (
70
- <main {...classes()}>
71
- <div {...classes('search-field-wrapper')}>
72
- <SearchFieldForm onSubmit={onSearch}>
73
- <SearchField
74
- inputRef={this.inputRef}
75
- value={searchString}
76
- onChange={onSearchFieldChange}
77
- placeholder={t('searchPage.searchFieldPlaceholder')}
78
- filters={searchFieldFilters}
79
- onFilterRemove={onSearchFieldFilterRemove}
80
- resourceToLinkProps={resourceToLinkProps}
81
- messages={{
82
- searchFieldTitle: t('searchPage.search'),
83
- }}
84
- />
85
- </SearchFieldForm>
86
- </div>
87
- {author}
88
- <div {...classes('filter-result-wrapper')}>
89
- <aside {...classes('filter-wrapper')}>
90
- <h1 {...classes('filter-heading')}>{t('searchPage.searchPageMessages.filterHeading')}</h1>
91
- <div {...classes('filters')}>{!this.state.isNarrowScreen && filters}</div>
92
- </aside>
93
- <div {...classes('result-wrapper')}>
94
- <div {...classes('active-filters')}>
95
- <ActiveFilters
96
- filters={activeFilters}
97
- showOnSmallScreen
98
- onFilterRemove={(value, filterName) => onSearchFieldFilterRemove(value, filterName)}
99
- />
100
- </div>
101
- <div {...classes('toggle-filter')}>
102
- <Modal
103
- animation="subtle"
104
- animationDuration={150}
105
- size="fullscreen"
106
- backgroundColor="grey"
107
- activateButton={<Button outline>{t('searchPage.searchPageMessages.filterHeading')}</Button>}>
108
- {(onClose) => (
109
- <Fragment>
110
- <ModalHeader modifier="white left-align">
111
- <ModalCloseButton
112
- title={
113
- <Fragment>
114
- <Back /> {messages.narrowScreenFilterHeading}
115
- </Fragment>
116
- }
117
- onClick={onClose}>
118
- Close
119
- </ModalCloseButton>
120
- </ModalHeader>
121
- <ModalBody modifier="slide-in-left no-side-padding-mobile">
122
- {filters}
123
- <div {...filterClasses('usefilter-wrapper')}>
124
- <Button outline onClick={onClose}>
125
- {t('searchPage.searchFilterMessages.useFilter')}
126
- </Button>
127
- </div>
128
- </ModalBody>
129
- </Fragment>
130
- )}
131
- </Modal>
132
- </div>
133
-
134
- {children}
135
- </div>
136
- </div>
137
- </main>
138
- );
139
- }
140
- }
141
-
142
- SearchPage.propTypes = {
143
- // should be <Fragment />
144
- filters: PropTypes.node.isRequired,
145
- children: PropTypes.node.isRequired,
146
- searchString: PropTypes.string.isRequired,
147
- onSearchFieldChange: PropTypes.func.isRequired,
148
- onSearch: PropTypes.func.isRequired,
149
- onSearchFieldFilterRemove: PropTypes.func.isRequired,
150
- resourceToLinkProps: PropTypes.func.isRequired,
151
- searchFieldFilters: PropTypes.arrayOf(
152
- PropTypes.shape({
153
- value: PropTypes.string.isRequired,
154
- title: PropTypes.string.isRequired,
155
- filterName: PropTypes.string.isRequired,
156
- }),
157
- ),
158
- activeFilters: PropTypes.arrayOf(
159
- PropTypes.shape({
160
- value: PropTypes.string.isRequired,
161
- title: PropTypes.string.isRequired,
162
- filterName: PropTypes.string.isRequired,
163
- }),
164
- ),
165
- messages: PropTypes.shape({
166
- narrowScreenFilterHeading: PropTypes.string.isRequired,
167
- }).isRequired,
168
- author: PropTypes.node,
169
- hideResultText: PropTypes.bool,
170
- filterScreenChange: PropTypes.func,
171
- t: PropTypes.func.isRequired,
172
- };
173
-
174
- SearchPage.defaultProps = {
175
- author: null,
176
- };
177
-
178
- export default withTranslation()(SearchPage);
@@ -1,109 +0,0 @@
1
- /* eslint-disable react/no-multi-comp */
2
-
3
- import React, { Fragment, Component } from 'react';
4
- import PropTypes from 'prop-types';
5
- import BEMHelper from 'react-bem-helper';
6
- import { ChevronRight } from '@ndla/icons/common';
7
- import Modal, { ModalCloseButton, ModalHeader, ModalBody } from '@ndla/modal';
8
- import Button from '@ndla/button';
9
- import { FilterList } from '../Filter';
10
-
11
- const filterClasses = new BEMHelper({
12
- name: 'filter',
13
- prefix: 'c-',
14
- });
15
-
16
- const messagesShape = PropTypes.shape({
17
- backButton: PropTypes.string.isRequired,
18
- closeButton: PropTypes.string.isRequired,
19
- confirmButton: PropTypes.string.isRequired,
20
- filterLabel: PropTypes.string.isRequired,
21
- hasValuesButtonText: PropTypes.string.isRequired,
22
- noValuesButtonText: PropTypes.string.isRequired,
23
- });
24
-
25
- class Popover extends Component {
26
- constructor(props) {
27
- super(props);
28
- this.state = {
29
- values: props.values,
30
- };
31
- }
32
-
33
- render() {
34
- const { messages, close, options, onChange } = this.props;
35
-
36
- return (
37
- <Fragment>
38
- <FilterList
39
- preid="search-popover"
40
- options={options}
41
- label={messages.filterLabel}
42
- values={this.state.values}
43
- modifiers="search-popover"
44
- onChange={(values) => {
45
- this.setState({
46
- values,
47
- });
48
- }}
49
- />
50
- <div {...filterClasses('usefilter-wrapper')}>
51
- <Button
52
- outline
53
- onClick={() => {
54
- close();
55
- onChange(this.state.values);
56
- }}>
57
- {messages.confirmButton}
58
- </Button>
59
- </div>
60
- </Fragment>
61
- );
62
- }
63
- }
64
-
65
- Popover.propTypes = {
66
- messages: messagesShape.isRequired,
67
- close: PropTypes.func,
68
- values: PropTypes.arrayOf(PropTypes.string).isRequired,
69
- options: PropTypes.arrayOf(
70
- PropTypes.shape({
71
- title: PropTypes.string.isRequired,
72
- value: PropTypes.string.isRequired,
73
- }),
74
- ).isRequired,
75
- onChange: PropTypes.func.isRequired,
76
- };
77
-
78
- const PopoverFilter = ({ messages, values, onChange, ...rest }) => {
79
- const buttonText = values.length > 0 ? messages.hasValuesButtonText : messages.noValuesButtonText;
80
-
81
- const buttonContent = (
82
- <button type="button" {...filterClasses('expand')}>
83
- <span>{buttonText}</span>
84
- <ChevronRight />
85
- </button>
86
- );
87
- return (
88
- <Modal animation="slide-up" size="medium" activateButton={buttonContent}>
89
- {(onClose) => (
90
- <Fragment>
91
- <ModalHeader modifiers="white">
92
- <ModalCloseButton title="Lukk" onClick={onClose} />
93
- </ModalHeader>
94
- <ModalBody modifier="no-side-padding-mobile">
95
- <Popover close={onClose} onChange={onChange} messages={messages} {...rest} values={values} />
96
- </ModalBody>
97
- </Fragment>
98
- )}
99
- </Modal>
100
- );
101
- };
102
-
103
- PopoverFilter.propTypes = {
104
- values: PropTypes.arrayOf(PropTypes.string).isRequired,
105
- messages: messagesShape.isRequired,
106
- onChange: PropTypes.func.isRequired,
107
- };
108
-
109
- export default PopoverFilter;
@@ -1,239 +0,0 @@
1
- import React, { Fragment } from 'react';
2
- import PropTypes from 'prop-types';
3
- import BEMHelper from 'react-bem-helper';
4
- import { ChevronRight } from '@ndla/icons/common';
5
- import { Cross } from '@ndla/icons/action';
6
- import { uuid } from '@ndla/util';
7
- import { useTranslation } from 'react-i18next';
8
- import Button from '@ndla/button';
9
- import { FilterTabs } from '@ndla/tabs';
10
- import Tooltip from '@ndla/tooltip';
11
- import SafeLink from '@ndla/safelink';
12
- import { Spinner } from '@ndla/icons';
13
-
14
- const resultClasses = BEMHelper('c-search-result');
15
-
16
- export const SearchResult = ({
17
- tabOptions,
18
- children,
19
- messages,
20
- searchString,
21
- currentTab,
22
- onTabChange,
23
- author,
24
- currentCompetenceGoal,
25
- competenceGoalsOpen,
26
- onToggleCompetenceGoals,
27
- competenceGoals,
28
- hideResultText,
29
- }) => {
30
- const { t } = useTranslation();
31
- return (
32
- <Fragment>
33
- <h2 {...resultClasses('result-label')}>{!hideResultText ? messages.resultHeading : '\u00A0'}</h2>
34
-
35
- <div {...resultClasses()}>
36
- {author || (
37
- <div {...resultClasses('heading-wrapper')}>
38
- <h1 {...resultClasses('heading', currentCompetenceGoal ? 'competence-goal' : null)}>
39
- {messages.searchStringLabel} <span>{searchString}</span>
40
- </h1>
41
- {competenceGoalsOpen && (
42
- <Button link {...resultClasses('close-competencegoals-btn')} onClick={onToggleCompetenceGoals}>
43
- {t('competenceGoals.closeCompetenceGoals')}
44
- <Cross className="c-icon--22 u-margin-left-tiny" />
45
- </Button>
46
- )}
47
- </div>
48
- )}
49
- <h2>{messages.subHeading}</h2>
50
- {!competenceGoalsOpen && currentCompetenceGoal && (
51
- <ul {...resultClasses('current-goal')}>
52
- <li>{currentCompetenceGoal}</li>
53
- </ul>
54
- )}
55
- {!competenceGoalsOpen && competenceGoals && (
56
- <p {...resultClasses('current-goal-info')}>
57
- {messages.openCompetenceGoalsButtonPrefix}{' '}
58
- <Button link onClick={onToggleCompetenceGoals}>
59
- {messages.openCompetenceGoalsButton}
60
- </Button>
61
- </p>
62
- )}
63
- {competenceGoalsOpen && <div {...resultClasses('competence-goals')}>{competenceGoals}</div>}
64
- {!competenceGoalsOpen && (
65
- <Fragment>
66
- <FilterTabs
67
- dropdownBtnLabel={t('searchPage.searchPageMessages.dropdownBtnLabel')}
68
- value={currentTab}
69
- options={tabOptions}
70
- contentId="search-result-content"
71
- onChange={onTabChange}>
72
- {children}
73
- </FilterTabs>
74
- <div {...resultClasses('narrow-result')}>{children}</div>
75
- </Fragment>
76
- )}
77
- </div>
78
- </Fragment>
79
- );
80
- };
81
-
82
- SearchResult.propTypes = {
83
- hideResultText: PropTypes.bool,
84
- tabOptions: PropTypes.arrayOf(
85
- PropTypes.shape({
86
- title: PropTypes.string.isRequired,
87
- value: PropTypes.string.isRequired,
88
- }),
89
- ).isRequired,
90
- currentTab: PropTypes.string,
91
- children: PropTypes.node.isRequired,
92
- messages: PropTypes.shape({
93
- searchStringLabel: PropTypes.string.isRequired,
94
- subHeading: PropTypes.string.isRequired,
95
- openCompetenceGoalsButtonPrefix: PropTypes.string,
96
- openCompetenceGoalsButton: PropTypes.string,
97
- }).isRequired,
98
- currentCompetenceGoal: PropTypes.string,
99
- competenceGoalsOpen: PropTypes.bool,
100
- onToggleCompetenceGoals: PropTypes.func,
101
- competenceGoals: PropTypes.node,
102
- searchString: (props, propName, componentName) => {
103
- if (props.author === null && typeof props[propName] !== 'string') {
104
- return new Error(
105
- `Invalid prop 'searchString' in ${componentName}. Required unless props.author === PropTypes.node`,
106
- );
107
- }
108
- return null;
109
- },
110
- onTabChange: PropTypes.func.isRequired,
111
- author: PropTypes.node,
112
- };
113
-
114
- SearchResult.defaultProps = {
115
- author: null,
116
- };
117
-
118
- const searchResultItemClasses = BEMHelper('c-search-result-item');
119
-
120
- const searchResultItemShape = PropTypes.shape({
121
- id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
122
- title: PropTypes.string.isRequired,
123
- url: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
124
- breadcrumb: PropTypes.arrayOf(PropTypes.string),
125
- subjects: PropTypes.arrayOf(
126
- PropTypes.shape({
127
- title: PropTypes.string.isRequired,
128
- url: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
129
- }),
130
- ),
131
- additional: PropTypes.bool,
132
- image: PropTypes.node,
133
- ingress: PropTypes.string.isRequired,
134
- contentTypeIcon: PropTypes.node.isRequired,
135
- contentTypeLabel: PropTypes.string.isRequired,
136
- });
137
-
138
- export const SearchResultItem = ({ item, subjectsLabel, additionalContentToolip, children }) => {
139
- const itemBreadcrumb = (item, cssClasses = {}) => {
140
- if (item.breadcrumb?.length > 0) {
141
- return (
142
- <div {...cssClasses}>
143
- {item.breadcrumb.map((breadcrumbItem, index) => {
144
- let icon = null;
145
- if (index !== item.breadcrumb.length - 1) {
146
- icon = <ChevronRight />;
147
- }
148
- return (
149
- <Fragment key={uuid()}>
150
- <span>{breadcrumbItem}</span>
151
- {icon}
152
- </Fragment>
153
- );
154
- })}
155
- </div>
156
- );
157
- }
158
- };
159
- return (
160
- <li key={item.id} {...searchResultItemClasses()}>
161
- <article>
162
- <header {...searchResultItemClasses('header')}>
163
- <h1>{item.url.href ? <a {...item.url}>{item.title}</a> : <SafeLink to={item.url}>{item.title}</SafeLink>}</h1>
164
- <div {...searchResultItemClasses('content-type-wrapper')}>{item.contentTypeIcon}</div>
165
- {item.contentTypeLabel && <div {...searchResultItemClasses('pills')}>{item.contentTypeLabel}</div>}
166
- {item.type && <div {...searchResultItemClasses('pills')}>{item.type}</div>}
167
- {item.additional && <div {...searchResultItemClasses('pills')}>{additionalContentToolip}</div>}
168
- {children}
169
- </header>
170
- <div {...searchResultItemClasses('content')}>
171
- <p {...searchResultItemClasses('ingress')} dangerouslySetInnerHTML={{ __html: item.ingress }} />
172
- {item.image}
173
- </div>
174
- {(!item.subjects || item.subjects.length === 0) && itemBreadcrumb(item, searchResultItemClasses('breadcrumb'))}
175
- {item.subjects && item.subjects.length !== 0 && (
176
- <div {...searchResultItemClasses('subjects')}>
177
- <span>{subjectsLabel}</span>
178
- <ul>
179
- {item.subjects.map((subject) => (
180
- <li key={uuid()}>
181
- <Tooltip tooltip={itemBreadcrumb(subject)}>
182
- {subject.url.href ? (
183
- <a {...subject.url}>{subject.title}</a>
184
- ) : (
185
- <SafeLink to={subject.url}>{subject.title}</SafeLink>
186
- )}
187
- </Tooltip>
188
- </li>
189
- ))}
190
- </ul>
191
- </div>
192
- )}
193
- </article>
194
- </li>
195
- );
196
- };
197
-
198
- SearchResultItem.propTypes = {
199
- item: searchResultItemShape.isRequired,
200
- additionalContentToolip: PropTypes.string.isRequired,
201
- subjectsLabel: PropTypes.string.isRequired,
202
- };
203
-
204
- export const SearchResultList = ({ results, component: Component, loading }) => {
205
- const { t } = useTranslation();
206
- if (loading) {
207
- return <Spinner />;
208
- }
209
- if (!results) {
210
- return <article className="c-search-result-list__empty" />;
211
- }
212
- return results.length === 0 ? (
213
- <article className="c-search-result-list__empty">
214
- <h1>{t('searchPage.searchResultListMessages.noResultHeading')}</h1>
215
- <p>{t('searchPage.searchResultListMessages.noResultDescription')}</p>
216
- </article>
217
- ) : (
218
- <ul className="c-search-result-list">
219
- {results.map((item) => (
220
- <Component
221
- key={`search_result_item_${typeof item.url === 'object' ? item.url.href : item.url}`}
222
- item={item}
223
- additionalContentToolip={t('resource.tooltipAdditionalTopic')}
224
- subjectsLabel={t('searchPage.searchResultListMessages.subjectsLabel')}>
225
- {item.children}
226
- </Component>
227
- ))}
228
- </ul>
229
- );
230
- };
231
-
232
- SearchResultList.defaultProps = {
233
- component: SearchResultItem,
234
- };
235
-
236
- SearchResultList.propTypes = {
237
- results: PropTypes.arrayOf(searchResultItemShape),
238
- loading: PropTypes.bool,
239
- };
@@ -1,54 +0,0 @@
1
- /*
2
- * Copyright (c) 2016-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 PropTypes from 'prop-types';
11
- import BEMHelper from 'react-bem-helper';
12
- import SafeLink from '@ndla/safelink';
13
- import Portrait from '../Portrait';
14
-
15
- const classes = new BEMHelper('c-search-result-author');
16
-
17
- const SearchResultAuthor = ({ messages, url, image, modifier }) => (
18
- <div {...classes('', modifier)}>
19
- <div>
20
- <h1 {...classes('heading')}>{messages.authorName}</h1>
21
- <p>{messages.role}</p>
22
- <p>{messages.phone}</p>
23
- <div {...classes('links')}>
24
- {messages.email && <SafeLink to={`mailto:${messages.email}`}>{messages.email}</SafeLink>}
25
- {url && (
26
- <SafeLink to={url} {...classes('url')}>
27
- {messages.readmoreLabel}
28
- </SafeLink>
29
- )}
30
- </div>
31
- </div>
32
- <Portrait src={image} alt={messages.authorName} {...classes('portrait-image')} />
33
- </div>
34
- );
35
-
36
- SearchResultAuthor.propTypes = {
37
- messages: PropTypes.shape({
38
- authorName: PropTypes.string.isRequired,
39
- role: PropTypes.string.isRequired,
40
- phone: PropTypes.string,
41
- email: PropTypes.string,
42
- readmoreLabel: (props, propName, componentName) => {
43
- if (typeof props.url === 'string' && typeof props[propName] !== 'string') {
44
- return new Error(`${componentName} messages.readmoreLabel is required when propTypes.url`);
45
- }
46
- return null;
47
- },
48
- }),
49
- image: PropTypes.string.isRequired,
50
- url: PropTypes.string,
51
- modifier: PropTypes.oneOf(['desktop', 'tablet']).isRequired,
52
- };
53
-
54
- export default SearchResultAuthor;
@@ -1,64 +0,0 @@
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 PropTypes from 'prop-types';
11
- import { css } from '@emotion/core';
12
- import { spacing, spacingUnit, breakpoints, mq, misc, fonts, colors } from '@ndla/core';
13
- import { Search } from '@ndla/icons/common';
14
- import Button from '@ndla/button';
15
-
16
- const style = (hideOnNarrowScreen, hideOnWideScreen, ndlaFilm) => css`
17
- background: ${ndlaFilm ? colors.ndlaFilm.filmColorBright : colors.brand.greyLighter};
18
- border-radius: ${misc.borderRadius};
19
- border: 0;
20
- display: flex;
21
- color: ${ndlaFilm ? '#fff' : colors.brand.primary};
22
- padding: ${spacing.small} ${spacingUnit * 0.75}px ${spacing.small} ${spacing.normal};
23
- ${hideOnNarrowScreen &&
24
- css`
25
- display: none;
26
- `}
27
-
28
- align-items: center;
29
- margin-left: ${spacing.normal};
30
-
31
- .c-icon {
32
- height: 24px;
33
- width: 24px;
34
- }
35
-
36
- ${fonts.sizes('16px', '32px')};
37
-
38
- ${mq.range({ from: breakpoints.desktop })} {
39
- display: ${hideOnWideScreen ? 'none' : 'flex'};
40
- margin-right: ${spacing.medium};
41
- padding: ${spacing.small} ${spacing.normal};
42
- }
43
- &:hover,
44
- &:focus,
45
- &:active {
46
- border: 0;
47
- }
48
- `;
49
-
50
- const ToggleSearchButton = ({ children, ndlaFilm, hideOnNarrowScreen, hideOnWideScreen, ...rest }) => (
51
- <Button type="button" css={style(hideOnNarrowScreen, hideOnWideScreen, ndlaFilm)} {...rest}>
52
- <span css={{ marginRight: spacing.normal, fontWeight: fonts.weight.normal }}>{children}</span>
53
- <Search />
54
- </Button>
55
- );
56
-
57
- ToggleSearchButton.propTypes = {
58
- children: PropTypes.node.isRequired,
59
- hideOnNarrowScreen: PropTypes.bool,
60
- hideOnWideScreen: PropTypes.bool,
61
- ndlaFilm: PropTypes.bool,
62
- };
63
-
64
- export default ToggleSearchButton;