@ndla/ui 30.8.3 → 30.9.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.
Files changed (70) hide show
  1. package/es/Article/Article.js +3 -3
  2. package/es/Article/ArticleSideBar.js +5 -5
  3. package/es/AudioPlayer/Controls.js +26 -26
  4. package/es/AudioPlayer/SpeechControl.js +1 -1
  5. package/es/Frontpage/FrontpageSearch.js +2 -9
  6. package/es/MediaList/MediaList.js +1 -0
  7. package/es/Messages/MessageBox.js +10 -10
  8. package/es/NDLAFilm/AllMoviesAlphabetically.js +11 -11
  9. package/es/NDLAFilm/FilmSlideshow.js +7 -7
  10. package/es/NDLAFilm/MovieGrid.js +4 -4
  11. package/es/Programme/Programme.js +5 -5
  12. package/es/ResourceGroup/ResourceItem.js +12 -13
  13. package/es/ResourceGroup/ResourceList.js +3 -4
  14. package/es/Search/SearchField.js +2 -3
  15. package/es/Subject/SubjectNewContent.js +10 -10
  16. package/es/TreeStructure/AddFolderButton.js +9 -9
  17. package/es/TreeStructure/ComboboxButton.js +30 -5
  18. package/es/TreeStructure/TreeStructure.js +9 -7
  19. package/es/i18n/index.js +1 -2
  20. package/es/index.js +1 -1
  21. package/lib/Article/Article.js +3 -3
  22. package/lib/Article/ArticleSideBar.js +5 -5
  23. package/lib/AudioPlayer/Controls.js +26 -26
  24. package/lib/AudioPlayer/SpeechControl.js +1 -1
  25. package/lib/Frontpage/FrontpageSearch.js +2 -9
  26. package/lib/MediaList/MediaList.js +1 -0
  27. package/lib/Messages/MessageBox.js +10 -10
  28. package/lib/NDLAFilm/AllMoviesAlphabetically.js +11 -11
  29. package/lib/NDLAFilm/FilmSlideshow.js +7 -7
  30. package/lib/NDLAFilm/MovieGrid.js +4 -4
  31. package/lib/Programme/Programme.js +5 -5
  32. package/lib/ResourceGroup/ResourceItem.js +11 -11
  33. package/lib/ResourceGroup/ResourceList.js +2 -2
  34. package/lib/Search/SearchField.js +2 -3
  35. package/lib/Subject/SubjectNewContent.js +10 -10
  36. package/lib/TreeStructure/AddFolderButton.d.ts +2 -1
  37. package/lib/TreeStructure/AddFolderButton.js +9 -9
  38. package/lib/TreeStructure/ComboboxButton.d.ts +1 -0
  39. package/lib/TreeStructure/ComboboxButton.js +29 -3
  40. package/lib/TreeStructure/TreeStructure.js +9 -7
  41. package/lib/i18n/index.d.ts +0 -1
  42. package/lib/i18n/index.js +1 -8
  43. package/lib/index.d.ts +1 -1
  44. package/lib/index.js +0 -7
  45. package/package.json +10 -12
  46. package/src/Article/Article.tsx +1 -1
  47. package/src/Article/ArticleSideBar.tsx +1 -1
  48. package/src/AudioPlayer/Controls.tsx +1 -0
  49. package/src/AudioPlayer/SpeechControl.tsx +1 -0
  50. package/src/Frontpage/FrontpageSearch.tsx +0 -7
  51. package/src/MediaList/MediaList.tsx +1 -0
  52. package/src/Messages/MessageBox.tsx +1 -1
  53. package/src/NDLAFilm/AllMoviesAlphabetically.tsx +1 -1
  54. package/src/NDLAFilm/FilmSlideshow.tsx +1 -1
  55. package/src/NDLAFilm/MovieGrid.tsx +2 -1
  56. package/src/Programme/Programme.tsx +1 -2
  57. package/src/ResourceGroup/ResourceItem.tsx +1 -2
  58. package/src/ResourceGroup/ResourceList.tsx +1 -2
  59. package/src/Search/SearchField.tsx +1 -2
  60. package/src/Subject/SubjectNewContent.tsx +2 -2
  61. package/src/TreeStructure/AddFolderButton.tsx +16 -17
  62. package/src/TreeStructure/ComboboxButton.tsx +45 -20
  63. package/src/TreeStructure/TreeStructure.tsx +2 -0
  64. package/src/i18n/index.ts +0 -1
  65. package/src/index.ts +1 -1
  66. package/es/i18n/formatMessage.js +0 -45
  67. package/lib/i18n/formatMessage.d.ts +0 -14
  68. package/lib/i18n/formatMessage.js +0 -53
  69. package/src/i18n/__tests__/formatMessage-test.ts +0 -34
  70. package/src/i18n/formatMessage.ts +0 -61
