@ndla/ui 30.8.2 → 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 (76) 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/CopyParagraphButton/CopyParagraphButton.js +18 -24
  6. package/es/Frontpage/FrontpageSearch.js +3 -9
  7. package/es/MediaList/MediaList.js +1 -0
  8. package/es/Messages/MessageBox.js +10 -10
  9. package/es/NDLAFilm/AllMoviesAlphabetically.js +11 -11
  10. package/es/NDLAFilm/FilmSlideshow.js +7 -7
  11. package/es/NDLAFilm/MovieGrid.js +4 -4
  12. package/es/Programme/Programme.js +5 -5
  13. package/es/ResourceGroup/ResourceItem.js +12 -13
  14. package/es/ResourceGroup/ResourceList.js +3 -4
  15. package/es/Search/SearchField.js +2 -3
  16. package/es/Subject/SubjectNewContent.js +10 -10
  17. package/es/TreeStructure/AddFolderButton.js +9 -9
  18. package/es/TreeStructure/ComboboxButton.js +30 -5
  19. package/es/TreeStructure/TreeStructure.js +9 -7
  20. package/es/all.css +1 -1
  21. package/es/i18n/index.js +1 -2
  22. package/es/index.js +1 -1
  23. package/lib/Article/Article.js +3 -3
  24. package/lib/Article/ArticleSideBar.js +5 -5
  25. package/lib/AudioPlayer/Controls.js +26 -26
  26. package/lib/AudioPlayer/SpeechControl.js +1 -1
  27. package/lib/CopyParagraphButton/CopyParagraphButton.js +18 -24
  28. package/lib/Frontpage/FrontpageSearch.js +3 -9
  29. package/lib/MediaList/MediaList.js +1 -0
  30. package/lib/Messages/MessageBox.js +10 -10
  31. package/lib/NDLAFilm/AllMoviesAlphabetically.js +11 -11
  32. package/lib/NDLAFilm/FilmSlideshow.js +7 -7
  33. package/lib/NDLAFilm/MovieGrid.js +4 -4
  34. package/lib/Programme/Programme.js +5 -5
  35. package/lib/ResourceGroup/ResourceItem.js +11 -11
  36. package/lib/ResourceGroup/ResourceList.js +2 -2
  37. package/lib/Search/SearchField.js +2 -3
  38. package/lib/Subject/SubjectNewContent.js +10 -10
  39. package/lib/TreeStructure/AddFolderButton.d.ts +2 -1
  40. package/lib/TreeStructure/AddFolderButton.js +9 -9
  41. package/lib/TreeStructure/ComboboxButton.d.ts +1 -0
  42. package/lib/TreeStructure/ComboboxButton.js +29 -3
  43. package/lib/TreeStructure/TreeStructure.js +9 -7
  44. package/lib/all.css +1 -1
  45. package/lib/i18n/index.d.ts +0 -1
  46. package/lib/i18n/index.js +1 -8
  47. package/lib/index.d.ts +1 -1
  48. package/lib/index.js +0 -7
  49. package/package.json +17 -19
  50. package/src/Article/Article.tsx +1 -1
  51. package/src/Article/ArticleSideBar.tsx +1 -1
  52. package/src/AudioPlayer/Controls.tsx +1 -0
  53. package/src/AudioPlayer/SpeechControl.tsx +1 -0
  54. package/src/CopyParagraphButton/CopyParagraphButton.tsx +14 -10
  55. package/src/Frontpage/FrontpageSearch.tsx +1 -7
  56. package/src/MediaList/MediaList.tsx +1 -0
  57. package/src/Messages/MessageBox.tsx +1 -1
  58. package/src/NDLAFilm/AllMoviesAlphabetically.tsx +1 -1
  59. package/src/NDLAFilm/FilmSlideshow.tsx +1 -1
  60. package/src/NDLAFilm/MovieGrid.tsx +2 -1
  61. package/src/Programme/Programme.tsx +1 -2
  62. package/src/ResourceGroup/ResourceItem.tsx +1 -2
  63. package/src/ResourceGroup/ResourceList.tsx +1 -2
  64. package/src/Search/SearchField.tsx +1 -2
  65. package/src/Subject/SubjectNewContent.tsx +2 -2
  66. package/src/TopicMenu/component.topic-menu.scss +16 -13
  67. package/src/TreeStructure/AddFolderButton.tsx +16 -17
  68. package/src/TreeStructure/ComboboxButton.tsx +45 -20
  69. package/src/TreeStructure/TreeStructure.tsx +2 -0
  70. package/src/i18n/index.ts +0 -1
  71. package/src/index.ts +1 -1
  72. package/es/i18n/formatMessage.js +0 -45
  73. package/lib/i18n/formatMessage.d.ts +0 -14
  74. package/lib/i18n/formatMessage.js +0 -53
  75. package/src/i18n/__tests__/formatMessage-test.ts +0 -34
  76. package/src/i18n/formatMessage.ts +0 -61
