@ndla/ui 33.4.4 → 33.4.5

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.
@@ -1,247 +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, { Component } from 'react';
10
- import { Back, ChevronRight } from '@ndla/icons/common';
11
- import { Switch } from '@ndla/switch';
12
- import styled from '@emotion/styled';
13
- import { css } from '@emotion/react';
14
- import { mq, breakpoints, fonts, spacing } from '@ndla/core';
15
- import SafeLink from '@ndla/safelink';
16
- import { withTranslation } from 'react-i18next';
17
- import ContentTypeResult from '../Search/ContentTypeResult';
18
- import { renderAdditionalIcon } from './TopicMenu';
19
-
20
- const switchLarge = css`
21
- display: none;
22
- ${mq.range({ from: breakpoints.mobileWide })} {
23
- display: inline-flex;
24
- }
25
- `;
26
-
27
- const switchSmall = css`
28
- display: inline-flex !important;
29
- align-items: normal !important;
30
- margin: 0 0 0 20px;
31
- min-height: 0 !important;
32
- ${mq.range({ from: breakpoints.mobileWide })} {
33
- display: none !important;
34
- }
35
- `;
36
-
37
- const StyledHeader = styled.h1`
38
- ${fonts.sizes('16px', '26px')};
39
- margin: 0;
40
- text-transform: uppercase;
41
- padding-right: ${spacing.normal};
42
- `;
43
-
44
- const StyledAside = styled.aside`
45
- padding: ${spacing.normal};
46
- ${mq.range({ from: breakpoints.tabletWide })} {
47
- padding: ${spacing.small};
48
- width: 600px;
49
- }
50
- `;
51
-
52
- const HeaderWrapper = styled.div`
53
- display: flex;
54
- `;
55
-
56
- const SubtopicLink = ({
57
- classes,
58
- to,
59
- subtopic: { id, name, additional },
60
- onSubtopicExpand,
61
- expandedSubtopicId,
62
- subtopicId,
63
- additionalTooltipLabel,
64
- }) => {
65
- const active = id === expandedSubtopicId;
66
-
67
- return (
68
- <li {...classes('subtopic-item', active && 'active')} key={id}>
69
- <SafeLink
70
- {...classes('link')}
71
- onClick={(event) => {
72
- event.preventDefault();
73
- onSubtopicExpand(subtopicId);
74
- }}
75
- to={to}>
76
- <span>
77
- {name}
78
- {renderAdditionalIcon(additional, additionalTooltipLabel)}
79
- </span>
80
- <ChevronRight />
81
- </SafeLink>
82
- </li>
83
- );
84
- };
85
-
86
- class SubtopicLinkList extends Component {
87
- constructor(props) {
88
- super(props);
89
- this.state = {
90
- showAdditionalResources: false,
91
- animateUL: 0,
92
- };
93
- this.toggleAdditionalResources = this.toggleAdditionalResources.bind(this);
94
- this.containerRef = null;
95
- }
96
-
97
- componentDidMount() {
98
- this.setFocusOnFirstLink();
99
- }
100
-
101
- componentDidUpdate(prevProps) {
102
- if (this.props.topic.id !== prevProps.topic.id) {
103
- this.setFocusOnFirstLink();
104
- }
105
- }
106
-
107
- setFocusOnFirstLink() {
108
- this.containerRef.querySelector('a').focus();
109
- }
110
-
111
- toggleAdditionalResources() {
112
- this.setState((prevState) => ({
113
- showAdditionalResources: !prevState.showAdditionalResources,
114
- animateUL: prevState.animateUL + 1,
115
- }));
116
- }
117
-
118
- render() {
119
- const {
120
- className,
121
- classes,
122
- closeMenu,
123
- topic,
124
- toTopic,
125
- expandedSubtopicId,
126
- onSubtopicExpand,
127
- onGoBack,
128
- backLabel,
129
- resourceToLinkProps,
130
- lastOpen,
131
- t,
132
- isUngrouped,
133
- } = this.props;
134
-
135
- const { showAdditionalResources, animateUL } = this.state;
136
-
137
- const hasSubTopics = topic.subtopics && topic.subtopics.length > 0;
138
- const hasContentTypeResults = lastOpen && topic.contentTypeResults && topic.contentTypeResults.length > 0;
139
-
140
- const someResourcesAreAdditional =
141
- hasContentTypeResults &&
142
- topic.contentTypeResults.some((result) => result.resources.some((resource) => resource.additional));
143
-
144
- return (
145
- <div
146
- className={className}
147
- ref={(ref) => {
148
- this.containerRef = ref;
149
- }}>
150
- <button type="button" {...classes('back-button')} onClick={onGoBack}>
151
- <Back /> <span>{backLabel}</span>
152
- </button>
153
- <SafeLink {...classes('link', ['big'])} onClick={closeMenu} to={toTopic(topic.id)}>
154
- <span {...classes('link-wrapper')}>
155
- <span {...classes('link-label')}>{t('masthead.menu.goTo')}:</span>
156
- <span {...classes('link-target')}>
157
- <span {...classes('link-target-name')}>{topic.name} </span>
158
- <ChevronRight className="c-icon--22" />
159
- </span>
160
- </span>
161
- <ChevronRight />
162
- </SafeLink>
163
- {hasSubTopics && (
164
- <ul {...classes('list')}>
165
- {topic.subtopics.map((subtopic) => (
166
- <SubtopicLink
167
- onSubtopicExpand={onSubtopicExpand}
168
- expandedSubtopicId={expandedSubtopicId}
169
- classes={classes}
170
- key={subtopic.id}
171
- to={toTopic(topic.id, subtopic.id)}
172
- subtopicId={subtopic.id}
173
- subtopic={subtopic}
174
- additionalTooltipLabel={t('resource.additionalTooltip')}
175
- />
176
- ))}
177
- </ul>
178
- )}
179
- {hasContentTypeResults && (
180
- <StyledAside>
181
- <HeaderWrapper>
182
- <StyledHeader>{t('masthead.menu.learningResourcesHeading')}</StyledHeader>
183
- {someResourcesAreAdditional && (
184
- <Switch
185
- id="showAdditionalId"
186
- checked={showAdditionalResources}
187
- label={t('masthead.menu.additionalFilterLabel')}
188
- onChange={this.toggleAdditionalResources}
189
- css={switchLarge}
190
- />
191
- )}
192
- </HeaderWrapper>
193
- {isUngrouped && (
194
- <ContentTypeResult
195
- animateUL={animateUL}
196
- resourceToLinkProps={resourceToLinkProps}
197
- onNavigate={closeMenu}
198
- contentTypeResult={{
199
- resources: topic.contentTypeResults
200
- .flatMap((grouped) =>
201
- grouped.resources.map((res) => ({
202
- ...res,
203
- contentType: grouped.contentType,
204
- })),
205
- )
206
- .sort((a, b) => a.rank - b.rank),
207
- }}
208
- messages={{
209
- noHit: t(`masthead.menu.contentTypeResultsNoHit.unGrouped`),
210
- }}
211
- unGrouped={isUngrouped}
212
- showAdditionalResources={showAdditionalResources}
213
- inMenu
214
- />
215
- )}
216
- {!isUngrouped &&
217
- topic.contentTypeResults.map((result) => (
218
- <ContentTypeResult
219
- animateUL={animateUL}
220
- resourceToLinkProps={resourceToLinkProps}
221
- onNavigate={closeMenu}
222
- key={result.title}
223
- contentTypeResult={result}
224
- messages={{
225
- noHit: t(`masthead.menu.contentTypeResultsNoHit.${result.contentType}`),
226
- }}
227
- showAdditionalResources={showAdditionalResources}
228
- inMenu
229
- />
230
- ))}
231
- {someResourcesAreAdditional && (
232
- <Switch
233
- id="showSomeAdditionalId"
234
- checked={showAdditionalResources}
235
- label={t('masthead.menu.additionalFilterLabel')}
236
- onChange={this.toggleAdditionalResources}
237
- css={switchSmall}
238
- />
239
- )}
240
- </StyledAside>
241
- )}
242
- </div>
243
- );
244
- }
245
- }
246
-
247
- export default withTranslation()(SubtopicLinkList);
@@ -1,370 +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
- // Can be removed when updating to jsx-a11y 6.x
10
- /* eslint jsx-a11y/no-noninteractive-element-to-interactive-role: 1 */
11
-
12
- import React, { Fragment, useEffect, useState } from 'react';
13
- import BEMHelper from 'react-bem-helper';
14
- import debounce from 'lodash/debounce';
15
- import { spacing } from '@ndla/core';
16
- import styled from '@emotion/styled';
17
-
18
- import { Home, Back, Additional, ChevronRight } from '@ndla/icons/common';
19
- import { Cross } from '@ndla/icons/action';
20
- import { ModalHeader } from '@ndla/modal';
21
- import { ButtonV2 as Button } from '@ndla/button';
22
- import SafeLink from '@ndla/safelink';
23
- import Tooltip from '@ndla/tooltip';
24
- import { useTranslation } from 'react-i18next';
25
- import SubtopicLinkList from './SubtopicLinkList';
26
-
27
- import Logo from '../Logo';
28
- import FrontpageAllSubjects from '../Frontpage/FrontpageAllSubjects';
29
- import NavigationBox from '../Navigation/NavigationBox';
30
- import { ProgrammeSubjects } from '../Programme';
31
- import { MessageBanner } from '../Messages';
32
-
33
- const classes = new BEMHelper({
34
- name: 'topic-menu',
35
- prefix: 'c-',
36
- });
37
-
38
- const StyledButton = styled(Button)`
39
- padding: ${spacing.xsmall} ${spacing.small};
40
- `;
41
-
42
- export const renderAdditionalIcon = (isAdditional, label) => {
43
- if (isAdditional && label) {
44
- return (
45
- <Tooltip tooltip={label} stooltipContainerClass="c-topic-menu__tooltipContainer">
46
- <Additional className="c-icon--20" />
47
- </Tooltip>
48
- );
49
- }
50
- if (isAdditional) {
51
- return <Additional className="c-icon--20 c-topic-menu__tooltipContainer" />;
52
- }
53
- return null;
54
- };
55
-
56
- const MENU_CURRENT_SUBJECT = 'subject';
57
- const MENU_CURRENT_PROGRAMME = 'programme';
58
- const MENU_PROGRAMMES = 'programmes';
59
- const MENU_ALL_SUBJECTS = 'allSubjects';
60
-
61
- export const TopicMenu = ({
62
- topics,
63
- toTopic,
64
- subjectTitle,
65
- toSubject,
66
- close: closeMenu,
67
- expandedTopicId,
68
- expandedSubtopicsId,
69
- resourceToLinkProps,
70
- hideSearch,
71
- defaultCount,
72
- searchFieldComponent,
73
- toFrontpage,
74
- locale,
75
- onNavigate,
76
- subjectCategories,
77
- programmes,
78
- currentProgramme,
79
- initialSelectedMenu,
80
- messages,
81
- closeAlert,
82
- selectedGrade,
83
- onGradeChange,
84
- }) => {
85
- const { t } = useTranslation();
86
- const [isNarrowScreen, setIsNarrowScreen] = useState(false);
87
- const [selectedMenu, setSelectedMenu] = useState(() => initialSelectedMenu || MENU_CURRENT_SUBJECT);
88
-
89
- useEffect(() => {
90
- const setScreenSize = (initial = false) => {
91
- const isNarrow = (window.innerWidth || document.documentElement.clientWidth) < 768;
92
-
93
- if ((initial && isNarrow) || !initial) {
94
- setIsNarrowScreen(isNarrow);
95
- }
96
- };
97
- const setScreenSizeDebounced = debounce(() => setScreenSize(false), 50);
98
- setScreenSize(true);
99
- window.addEventListener('resize', setScreenSizeDebounced);
100
- return () => {
101
- setScreenSizeDebounced.cancel();
102
- window.removeEventListener('resize', setScreenSizeDebounced);
103
- };
104
- }, []);
105
-
106
- const handleClick = (event, topicId) => {
107
- onNavigate(topicId, null);
108
- };
109
-
110
- const handleSubtopicExpand = (subtopicId, index) => {
111
- onNavigate(expandedTopicId, subtopicId, index);
112
- };
113
-
114
- const handleOnGoBack = () => {
115
- onNavigate(expandedSubtopicsId.length ? expandedTopicId : null, null);
116
- };
117
-
118
- const handleBtnKeyPress = (event, topicId) => {
119
- if (event.charCode === 32 || event.charCode === 13) {
120
- // space or enter
121
- event.preventDefault();
122
- onNavigate(topicId, null);
123
- }
124
- };
125
-
126
- const expandedTopic = topics.find((topic) => topic.id === expandedTopicId);
127
-
128
- const currentlyExpandedSubTopics = [];
129
- if (expandedTopic) {
130
- let currentSubtopic;
131
- let foundMatch;
132
- expandedSubtopicsId.forEach((id, index) => {
133
- if (index === 0) {
134
- currentSubtopic = expandedTopic.subtopics.find((topic) => topic.id === id);
135
- foundMatch = currentSubtopic ? 0 : undefined;
136
- } else {
137
- currentSubtopic = currentSubtopic.subtopics.find((topic) => topic.id === id);
138
- foundMatch += currentSubtopic ? 1 : 0;
139
- }
140
- if (foundMatch === index) {
141
- currentlyExpandedSubTopics[index] = currentSubtopic;
142
- }
143
- });
144
- }
145
-
146
- const hasExpandedSubtopics = currentlyExpandedSubTopics.length > 0;
147
- const subTopicModifiers = ['sub-topic'];
148
-
149
- if (!hasExpandedSubtopics) {
150
- subTopicModifiers.push('no-border');
151
- }
152
-
153
- const disableMain = isNarrowScreen && expandedTopic;
154
- const disableSubTopic = disableMain && hasExpandedSubtopics;
155
-
156
- const sliderCounter = !expandedTopicId ? 0 : expandedSubtopicsId.length + 1;
157
- return (
158
- <nav>
159
- {messages?.map((message) => (
160
- <MessageBanner
161
- key={message.number}
162
- showCloseButton={message.closable}
163
- onClose={() => closeAlert?.(message.number)}>
164
- {message.content}
165
- </MessageBanner>
166
- ))}
167
- <ModalHeader modifier={['white', 'menu']}>
168
- <div {...classes('masthead-left')}>
169
- <button type="button" {...classes('close-button')} onClick={closeMenu}>
170
- <Cross />
171
- <span>{t('masthead.menu.close')}</span>
172
- </button>
173
- </div>
174
- <div {...classes('masthead-right')}>
175
- {!hideSearch && searchFieldComponent}
176
- <Logo to="/" label={t('logo.altText')} locale={locale} />
177
- </div>
178
- </ModalHeader>
179
- <div {...classes('content')}>
180
- <div {...classes('back', 'wide')}>
181
- <SafeLink {...classes('back-link')} to={toFrontpage()} onClick={closeMenu}>
182
- <Home {...classes('home-icon', '', 'c-icon--20')} />
183
- {t('masthead.menu.toFrontpage')}
184
- </SafeLink>
185
- </div>
186
- <div
187
- {...classes('back', {
188
- narrow: true,
189
- })}>
190
- <SafeLink {...classes('back-link')} to={toFrontpage()} onClick={closeMenu}>
191
- <Home {...classes('home-icon', '', 'c-icon--20')} />
192
- {t('masthead.menu.toFrontpage')}
193
- </SafeLink>
194
- </div>
195
- <div {...classes('subject')}>
196
- <div {...classes('subject__header')}>
197
- <div {...classes('subject__header__menu-filter')}>
198
- {subjectTitle && (
199
- <StyledButton
200
- onClick={() => setSelectedMenu(MENU_CURRENT_SUBJECT)}
201
- colorTheme={selectedMenu !== MENU_CURRENT_SUBJECT ? 'lighter' : 'primary'}
202
- size="small"
203
- shape="pill">
204
- {subjectTitle}
205
- </StyledButton>
206
- )}
207
- {currentProgramme && (
208
- <StyledButton
209
- onClick={() => setSelectedMenu(MENU_CURRENT_PROGRAMME)}
210
- colorTheme={selectedMenu !== MENU_CURRENT_PROGRAMME ? 'lighter' : 'primary'}
211
- size="small"
212
- shape="pill">
213
- {currentProgramme.name}
214
- </StyledButton>
215
- )}
216
- {programmes && (
217
- <StyledButton
218
- onClick={() => setSelectedMenu(MENU_PROGRAMMES)}
219
- colorTheme={selectedMenu !== MENU_PROGRAMMES ? 'lighter' : 'primary'}
220
- size="small"
221
- shape="pill">
222
- {t('frontpageMenu.program')}
223
- </StyledButton>
224
- )}
225
- {subjectCategories && (
226
- <StyledButton
227
- onClick={() => setSelectedMenu(MENU_ALL_SUBJECTS)}
228
- colorTheme={selectedMenu !== MENU_ALL_SUBJECTS ? 'lighter' : 'primary'}
229
- size="small"
230
- shape="pill">
231
- {t('frontpageMenu.allsubjects')}
232
- </StyledButton>
233
- )}
234
- </div>
235
- </div>
236
- {selectedMenu === MENU_CURRENT_SUBJECT && (
237
- <div {...classes('back-button-slide-wrapper')}>
238
- <button
239
- type="button"
240
- {...classes('back-button-slides', `slide-${sliderCounter}`)}
241
- onClick={handleOnGoBack}>
242
- <Back /> <span>{t('masthead.menu.back')}</span>
243
- </button>
244
- </div>
245
- )}
246
- </div>
247
- {selectedMenu === MENU_ALL_SUBJECTS && subjectCategories && (
248
- <div {...classes('all-subjects')}>
249
- <FrontpageAllSubjects categories={subjectCategories} onNavigate={closeMenu} />
250
- </div>
251
- )}
252
- {selectedMenu === MENU_CURRENT_PROGRAMME && currentProgramme && (
253
- <div {...classes('all-subjects')}>
254
- <ProgrammeSubjects
255
- grades={currentProgramme.grades}
256
- selectedGrade={selectedGrade}
257
- onChangeGrade={onGradeChange}
258
- onNavigate={closeMenu}
259
- />
260
- </div>
261
- )}
262
- {selectedMenu === MENU_PROGRAMMES && programmes && (
263
- <div {...classes('all-subjects')}>
264
- <NavigationBox colorMode="light" items={programmes} listDirection="vertical" onClick={closeMenu} />
265
- </div>
266
- )}
267
- {selectedMenu === MENU_CURRENT_SUBJECT && (
268
- <div {...classes('subject-navigation', `slide-${sliderCounter}`)}>
269
- {!disableMain && (
270
- <Fragment>
271
- <div {...classes('section', 'main')}>
272
- <SafeLink onClick={closeMenu} to={toSubject()} className={classes('link', 'big').className}>
273
- <span {...classes('link-wrapper')}>
274
- <span {...classes('link-label')}>{t('masthead.menu.goTo')}:</span>
275
- <span {...classes('link-target')}>
276
- <span {...classes('link-target-name')}>{t('masthead.menu.subjectPage')}</span>
277
- <ChevronRight className="c-icon--22" />
278
- </span>
279
- </span>
280
- </SafeLink>
281
- <ul {...classes('list')}>
282
- {topics.map((topic) => {
283
- const active = topic.id === expandedTopicId ? 'active' : null;
284
-
285
- return (
286
- <li {...classes('topic-item', active)} key={topic.id}>
287
- <button
288
- type="button"
289
- {...classes('link')}
290
- onClick={(event) => handleClick(event, topic.id)}
291
- onKeyPress={(event) => handleBtnKeyPress(event, topic.id)}>
292
- <span>
293
- {topic.name}
294
- {renderAdditionalIcon(topic.additional, t('resource.additionalTooltip'))}
295
- </span>
296
- <ChevronRight />
297
- </button>
298
- </li>
299
- );
300
- })}
301
- </ul>
302
- </div>
303
- </Fragment>
304
- )}
305
- {expandedTopic && !disableSubTopic && (
306
- <SubtopicLinkList
307
- classes={classes}
308
- className={classes('section', subTopicModifiers).className}
309
- closeMenu={closeMenu}
310
- topic={expandedTopic}
311
- backLabel={
312
- !hasExpandedSubtopics
313
- ? subjectTitle
314
- : currentlyExpandedSubTopics[currentlyExpandedSubTopics.length - 1].name
315
- }
316
- goToTitle={t('masthead.menu.goTo')}
317
- toTopic={toTopic}
318
- expandedSubtopicId={currentlyExpandedSubTopics[0] && currentlyExpandedSubTopics[0].id}
319
- onSubtopicExpand={(id) => {
320
- handleSubtopicExpand(id, 0);
321
- }}
322
- onGoBack={handleOnGoBack}
323
- resourceToLinkProps={resourceToLinkProps}
324
- lastOpen={!hasExpandedSubtopics}
325
- />
326
- )}
327
-
328
- {currentlyExpandedSubTopics.map((subTopic, index) => {
329
- const { metadata } = subTopic;
330
- const isUngrouped = metadata?.customFields['topic-resources'] === 'ungrouped' || false;
331
-
332
- return (
333
- <SubtopicLinkList
334
- isUngrouped={isUngrouped}
335
- key={subTopic.id}
336
- classes={classes}
337
- className={classes('section', ['sub-topic', 'no-border']).className}
338
- closeMenu={closeMenu}
339
- topic={subTopic}
340
- backLabel={
341
- index === 0
342
- ? topics.find((topic) => topic.id === expandedTopicId).name
343
- : currentlyExpandedSubTopics[index - 1].name
344
- }
345
- toTopic={toTopic}
346
- expandedSubtopicId={
347
- currentlyExpandedSubTopics[index + 1] ? currentlyExpandedSubTopics[index + 1].id : 'no way'
348
- }
349
- onSubtopicExpand={(id) => {
350
- handleSubtopicExpand(id, index + 1);
351
- }}
352
- onGoBack={handleOnGoBack}
353
- resourceToLinkProps={resourceToLinkProps}
354
- lastOpen={sliderCounter === index + 2}
355
- defaultCount={defaultCount}
356
- />
357
- );
358
- })}
359
- </div>
360
- )}
361
- </div>
362
- </nav>
363
- );
364
- };
365
-
366
- TopicMenu.defaultProps = {
367
- defaultCount: 12,
368
- };
369
-
370
- export default TopicMenu;
@@ -1,51 +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 { css } from '@emotion/react';
11
- import { spacing, fonts, colors, mq, breakpoints } from '@ndla/core';
12
- import { Menu } from '@ndla/icons/common';
13
- import { ButtonV2 as Button } from '@ndla/button';
14
-
15
- const style = css`
16
- display: block;
17
- position: relative;
18
- background: transparent;
19
- padding: ${spacing.small} ${spacing.normal};
20
- font-weight: ${fonts.weight.normal};
21
-
22
- svg {
23
- width: 25px;
24
- height: 25px;
25
- margin-top: -3px;
26
- margin-right: ${spacing.xsmall};
27
- }
28
- &:hover {
29
- border-color: transparent;
30
- background: ${colors.brand.primary};
31
- color: ${colors.white};
32
- }
33
- &:active,
34
- &:focus {
35
- border-color: ${colors.brand.lighter};
36
- background: ${colors.white};
37
- color: ${colors.brand.primary};
38
- }
39
- ${mq.range({ until: breakpoints.tablet })} {
40
- padding-left: ${spacing.xsmall};
41
- padding-right: ${spacing.xsmall};
42
- }
43
- `;
44
-
45
- const TopicMenuButton = ({ ndlaFilm, children, ...rest }) => (
46
- <Button inverted={ndlaFilm} variant="outline" css={style} {...rest}>
47
- <Menu /> {children}
48
- </Button>
49
- );
50
-
51
- export default TopicMenuButton;