@@ -66,8 +66,9 @@ const MovieGrid = ({
66
66
  <MovieListing marginLeft={autoSizedProps.margin}>
67
67
  {fetchingMoviesByType && <LoadingPlaceholder height={loadingPlaceholderHeight} />}
68
68
  {!fetchingMoviesByType &&
69
- moviesByType.map((movie) => (
69
+ moviesByType.map((movie, index) => (
70
70
  <FilmContentCard
71
+ key={index}
71
72
  hideTags
72
73
  movie={movie}
73
74
  columnWidth={autoSizedProps.columnWidth}
@@ -3,8 +3,7 @@ import styled from '@emotion/styled';
3
3
  import { breakpoints, mq, spacing } from '@ndla/core';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import LayoutItem, { OneColumn } from '../Layout';
6
- import ProgrammeSubjects from './ProgrammeSubjects';
7
- import { GradesProps } from './ProgrammeSubjects';
6
+ import ProgrammeSubjects, { GradesProps } from './ProgrammeSubjects';
8
7
  import MessageBox from '../Messages/MessageBox';
9
8
  import { NavigationHeading } from '..';
10
9
  const StyledWrapper = styled.div`
@@ -9,8 +9,7 @@
9
9
  import React, { ReactNode } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import styled from '@emotion/styled';
12
- import { css } from '@emotion/react';
13
- import { keyframes } from '@emotion/react';
12
+ import { css, keyframes } from '@emotion/react';
14
13
  import SafeLink from '@ndla/safelink';
15
14
  import { Additional, Core, HumanMaleBoard } from '@ndla/icons/common';
16
15
  import { breakpoints, colors, fonts, mq, spacing } from '@ndla/core';
@@ -8,8 +8,7 @@
8
8
 
9
9
  import React, { ReactNode } from 'react';
10
10
  import styled from '@emotion/styled';
11
- import { css } from '@emotion/react';
12
- import { keyframes } from '@emotion/react';
11
+ import { css, keyframes } from '@emotion/react';
13
12
  import { useTranslation } from 'react-i18next';
14
13
  import NoContentBox from '../NoContentBox';
15
14
  import ResourceItem from './ResourceItem';
@@ -134,8 +134,7 @@ const SearchField = ({
134
134
  onChange('');
135
135
  onFocus?.();
136
136
  inputRef?.current?.focus();
137
- }}
138
- onBlur={onBlur}>
137
+ }}>
139
138
  {t('welcomePage.resetSearch')}
140
139
  </button>
141
140
  )}
@@ -91,8 +91,8 @@ const SubjectNewContent = ({ heading, content }: Props) => (
91
91
  <StyledSubjectSectionTitle>{heading}</StyledSubjectSectionTitle>
92
92
  <nav>
93
93
  <StyledUl>
94
- {content.map((item) => (
95
- <StyledListItem>
94
+ {content.map((item, index) => (
95
+ <StyledListItem key={index}>
96
96
  <LeftWrapper>
97
97
  <ContentTypeBadge type={item.contentType} size="x-small" background border />
98
98
  <ContentLinkWrapper>
@@ -19,6 +19,7 @@ interface AddFolderButtonProps {
19
19
  focusedFolder?: FolderType;
20
20
  setNewFolderParentId: (id?: string) => void;
21
21
  setShowTree: (value: boolean) => void;
22
+ loading?: boolean;
22
23
  }
23
24
 
24
25
  const StyledAddFolderButton = styled(Button)`
@@ -33,30 +34,28 @@ const StyledPlus = styled(Plus)`
33
34
  width: 24px;
34
35
  `;
35
36
 
36
- const AddFolderButton = ({ canAddFolder, setNewFolderParentId, focusedFolder, setShowTree }: AddFolderButtonProps) => {
37
+ const AddFolderButton = ({
38
+ canAddFolder,
39
+ loading,
40
+ setNewFolderParentId,
41
+ focusedFolder,
42
+ setShowTree,
43
+ }: AddFolderButtonProps) => {
37
44
  const { t } = useTranslation();
38
45
  const ref = useRef<HTMLButtonElement>(null);
46
+ const tooltip = loading
47
+ ? t('loading')
48
+ : canAddFolder
49
+ ? t('myNdla.newFolderUnder', { folderName: focusedFolder?.name })
50
+ : t('treeStructure.maxFoldersAlreadyAdded');
39
51
  return (
40
- <Tooltip
41
- tooltip={
42
- canAddFolder
43
- ? t('myNdla.newFolderUnder', {
44
- folderName: focusedFolder?.name,
45
- })
46
- : t('treeStructure.maxFoldersAlreadyAdded')
47
- }>
52
+ <Tooltip tooltip={tooltip}>
48
53
  <StyledAddFolderButton
49
54
  ref={ref}
50
55
  variant="outline"
51
56
  shape="pill"
52
- disabled={!canAddFolder}
53
- aria-label={
54
- canAddFolder
55
- ? t('myNdla.newFolderUnder', {
56
- folderName: focusedFolder?.name,
57
- })
58
- : t('treeStructure.maxFoldersAlreadyAdded')
59
- }
57
+ disabled={loading || !canAddFolder}
58
+ aria-label={tooltip}
60
59
  onMouseDown={(e) => {
61
60
  e.preventDefault();
62
61
  e.stopPropagation();
@@ -6,16 +6,16 @@
6
6
  *
7
7
  */
8
8
 
9
- import React, { KeyboardEvent } from 'react';
9
+ import React, { KeyboardEvent, forwardRef } from 'react';
10
10
  import styled from '@emotion/styled';
11
11
  import { useForwardedRef } from '@ndla/util';
12
12
  import { breakpoints, colors, mq, spacing } from '@ndla/core';
13
13
  import { ChevronUp, ChevronDown } from '@ndla/icons/common';
14
- import { forwardRef } from 'react';
15
14
  import { ButtonV2 as Button, IconButtonV2 as IconButton } from '@ndla/button';
16
15
  import { treestructureId } from './helperFunctions';
17
16
  import { FolderType, TreeStructureType } from './types';
18
17
  import { arrowNavigation } from './arrowNavigation';
18
+ import ContentLoader from '../ContentLoader';
19
19
 
20
20
  interface StyledRowProps {
21
21
  isOpen: boolean;
@@ -52,6 +52,7 @@ interface Props {
52
52
  showTree: boolean;
53
53
  type: TreeStructureType;
54
54
  label?: string;
55
+ loading?: boolean;
55
56
  focusedFolder?: FolderType;
56
57
  selectedFolder?: FolderType;
57
58
  setSelectedFolder: (folder: FolderType) => void;
@@ -77,6 +78,7 @@ const ComboboxButton = forwardRef<HTMLButtonElement, Props>(
77
78
  setFocusedFolder,
78
79
  onOpenFolder,
79
80
  onCloseFolder,
81
+ loading,
80
82
  ariaDescribedby,
81
83
  },
82
84
  ref,
@@ -116,25 +118,48 @@ const ComboboxButton = forwardRef<HTMLButtonElement, Props>(
116
118
  innerRef.current?.focus();
117
119
  }
118
120
  }}>
119
- <StyledSelectedFolder
120
- ref={innerRef}
121
- tabIndex={0}
122
- id={treestructureId(type, 'combobox')}
123
- role="combobox"
124
- aria-controls={treestructureId(type, 'popup')}
125
- aria-haspopup="tree"
126
- aria-expanded={showTree}
127
- aria-labelledby={label ? treestructureId(type, 'label') : undefined}
128
- aria-activedescendant={focusedFolder ? treestructureId(type, focusedFolder.id) : undefined}
129
- aria-describedby={ariaDescribedby}
121
+ {loading && (
122
+ <ContentLoader width={1000} height={40}>
123
+ <rect x="15" y="0" width="1000" rx="3" ry="3" r="15" height="40" />
124
+ </ContentLoader>
125
+ )}
126
+ {!loading && (
127
+ <StyledSelectedFolder
128
+ ref={innerRef}
129
+ tabIndex={0}
130
+ id={treestructureId(type, 'combobox')}
131
+ role="combobox"
132
+ aria-controls={treestructureId(type, 'popup')}
133
+ aria-haspopup="tree"
134
+ aria-expanded={showTree}
135
+ aria-labelledby={label ? treestructureId(type, 'label') : undefined}
136
+ aria-activedescendant={focusedFolder ? treestructureId(type, focusedFolder.id) : undefined}
137
+ aria-describedby={ariaDescribedby}
138
+ variant="ghost"
139
+ colorTheme="light"
140
+ fontWeight="normal"
141
+ shape="sharp"
142
+ onKeyDown={onKeyDown}
143
+ onClick={() => {
144
+ innerRef.current?.focus();
145
+ onToggleTree(!showTree);
146
+ }}>
147
+ {selectedFolder?.name}
148
+ </StyledSelectedFolder>
149
+ )}
150
+ <IconButton
151
+ disabled={loading}
152
+ aria-busy={loading}
153
+ aria-hidden
154
+ aria-label=""
155
+ tabIndex={-1}
130
156
  variant="ghost"
131
- colorTheme="light"
132
- fontWeight="normal"
133
- shape="sharp"
134
- onKeyDown={onKeyDown}>
135
- {selectedFolder?.name}
136
- </StyledSelectedFolder>
137
- <IconButton aria-hidden aria-label="" tabIndex={-1} variant="ghost" colorTheme="greyLighter" size="small">
157
+ colorTheme="greyLighter"
158
+ size="small"
159
+ onClick={() => {
160
+ innerRef.current?.focus();
161
+ onToggleTree(!showTree);
162
+ }}>
138
163
  {showTree ? <ChevronUp /> : <ChevronDown />}
139
164
  </IconButton>
140
165
  </StyledRow>
@@ -189,6 +189,7 @@ const TreeStructure = ({
189
189
  {label && <StyledLabel id={treestructureId(type, 'label')}>{label}</StyledLabel>}
190
190
  {type === 'picker' && (
191
191
  <AddFolderButton
192
+ loading={loading}
192
193
  canAddFolder={!!canAddFolder}
193
194
  focusedFolder={focusedFolder}
194
195
  setNewFolderParentId={setNewFolderParentId}
@@ -203,6 +204,7 @@ const TreeStructure = ({
203
204
  showTree={showTree}
204
205
  type={type}
205
206
  label={label}
207
+ loading={loading}
206
208
  focusedFolder={focusedFolder}
207
209
  selectedFolder={selectedFolder}
208
210
  setSelectedFolder={setSelectedFolder}
package/src/i18n/index.ts CHANGED
@@ -8,4 +8,3 @@
8
8
 
9
9
  export { i18nInstance } from './i18n';
10
10
  export { formatNestedMessages } from './formatNestedMessages';
11
- export { formatMessage } from './formatMessage';
package/src/index.ts CHANGED
@@ -174,7 +174,7 @@ export { default as Breadcrumb, HeaderBreadcrumb, HomeBreadcrumb, ActionBreadcru
174
174
  export type { SimpleBreadcrumbItem, IndexedBreadcrumbItem } from './Breadcrumb';
175
175
 
176
176
  export type { BreadcrumbItemProps } from './Breadcrumblist';
177
- export { i18nInstance, formatNestedMessages, formatMessage } from './i18n';
177
+ export { i18nInstance, formatNestedMessages } from './i18n';
178
178
  export { default as ResourceGroup } from './ResourceGroup';
179
179
 
180
180
  export { default as LayoutItem, OneColumn, PageContainer, Content } from './Layout';
@@ -1,45 +0,0 @@
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 invariant from 'invariant';
10
- export var formatMessage = function formatMessage(locale, messages, getMessageFormat, id) {
11
- var values = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
12
- // `id` is a required parameter.
13
- invariant(id, 'An `id` must be provided to format a message.');
14
- var message = messages && messages[id];
15
- var hasValues = Object.keys(values).length > 0;
16
-
17
- // Avoid expensive message formatting for simple messages without values. In
18
- // development messages will always be formatted in case of missing values.
19
- if (!hasValues && process.env.NODE_ENV === 'production') {
20
- return message || id;
21
- }
22
- var formattedMessage;
23
- if (message) {
24
- try {
25
- var formatter = getMessageFormat(message, locale, {});
26
- formattedMessage = formatter.format(values);
27
- } catch (e) {
28
- if (process.env.NODE_ENV !== 'production') {
29
- // eslint-disable-next-line no-console
30
- console.error("Error formatting message: \"".concat(id, "\" for locale: \"").concat(locale, "\"\n").concat(e));
31
- }
32
- }
33
- } else if (process.env.NODE_ENV !== 'production') {
34
- // eslint-disable-next-line no-console
35
- console.error("Missing message: \"".concat(id, "\" for locale: \"").concat(locale, "\", using id as fallback"));
36
- return id;
37
- }
38
- if (!formattedMessage) {
39
- if (process.env.NODE_ENV !== 'production') {
40
- // eslint-disable-next-line no-console
41
- console.error("Cannot format message: \"".concat(id, "\", using message source as fallback."));
42
- }
43
- }
44
- return formattedMessage || message || id;
45
- };
@@ -1,14 +0,0 @@
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
- interface Messages {
9
- [key: string]: string;
10
- }
11
- export declare const formatMessage: (locale: string, messages: Messages, getMessageFormat: any, id: string, values?: {
12
- [key: string]: any;
13
- }) => string;
14
- export {};
@@ -1,53 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.formatMessage = void 0;
7
- var _invariant = _interopRequireDefault(require("invariant"));
8
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
9
- /**
10
- * Copyright (c) 2017-present, NDLA.
11
- *
12
- * This source code is licensed under the GPLv3 license found in the
13
- * LICENSE file in the root directory of this source tree.
14
- *
15
- */
16
-
17
- var formatMessage = function formatMessage(locale, messages, getMessageFormat, id) {
18
- var values = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
19
- // `id` is a required parameter.
20
- (0, _invariant["default"])(id, 'An `id` must be provided to format a message.');
21
- var message = messages && messages[id];
22
- var hasValues = Object.keys(values).length > 0;
23
-
24
- // Avoid expensive message formatting for simple messages without values. In
25
- // development messages will always be formatted in case of missing values.
26
- if (!hasValues && process.env.NODE_ENV === 'production') {
27
- return message || id;
28
- }
29
- var formattedMessage;
30
- if (message) {
31
- try {
32
- var formatter = getMessageFormat(message, locale, {});
33
- formattedMessage = formatter.format(values);
34
- } catch (e) {
35
- if (process.env.NODE_ENV !== 'production') {
36
- // eslint-disable-next-line no-console
37
- console.error("Error formatting message: \"".concat(id, "\" for locale: \"").concat(locale, "\"\n").concat(e));
38
- }
39
- }
40
- } else if (process.env.NODE_ENV !== 'production') {
41
- // eslint-disable-next-line no-console
42
- console.error("Missing message: \"".concat(id, "\" for locale: \"").concat(locale, "\", using id as fallback"));
43
- return id;
44
- }
45
- if (!formattedMessage) {
46
- if (process.env.NODE_ENV !== 'production') {
47
- // eslint-disable-next-line no-console
48
- console.error("Cannot format message: \"".concat(id, "\", using message source as fallback."));
49
- }
50
- }
51
- return formattedMessage || message || id;
52
- };
53
- exports.formatMessage = formatMessage;
@@ -1,34 +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
- /* eslint-env jest */
10
- import IntlMessageFormat from 'intl-messageformat';
11
- import memoizeIntlConstructor from 'intl-format-cache';
12
- import { formatMessage } from '../formatMessage';
13
-
14
- const getMessageFormat = memoizeIntlConstructor(IntlMessageFormat);
15
- const locale = 'nb';
16
- const messages = {
17
- helloworld: 'Hello world',
18
- 'test.me': 'Test me {test}',
19
- 'welcome.to.my.unittest': 'Welcome to my unittest',
20
- };
21
-
22
- test('@ndla/i18n/formatMessage message with matching ID', () => {
23
- expect(formatMessage(locale, messages, getMessageFormat, 'helloworld')).toBe('Hello world');
24
- });
25
- test('@ndla/i18n/formatMessage message without matching ID', () => {
26
- expect(formatMessage(locale, messages, getMessageFormat, 'does.not.exists')).toBe('does.not.exists');
27
- });
28
- test('@ndla/i18n/formatMessage matching message with value', () => {
29
- expect(
30
- formatMessage(locale, messages, getMessageFormat, 'test.me', {
31
- test: '1337',
32
- }),
33
- ).toBe('Test me 1337');
34
- });
@@ -1,61 +0,0 @@
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 invariant from 'invariant';
10
-
11
- interface Messages {
12
- [key: string]: string;
13
- }
14
-
15
- export const formatMessage = (
16
- locale: string,
17
- messages: Messages,
18
- getMessageFormat: any,
19
- id: string,
20
- values: { [key: string]: any } = {},
21
- ): string => {
22
- // `id` is a required parameter.
23
- invariant(id, 'An `id` must be provided to format a message.');
24
-
25
- const message = messages && messages[id];
26
- const hasValues = Object.keys(values).length > 0;
27
-
28
- // Avoid expensive message formatting for simple messages without values. In
29
- // development messages will always be formatted in case of missing values.
30
- if (!hasValues && process.env.NODE_ENV === 'production') {
31
- return message || id;
32
- }
33
-
34
- let formattedMessage;
35
-
36
- if (message) {
37
- try {
38
- const formatter = getMessageFormat(message, locale, {});
39
-
40
- formattedMessage = formatter.format(values);
41
- } catch (e) {
42
- if (process.env.NODE_ENV !== 'production') {
43
- // eslint-disable-next-line no-console
44
- console.error(`Error formatting message: "${id}" for locale: "${locale}"\n${e}`);
45
- }
46
- }
47
- } else if (process.env.NODE_ENV !== 'production') {
48
- // eslint-disable-next-line no-console
49
- console.error(`Missing message: "${id}" for locale: "${locale}", using id as fallback`);
50
- return id;
51
- }
52
-
53
- if (!formattedMessage) {
54
- if (process.env.NODE_ENV !== 'production') {
55
- // eslint-disable-next-line no-console
56
- console.error(`Cannot format message: "${id}", using message source as fallback.`);
57
- }
58
- }
59
-
60
- return formattedMessage || message || id;
61
- };