@@ -7,4 +7,3 @@
7
7
  */
8
8
  export { i18nInstance } from './i18n';
9
9
  export { formatNestedMessages } from './formatNestedMessages';
10
- export { formatMessage } from './formatMessage';
package/lib/i18n/index.js CHANGED
@@ -3,12 +3,6 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- Object.defineProperty(exports, "formatMessage", {
7
- enumerable: true,
8
- get: function get() {
9
- return _formatMessage.formatMessage;
10
- }
11
- });
12
6
  Object.defineProperty(exports, "formatNestedMessages", {
13
7
  enumerable: true,
14
8
  get: function get() {
@@ -22,5 +16,4 @@ Object.defineProperty(exports, "i18nInstance", {
22
16
  }
23
17
  });
24
18
  var _i18n = require("./i18n");
25
- var _formatNestedMessages = require("./formatNestedMessages");
26
- var _formatMessage = require("./formatMessage");
19
+ var _formatNestedMessages = require("./formatNestedMessages");
package/lib/index.d.ts CHANGED
@@ -70,7 +70,7 @@ export { default as AuthorInfo } from './AuthorInfo';
70
70
  export { default as Breadcrumb, HeaderBreadcrumb, HomeBreadcrumb, ActionBreadcrumb } from './Breadcrumb';
71
71
  export type { SimpleBreadcrumbItem, IndexedBreadcrumbItem } from './Breadcrumb';
72
72
  export type { BreadcrumbItemProps } from './Breadcrumblist';
73
- export { i18nInstance, formatNestedMessages, formatMessage } from './i18n';
73
+ export { i18nInstance, formatNestedMessages } from './i18n';
74
74
  export { default as ResourceGroup } from './ResourceGroup';
75
75
  export { default as LayoutItem, OneColumn, PageContainer, Content } from './Layout';
76
76
  export { FilmSlideshow, MovieGrid, AboutNdlaFilm, FilmMovieSearch, FilmMovieList, AllMoviesAlphabetically, } from './NDLAFilm';
package/lib/index.js CHANGED
@@ -133,7 +133,6 @@ var _exportNames = {
133
133
  ActionBreadcrumb: true,
134
134
  i18nInstance: true,
135
135
  formatNestedMessages: true,
136
- formatMessage: true,
137
136
  ResourceGroup: true,
138
137
  LayoutItem: true,
139
138
  OneColumn: true,
@@ -1306,12 +1305,6 @@ Object.defineProperty(exports, "createUniversalPortal", {
1306
1305
  return _createUniversalPortal.createUniversalPortal;
1307
1306
  }
1308
1307
  });
1309
- Object.defineProperty(exports, "formatMessage", {
1310
- enumerable: true,
1311
- get: function get() {
1312
- return _i18n.formatMessage;
1313
- }
1314
- });
1315
1308
  Object.defineProperty(exports, "formatNestedMessages", {
1316
1309
  enumerable: true,
1317
1310
  get: function get() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
- "version": "30.8.2",
3
+ "version": "30.9.0",
4
4
  "description": "UI component library for NDLA.",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -31,29 +31,27 @@
31
31
  "types"
32
32
  ],
33
33
  "dependencies": {
34
- "@ndla/article-scripts": "^3.0.9",
35
- "@ndla/button": "^6.0.2",
36
- "@ndla/carousel": "^2.0.7",
37
- "@ndla/core": "^3.0.2",
38
- "@ndla/forms": "^4.0.14",
39
- "@ndla/hooks": "^1.1.13",
40
- "@ndla/icons": "^2.0.7",
41
- "@ndla/licenses": "^6.0.9",
42
- "@ndla/modal": "^2.1.2",
43
- "@ndla/notion": "^4.0.16",
44
- "@ndla/safelink": "^3.0.14",
45
- "@ndla/switch": "^1.0.2",
46
- "@ndla/tabs": "^2.0.9",
47
- "@ndla/tooltip": "^3.0.5",
34
+ "@ndla/article-scripts": "^3.0.11",
35
+ "@ndla/button": "^6.0.4",
36
+ "@ndla/carousel": "^2.0.8",
37
+ "@ndla/core": "^3.0.3",
38
+ "@ndla/forms": "^4.0.16",
39
+ "@ndla/hooks": "^1.1.15",
40
+ "@ndla/icons": "^2.0.8",
41
+ "@ndla/licenses": "^6.0.10",
42
+ "@ndla/modal": "^2.1.3",
43
+ "@ndla/notion": "^4.0.18",
44
+ "@ndla/safelink": "^3.0.16",
45
+ "@ndla/switch": "^1.0.3",
46
+ "@ndla/tabs": "^2.0.11",
47
+ "@ndla/tooltip": "^3.0.6",
48
48
  "@ndla/types-learningpath-api": "^0.0.17",
49
- "@ndla/util": "^3.1.7",
49
+ "@ndla/util": "^3.1.9",
50
50
  "@reach/menu-button": "^0.16.2",
51
51
  "@reach/slider": "^0.16.0",
52
52
  "focus-trap-react": "^8.9.2",
53
53
  "html-react-parser": "^0.14.1",
54
54
  "i18next-browser-languagedetector": "^6.1.1",
55
- "intl-format-cache": "^4.3.1",
56
- "intl-messageformat": "5.4.3",
57
55
  "invariant": "^2.2.3",
58
56
  "lodash": "^4.17.20",
59
57
  "react-bem-helper": "1.4.1",
@@ -86,5 +84,5 @@
86
84
  "publishConfig": {
87
85
  "access": "public"
88
86
  },
89
- "gitHead": "87a608977cc0558fcf41dcf7c9f6e6414d7c7c02"
87
+ "gitHead": "68b1327bb5950eee8a41227b3b48589c7d428bd7"
90
88
  }
@@ -65,7 +65,7 @@ export const ArticleTitle = ({ children, icon, label }: ArticleTitleProps) => {
65
65
  <div {...classes('title', modifiers)}>
66
66
  {icon}
67
67
  {labelView}
68
- <h1 tabIndex={0}>{children}</h1>
68
+ <h1 tabIndex={-1}>{children}</h1>
69
69
  </div>
70
70
  );
71
71
  };
@@ -74,7 +74,7 @@ const ArticleSideBar = ({
74
74
  //TOP
75
75
  if (resourcesRef.current.getBoundingClientRect().top <= 0) {
76
76
  setHide(true);
77
- } else if (resourcesRef.current.getBoundingClientRect().top <= 0) {
77
+ } else if (resourcesRef.current.getBoundingClientRect().top >= 0) {
78
78
  setHide(false);
79
79
  }
80
80
  };
@@ -411,6 +411,7 @@ const Controls = ({ src, title }: Props) => {
411
411
 
412
412
  return (
413
413
  <div>
414
+ {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
414
415
  <audio ref={audioRef} src={src} title={title} preload="metadata" />
415
416
  <ControlsWrapper>
416
417
  <PlayButton type="button" onClick={togglePlay} title="play" aria-label="play">
@@ -52,6 +52,7 @@ const SpeechControl = ({ src, title }: Props) => {
52
52
  };
53
53
  return (
54
54
  <div>
55
+ {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
55
56
  <audio ref={audioRef} src={src} title={title} preload="metadata" />
56
57
  <SpeechPlayButton type="button" onClick={togglePlay}>
57
58
  <VolumeUp role="img" aria-label="play" title="play" />
@@ -13,6 +13,11 @@ import { Link } from '@ndla/icons/common';
13
13
  import { useTranslation } from 'react-i18next';
14
14
  import Tooltip from '@ndla/tooltip';
15
15
  import { copyTextToClipboard } from '@ndla/util';
16
+ import { colors } from '@ndla/core';
17
+
18
+ const ContainerDiv = styled.div`
19
+ position: relative;
20
+ `;
16
21
 
17
22
  const IconButton = styled.button`
18
23
  position: absolute;
@@ -23,18 +28,17 @@ const IconButton = styled.button`
23
28
  z-index: 1;
24
29
  transition: 0.2s;
25
30
  opacity: 0;
31
+ color: ${colors.brand.grey};
26
32
 
27
33
  & svg {
28
34
  width: 30px;
29
35
  height: 30px;
30
36
  }
31
- `;
32
37
 
33
- const ContainerDiv = styled.div`
34
- position: relative;
35
- &:hover button {
38
+ ${ContainerDiv}:hover &,
39
+ &:focus, &:focus-visible, &:active {
36
40
  cursor: pointer;
37
- opacity: 0.5;
41
+ opacity: 1;
38
42
  }
39
43
  `;
40
44
 
@@ -54,12 +58,12 @@ interface CopyButtonProps {
54
58
  const CopyButton = ({ onClick, title, tooltip, content }: CopyButtonProps) => {
55
59
  return (
56
60
  <div>
57
- <IconButton onClick={onClick} data-title={title}>
58
- <Tooltip tooltip={tooltip}>
61
+ <Tooltip tooltip={tooltip}>
62
+ <IconButton onClick={onClick} data-title={title} aria-label={`${tooltip}: ${title}`}>
59
63
  <Link title={''} />
60
- </Tooltip>
61
- </IconButton>
62
- <h2 id={title} tabIndex={0} dangerouslySetInnerHTML={{ __html: content || '' }} />
64
+ </IconButton>
65
+ </Tooltip>
66
+ <h2 id={title} tabIndex={-1} dangerouslySetInnerHTML={{ __html: content || '' }} />
63
67
  </div>
64
68
  );
65
69
  };
@@ -141,17 +141,11 @@ const FrontpageSearch = ({
141
141
  return () => {
142
142
  noScroll(false, 'preventPageScroll');
143
143
  window.removeEventListener('scroll', resetScroll);
144
+ inputHasFocusRef.current = false;
144
145
  };
145
146
  }, [inputHasFocus]);
146
147
 
147
148
  const onBlur = () => {
148
- setTimeout(() => {
149
- if (searchFieldRef.current) {
150
- if (!searchFieldRef.current.contains(document.activeElement)) {
151
- onInputBlur();
152
- }
153
- }
154
- }, 0);
155
149
  // This is needed when user tabs out of field
156
150
  if (!searchFieldValue) {
157
151
  onInputBlur();
@@ -147,6 +147,7 @@ export const MediaListItemMeta = ({ items = [] }: MediaListItemMetaProps) => {
147
147
 
148
148
  return (
149
149
  //@ts-ignore
150
+ // eslint-disable-next-line react/no-unknown-property
150
151
  <ul {...cClasses('actions')} property="cc:attributionName" content={attributionMeta}>
151
152
  {items.map((item) => (
152
153
  <li key={uuid()} className="c-medialist__meta-item">
@@ -138,7 +138,7 @@ export const MessageBox = ({ type, children = '', links, showCloseButton, onClos
138
138
  {links && (
139
139
  <LinkWrapper>
140
140
  {links.map((x) => (
141
- <Link href={x.href}>
141
+ <Link href={x.href} key={x.href}>
142
142
  <span>{x.text}</span>
143
143
  <Forward />
144
144
  </Link>
@@ -170,7 +170,7 @@ const hasForEachPolyfill = () => {
170
170
  if ('NodeList' in window && !NodeList.prototype.forEach) {
171
171
  NodeList.prototype.forEach = function (callback, thisArg) {
172
172
  thisArg = thisArg || window;
173
- for (var i = 0; i < this.length; i++) {
173
+ for (let i = 0; i < this.length; i++) {
174
174
  callback.call(thisArg, this[i], i, this);
175
175
  }
176
176
  };
@@ -217,7 +217,7 @@ const FilmSlideshow = ({ autoSlide = false, slideshow = [], slideInterval = 5000
217
217
  const [animationComplete, setAnimationComplete] = useState(true);
218
218
  const slideRef = useRef<HTMLDivElement>(null);
219
219
  const slideText = useRef<HTMLDivElement>(null);
220
- let timer = useRef<ReturnType<typeof setTimeout> | null>(null);
220
+ const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
221
221
 
222
222
  const gotoSlide = useCallback((indexTarget: number, useAnimation = false) => {
223
223
  setSwipeDistance(0);
@@ -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>
@@ -264,8 +264,7 @@ $navigation-row-width: 980px / 3;
264
264
 
265
265
  @include mq(desktop) {
266
266
  padding: $spacing--large;
267
- margin: -($spacing + $spacing--small) $spacing + $spacing--small 0 $spacing +
268
- $spacing--small;
267
+ margin: -($spacing + $spacing--small) $spacing + $spacing--small 0 $spacing + $spacing--small;
269
268
  }
270
269
 
271
270
  & > * {
@@ -362,7 +361,8 @@ $navigation-row-width: 980px / 3;
362
361
  @include inuit-font-size(26px);
363
362
  }
364
363
 
365
- a, button {
364
+ a,
365
+ button {
366
366
  padding: 0;
367
367
  padding-right: $spacing--small;
368
368
  border: 0;
@@ -374,7 +374,8 @@ $navigation-row-width: 980px / 3;
374
374
  svg {
375
375
  transition: transform 100ms ease;
376
376
  }
377
- &:hover, &:focus {
377
+ &:hover,
378
+ &:focus {
378
379
  svg {
379
380
  transform: translateX($spacing--small / 4);
380
381
  }
@@ -386,7 +387,8 @@ $navigation-row-width: 980px / 3;
386
387
 
387
388
  @include mq($until: tabletWide) {
388
389
  width: 100%;
389
- a, button {
390
+ a,
391
+ button {
390
392
  background: $brand-color--lighter;
391
393
  box-shadow: none;
392
394
  width: 100%;
@@ -402,13 +404,14 @@ $navigation-row-width: 980px / 3;
402
404
  }
403
405
  }
404
406
  @include mq($until: mobileWide) {
405
- a, button {
407
+ a,
408
+ button {
406
409
  padding: 0 $left-margin-narrow-screen;
407
410
  }
408
411
  }
409
412
  }
410
413
  &__menu-filter {
411
- display:flex;
414
+ display: flex;
412
415
  flex-wrap: wrap;
413
416
  margin-top: -$spacing--small;
414
417
  button {
@@ -453,7 +456,7 @@ $navigation-row-width: 980px / 3;
453
456
  .c-topic-menu__back-button {
454
457
  display: flex;
455
458
  align-items: center;
456
- cursor:pointer;
459
+ cursor: pointer;
457
460
  border: 0;
458
461
  padding: 0;
459
462
  color: $brand-color;
@@ -484,7 +487,7 @@ $navigation-row-width: 980px / 3;
484
487
  .c-topic-menu__back-button-slides {
485
488
  display: flex;
486
489
  align-items: center;
487
- cursor:pointer;
490
+ cursor: pointer;
488
491
  border: 0;
489
492
  padding: 0;
490
493
  color: $brand-color;
@@ -734,7 +737,6 @@ $navigation-row-width: 980px / 3;
734
737
  &:active,
735
738
  &:focus {
736
739
  background-color: $brand-color--light;
737
-
738
740
  }
739
741
  }
740
742
 
@@ -745,13 +747,13 @@ $navigation-row-width: 980px / 3;
745
747
  height: auto;
746
748
  min-height: $link-height;
747
749
 
748
- .c-topic-menu__link-wrapper{
750
+ .c-topic-menu__link-wrapper {
749
751
  border-bottom: 1px solid $brand-color--dark;
750
752
  padding-bottom: $spacing--small;
751
753
  display: flex;
752
754
  flex-wrap: wrap;
753
755
  @include mq($until: tabletWide) {
754
- border:0;
756
+ border: 0;
755
757
  padding-bottom: 0;
756
758
  }
757
759
  }
@@ -871,6 +873,7 @@ $navigation-row-width: 980px / 3;
871
873
 
872
874
  &-right {
873
875
  justify-content: flex-end;
876
+ margin-right: 16px;
874
877
  }
875
878
  }
876
879
  .c-topic-menu__search {
@@ -940,7 +943,7 @@ $navigation-row-width: 980px / 3;
940
943
  padding-right: $spacing + $spacing--small;
941
944
  }
942
945
  & > nav:first-child {
943
- margin-top:0;
946
+ margin-top: 0;
944
947
  }
945
948
  }
946
949
 
@@ -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';