@ndla/ui 20.0.2 → 22.0.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 (88) hide show
  1. package/es/LearningPaths/LearningPathInformation.js +21 -3
  2. package/es/LearningPaths/LearningPathMenu.js +8 -5
  3. package/es/LearningPaths/LearningPathMenuAsideCopyright.js +17 -4
  4. package/es/LearningPaths/LearningPathMenuIntro.js +19 -8
  5. package/es/MyNdla/Resource/Folder.js +7 -6
  6. package/es/Resource/BlockResource.js +6 -5
  7. package/es/Resource/ListResource.js +7 -6
  8. package/es/ResourceGroup/ResourceGroup.js +3 -3
  9. package/es/ResourceGroup/ResourceItem.js +12 -12
  10. package/es/ResourceGroup/ResourceList.js +2 -2
  11. package/es/TopicMenu/TopicMenuButton.js +4 -2
  12. package/es/TreeStructure/FolderItem.js +72 -51
  13. package/es/TreeStructure/FolderItems.js +33 -61
  14. package/es/TreeStructure/FolderNameInput.js +14 -13
  15. package/es/TreeStructure/TreeStructure.js +80 -96
  16. package/es/TreeStructure/helperFunctions.js +4 -73
  17. package/es/TreeStructure/{TreeStructure.types.js → types.js} +0 -0
  18. package/es/locale/messages-en.js +6 -1
  19. package/es/locale/messages-nb.js +6 -1
  20. package/es/locale/messages-nn.js +6 -1
  21. package/es/locale/messages-se.js +6 -1
  22. package/es/locale/messages-sma.js +6 -1
  23. package/lib/LearningPaths/LearningPathInformation.js +19 -2
  24. package/lib/LearningPaths/LearningPathMenu.d.ts +2 -1
  25. package/lib/LearningPaths/LearningPathMenu.js +8 -5
  26. package/lib/LearningPaths/LearningPathMenuAsideCopyright.js +16 -3
  27. package/lib/LearningPaths/LearningPathMenuIntro.d.ts +3 -1
  28. package/lib/LearningPaths/LearningPathMenuIntro.js +19 -8
  29. package/lib/MyNdla/Resource/Folder.js +7 -6
  30. package/lib/Resource/BlockResource.js +6 -5
  31. package/lib/Resource/ListResource.js +7 -6
  32. package/lib/ResourceGroup/ResourceGroup.d.ts +1 -1
  33. package/lib/ResourceGroup/ResourceGroup.js +3 -3
  34. package/lib/ResourceGroup/ResourceItem.d.ts +1 -1
  35. package/lib/ResourceGroup/ResourceItem.js +12 -12
  36. package/lib/ResourceGroup/ResourceList.d.ts +1 -1
  37. package/lib/ResourceGroup/ResourceList.js +2 -2
  38. package/lib/TopicMenu/TopicMenuButton.js +3 -1
  39. package/lib/TreeStructure/FolderItem.d.ts +6 -20
  40. package/lib/TreeStructure/FolderItem.js +74 -51
  41. package/lib/TreeStructure/FolderItems.d.ts +11 -2
  42. package/lib/TreeStructure/FolderItems.js +33 -61
  43. package/lib/TreeStructure/FolderNameInput.js +14 -13
  44. package/lib/TreeStructure/TreeStructure.d.ts +12 -2
  45. package/lib/TreeStructure/TreeStructure.js +78 -94
  46. package/lib/TreeStructure/helperFunctions.d.ts +2 -4
  47. package/lib/TreeStructure/helperFunctions.js +5 -80
  48. package/lib/TreeStructure/index.d.ts +2 -1
  49. package/lib/TreeStructure/types.d.ts +32 -0
  50. package/lib/TreeStructure/{TreeStructure.types.js → types.js} +0 -0
  51. package/lib/index.d.ts +1 -1
  52. package/lib/locale/messages-en.d.ts +6 -1
  53. package/lib/locale/messages-en.js +6 -1
  54. package/lib/locale/messages-nb.d.ts +6 -1
  55. package/lib/locale/messages-nb.js +6 -1
  56. package/lib/locale/messages-nn.d.ts +6 -1
  57. package/lib/locale/messages-nn.js +6 -1
  58. package/lib/locale/messages-se.d.ts +6 -1
  59. package/lib/locale/messages-se.js +6 -1
  60. package/lib/locale/messages-sma.d.ts +6 -1
  61. package/lib/locale/messages-sma.js +6 -1
  62. package/package.json +15 -14
  63. package/src/LearningPaths/LearningPathInformation.tsx +27 -12
  64. package/src/LearningPaths/LearningPathMenu.tsx +9 -1
  65. package/src/LearningPaths/LearningPathMenuAsideCopyright.tsx +22 -20
  66. package/src/LearningPaths/LearningPathMenuIntro.tsx +15 -2
  67. package/src/MyNdla/Resource/Folder.tsx +1 -1
  68. package/src/Resource/BlockResource.tsx +1 -1
  69. package/src/Resource/ListResource.tsx +1 -1
  70. package/src/ResourceGroup/ResourceGroup.tsx +1 -1
  71. package/src/ResourceGroup/ResourceItem.tsx +2 -2
  72. package/src/ResourceGroup/ResourceList.tsx +1 -1
  73. package/src/TopicMenu/TopicMenuButton.jsx +5 -1
  74. package/src/TreeStructure/FolderItem.tsx +59 -67
  75. package/src/TreeStructure/FolderItems.tsx +30 -50
  76. package/src/TreeStructure/FolderNameInput.tsx +6 -11
  77. package/src/TreeStructure/TreeStructure.tsx +73 -71
  78. package/src/TreeStructure/helperFunctions.ts +3 -37
  79. package/src/TreeStructure/index.ts +2 -1
  80. package/src/TreeStructure/types.ts +37 -0
  81. package/src/index.ts +1 -1
  82. package/src/locale/messages-en.ts +6 -1
  83. package/src/locale/messages-nb.ts +7 -1
  84. package/src/locale/messages-nn.ts +6 -1
  85. package/src/locale/messages-se.ts +7 -1
  86. package/src/locale/messages-sma.ts +7 -1
  87. package/lib/TreeStructure/TreeStructure.types.d.ts +0 -61
  88. package/src/TreeStructure/TreeStructure.types.ts +0 -71
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
- "version": "20.0.2",
3
+ "version": "22.0.0",
4
4
  "description": "UI component library for NDLA.",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -31,19 +31,20 @@
31
31
  "types"
32
32
  ],
33
33
  "dependencies": {
34
- "@ndla/button": "^3.1.5",
35
- "@ndla/carousel": "^1.2.14",
36
- "@ndla/core": "^2.3.2",
37
- "@ndla/forms": "^3.1.2",
34
+ "@ndla/button": "^3.2.0",
35
+ "@ndla/carousel": "^1.2.15",
36
+ "@ndla/core": "^2.3.3",
37
+ "@ndla/forms": "^3.1.3",
38
38
  "@ndla/hooks": "^1.1.4",
39
- "@ndla/icons": "^1.11.2",
40
- "@ndla/licenses": "^5.0.5",
41
- "@ndla/modal": "^1.2.15",
42
- "@ndla/notion": "^3.1.25",
43
- "@ndla/safelink": "^2.2.3",
44
- "@ndla/switch": "^0.1.9",
45
- "@ndla/tabs": "^1.1.13",
46
- "@ndla/tooltip": "^2.1.4",
39
+ "@ndla/icons": "^1.11.3",
40
+ "@ndla/licenses": "^5.0.6",
41
+ "@ndla/modal": "^1.2.16",
42
+ "@ndla/notion": "^3.1.26",
43
+ "@ndla/safelink": "^2.2.4",
44
+ "@ndla/switch": "^0.1.10",
45
+ "@ndla/tabs": "^1.1.14",
46
+ "@ndla/tooltip": "^2.1.5",
47
+ "@ndla/types-learningpath-api": "^0.0.12",
47
48
  "@ndla/util": "^3.0.0",
48
49
  "@reach/menu-button": "^0.16.2",
49
50
  "@reach/slider": "^0.16.0",
@@ -82,5 +83,5 @@
82
83
  "publishConfig": {
83
84
  "access": "public"
84
85
  },
85
- "gitHead": "8181e40829a9cbd8f2aaa1ccfb4dfdafa8560a5c"
86
+ "gitHead": "683202fb5ba5a555e6a3d61c89e5cce4ae3b151c"
86
87
  }
@@ -8,7 +8,8 @@
8
8
 
9
9
  import React from 'react';
10
10
  import styled from '@emotion/styled';
11
- import { fonts, spacing, spacingUnit, breakpoints, mq } from '@ndla/core';
11
+ import { fonts, spacing, spacingUnit, breakpoints, mq, colors } from '@ndla/core';
12
+ import { getLicenseByAbbreviation, LicenseByline } from '@ndla/licenses';
12
13
 
13
14
  type StyledWrapperProps = {
14
15
  invertedStyle?: boolean;
@@ -50,6 +51,17 @@ const StyledWrapper = styled.div<StyledWrapperProps>`
50
51
  }
51
52
  `;
52
53
 
54
+ const LicenseWrapper = styled.div`
55
+ ul {
56
+ margin-left: 0;
57
+ margin-bottom: ${spacing.small};
58
+ }
59
+ `;
60
+
61
+ const StyledHeader = styled.h1`
62
+ margin-bottom: ${spacing.small};
63
+ `;
64
+
53
65
  interface Props {
54
66
  description?: string;
55
67
  title: string;
@@ -59,14 +71,17 @@ interface Props {
59
71
  };
60
72
  }
61
73
 
62
- export const LearningPathInformation = ({ description, title, license, invertedStyle }: Props) => (
63
- <section className="o-wrapper">
64
- <StyledWrapper invertedStyle={invertedStyle} className="c-article">
65
- <div>
66
- <h1>{title}</h1>
67
- {license && license.license}
68
- </div>
69
- {description && <div dangerouslySetInnerHTML={{ __html: description }} />}
70
- </StyledWrapper>
71
- </section>
72
- );
74
+ export const LearningPathInformation = ({ description, title, license, invertedStyle }: Props) => {
75
+ const { rights } = getLicenseByAbbreviation(license?.license || '', 'nb');
76
+ return (
77
+ <section className="o-wrapper">
78
+ <StyledWrapper invertedStyle={invertedStyle} className="c-article">
79
+ <LicenseWrapper>
80
+ <StyledHeader>{title}</StyledHeader>
81
+ <LicenseByline licenseRights={rights} color={colors.brand.tertiary} />
82
+ </LicenseWrapper>
83
+ {description && <div dangerouslySetInnerHTML={{ __html: description }} />}
84
+ </StyledWrapper>
85
+ </section>
86
+ );
87
+ };
@@ -76,6 +76,7 @@ export type StepProps = {
76
76
  };
77
77
 
78
78
  interface Props {
79
+ onToggleAddToFavorites?: () => void;
79
80
  learningsteps: StepProps[];
80
81
  name: string;
81
82
  lastUpdated: string;
@@ -109,6 +110,7 @@ const LearningPathMenu = ({
109
110
  learningPathURL,
110
111
  invertedStyle,
111
112
  cookies,
113
+ onToggleAddToFavorites,
112
114
  }: Props) => {
113
115
  const { t } = useTranslation();
114
116
  const [isOpen, toggleOpenState] = useState(false);
@@ -129,7 +131,13 @@ const LearningPathMenu = ({
129
131
  </StyledToggleMenubutton>
130
132
  </Tooltip>
131
133
  </div>
132
- <LearningPathMenuIntro isOpen={isOpen} name={name} invertedStyle={invertedStyle} />
134
+ <LearningPathMenuIntro
135
+ isOpen={isOpen}
136
+ name={name}
137
+ invertedStyle={invertedStyle}
138
+ id={learningPathId}
139
+ onToggleAddToFavorites={onToggleAddToFavorites}
140
+ />
133
141
  <LearningPathMenuContent
134
142
  learningsteps={learningsteps}
135
143
  learningPathId={learningPathId}
@@ -9,22 +9,20 @@
9
9
  import React from 'react';
10
10
  import styled from '@emotion/styled';
11
11
  import { User } from '@ndla/icons/common';
12
- import { spacing, fonts } from '@ndla/core';
12
+ import { spacing, fonts, colors } from '@ndla/core';
13
+ import { getLicenseByAbbreviation, LicenseByline } from '@ndla/licenses';
13
14
 
14
15
  const StyledLearningPathDetails = styled.div`
15
16
  ${fonts.sizes(14, 1.1)};
16
17
  font-weight: ${fonts.weight.normal};
17
- margin: 0;
18
+ `;
19
+
20
+ const UserLine = styled.div`
18
21
  display: flex;
19
- align-items: flex-start;
20
- justify-items: flex-start;
21
22
  margin-bottom: ${spacing.xsmall};
22
- p {
23
- margin: 0;
24
- padding-left: ${spacing.xsmall};
25
- }
23
+
26
24
  span {
27
- display: block;
25
+ margin-left: ${spacing.xsmall};
28
26
  }
29
27
  `;
30
28
 
@@ -40,16 +38,20 @@ type Props = {
40
38
  };
41
39
  };
42
40
 
43
- const LearningPathMenuAsideCopyright = ({ copyright }: Props) => (
44
- <StyledLearningPathDetails>
45
- <User />
46
- <p>
47
- {copyright.contributors.map((contributor) => (
48
- <span key={contributor.name}>{contributor.name}</span>
49
- ))}
50
- <span>{copyright.license.license}</span>
51
- </p>
52
- </StyledLearningPathDetails>
53
- );
41
+ const LearningPathMenuAsideCopyright = ({ copyright }: Props) => {
42
+ const { rights } = getLicenseByAbbreviation(copyright.license.license || '', 'nb');
43
+ return (
44
+ <StyledLearningPathDetails>
45
+ <UserLine>
46
+ <User />
47
+ {copyright.contributors.map((contributor) => (
48
+ <span key={contributor.name}>{contributor.name}</span>
49
+ ))}
50
+ </UserLine>
51
+
52
+ <LicenseByline licenseRights={rights} color={colors.brand.tertiary} />
53
+ </StyledLearningPathDetails>
54
+ );
55
+ };
54
56
 
55
57
  export default LearningPathMenuAsideCopyright;
@@ -11,6 +11,7 @@ import styled from '@emotion/styled';
11
11
  import { css } from '@emotion/core';
12
12
  import { useTranslation } from 'react-i18next';
13
13
  import { colors, spacing, spacingUnit, fonts, typography, mq, breakpoints, animations } from '@ndla/core';
14
+ import { ArticleFavoritesButton } from '../Article';
14
15
 
15
16
  const StyledInfoHeader = styled.p`
16
17
  ${typography.smallHeading}
@@ -94,18 +95,30 @@ const StyledIntroHeader = styled.h1`
94
95
  margin: ${spacing.small} 0 ${spacing.normal};
95
96
  `;
96
97
 
98
+ const StyledRow = styled.div`
99
+ display: flex;
100
+ gap: ${spacing.small};
101
+ `;
102
+
97
103
  interface Props {
98
104
  isOpen: boolean;
99
105
  invertedStyle?: boolean;
100
106
  name: string;
107
+ onToggleAddToFavorites?: () => void;
108
+ id: number;
101
109
  }
102
110
 
103
- const LearningPathMenuIntro = ({ isOpen, name, invertedStyle }: Props) => {
111
+ const LearningPathMenuIntro = ({ isOpen, name, invertedStyle, onToggleAddToFavorites, id }: Props) => {
104
112
  const { t } = useTranslation();
105
113
  return (
106
114
  <StyledMenuIntro isOpen={isOpen} invertedStyle={invertedStyle}>
107
115
  <div>
108
- <StyledInfoHeader>{t('learningPath.youAreInALearningPath')}</StyledInfoHeader>
116
+ <StyledRow>
117
+ <StyledInfoHeader>{t('learningPath.youAreInALearningPath')}</StyledInfoHeader>
118
+ {onToggleAddToFavorites && (
119
+ <ArticleFavoritesButton onToggleAddToFavorites={onToggleAddToFavorites} articleId={id.toString()} />
120
+ )}
121
+ </StyledRow>
109
122
  <StyledIntroHeader>{name}</StyledIntroHeader>
110
123
  </div>
111
124
  </StyledMenuIntro>
@@ -141,7 +141,7 @@ const Folder = ({ link, title, subFolders, subResources, type = 'list', menuItem
141
141
  <FolderTitle>{title}</FolderTitle>
142
142
  <IconCount layoutType={type} type={'folder'} count={subFolders} />
143
143
  <IconCount layoutType={type} type={'resource'} count={subResources} />
144
- {menuItems && menuItems.length > 0 && <MenuButton size="small" menuItems={menuItems} />}
144
+ {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
145
145
  </FolderWrapper>
146
146
  );
147
147
  };
@@ -92,7 +92,7 @@ const BlockResource = ({ link, title, tags, resourceImage, topics, description,
92
92
  <BlockDescription>{description}</BlockDescription>
93
93
  <RightRow>
94
94
  {tags && <CompressedTagList tags={tags} />}
95
- {menuItems && menuItems.length > 0 && <MenuButton size="small" menuItems={menuItems} />}
95
+ {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
96
96
  </RightRow>
97
97
  </BlockInfoWrapper>
98
98
  </BlockElementWrapper>
@@ -131,7 +131,7 @@ const ListResource = ({ link, title, tags, resourceImage, topics, description, m
131
131
  {showDescription && <ResourceDescription>{description}</ResourceDescription>}
132
132
  <TagsandActionMenu>
133
133
  {tags && <CompressedTagList tags={tags} />}
134
- {menuItems && menuItems.length > 0 && <MenuButton size="small" menuItems={menuItems} />}
134
+ {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
135
135
  </TagsandActionMenu>
136
136
  </ResourceWrapper>
137
137
  );
@@ -36,7 +36,7 @@ const StyledHeading = styled.h1`
36
36
  type Props = {
37
37
  invertedStyle?: boolean;
38
38
  toggleAdditionalResources: () => void;
39
- onToggleAddToFavorites: (id: string | number, add: string) => void;
39
+ onToggleAddToFavorites: (id: string) => void;
40
40
  showAddToFavoriteButton: boolean;
41
41
  };
42
42
 
@@ -229,7 +229,7 @@ type Props = {
229
229
  showAdditionalResources?: boolean;
230
230
  access?: 'teacher';
231
231
  isFavorite?: boolean;
232
- onToggleAddToFavorites: (id: string, add: boolean) => void;
232
+ onToggleAddToFavorites: (id: string) => void;
233
233
  showAddToFavoriteButton: boolean;
234
234
  };
235
235
 
@@ -305,7 +305,7 @@ const ResourceItem = ({
305
305
  <ArticleFavoritesButton
306
306
  isFavorite={isFavorite}
307
307
  articleId={id}
308
- onToggleAddToFavorites={() => onToggleAddToFavorites(id, true)}
308
+ onToggleAddToFavorites={() => onToggleAddToFavorites(id)}
309
309
  />
310
310
  )}
311
311
  </TypeWrapper>
@@ -48,7 +48,7 @@ export type ResourceListProps = {
48
48
  contentType?: string;
49
49
  title?: string;
50
50
  showAdditionalResources?: boolean;
51
- onToggleAddToFavorites: (id: string, add: boolean) => void;
51
+ onToggleAddToFavorites: (id: string) => void;
52
52
  showAddToFavoriteButton: boolean;
53
53
  };
54
54
 
@@ -9,7 +9,7 @@
9
9
  import React from 'react';
10
10
  import PropTypes from 'prop-types';
11
11
  import { css } from '@emotion/core';
12
- import { spacing, fonts, colors } from '@ndla/core';
12
+ import { spacing, fonts, colors, mq, breakpoints } from '@ndla/core';
13
13
  import { Menu } from '@ndla/icons/common';
14
14
  import Button from '@ndla/button';
15
15
 
@@ -38,6 +38,10 @@ const style = css`
38
38
  background: ${colors.white};
39
39
  color: ${colors.brand.primary};
40
40
  }
41
+ ${mq.range({ until: breakpoints.tablet })} {
42
+ padding-left: ${spacing.xsmall};
43
+ padding-right: ${spacing.xsmall};
44
+ }
41
45
  `;
42
46
 
43
47
  const TopicMenuButton = ({ ndlaFilm, children, ...rest }) => (
@@ -6,13 +6,14 @@
6
6
  *
7
7
  */
8
8
 
9
- import React, { KeyboardEvent, useEffect, useRef } from 'react';
9
+ import React, { KeyboardEvent, MouseEvent, useEffect, useRef } from 'react';
10
10
  import styled from '@emotion/styled';
11
11
  import { ArrowDropDown } from '@ndla/icons/common';
12
+ import { MenuButton } from '@ndla/button';
12
13
  import { FolderOutlined } from '@ndla/icons/contentType';
13
14
  import { colors, spacing, misc, animations } from '@ndla/core';
14
15
  import SafeLink from '@ndla/safelink';
15
- import { SetFocusedFolderId, FolderChildFuncType } from './TreeStructure.types';
16
+ import { CommonFolderItemsProps, FolderType } from './types';
16
17
  import { arrowNavigation } from './arrowNavigation';
17
18
 
18
19
  const OpenButton = styled.button<{ isOpen: boolean }>`
@@ -39,10 +40,10 @@ const FolderItemWrapper = styled.div`
39
40
  align-items: center;
40
41
  `;
41
42
 
42
- const WrapperForFolderChild = styled.div<{ marked: boolean }>`
43
+ const WrapperForFolderChild = styled.div<{ selected?: boolean }>`
43
44
  position: absolute;
44
45
  right: ${spacing.xsmall};
45
- opacity: ${({ marked }) => (marked ? 1 : 0.25)};
46
+ opacity: ${({ selected }) => (selected ? 1 : 0.25)};
46
47
  &:hover,
47
48
  &:focus,
48
49
  &:focus-within {
@@ -50,16 +51,20 @@ const WrapperForFolderChild = styled.div<{ marked: boolean }>`
50
51
  }
51
52
  `;
52
53
 
53
- const FolderName = styled('button', { shouldForwardProp: (name) => !['marked', 'noArrow'].includes(name) })<{
54
- marked: boolean;
54
+ const shouldForwardProp = (name: string) => !['selected', 'noArrow'].includes(name);
55
+
56
+ interface FolderNameProps {
57
+ selected?: boolean;
55
58
  noArrow?: boolean;
56
- }>`
59
+ }
60
+
61
+ const FolderName = styled('button', { shouldForwardProp })<FolderNameProps>`
57
62
  line-height: 1;
58
- background: ${({ marked }) => (marked ? colors.brand.lighter : 'transparent')};
63
+ background: ${({ selected }) => (selected ? colors.brand.lighter : 'transparent')};
59
64
  color: ${colors.text.primary};
60
65
  &:hover,
61
66
  &:focus {
62
- background: ${({ marked }) => (marked ? colors.brand.light : colors.brand.lightest)};
67
+ background: ${({ selected }) => (selected ? colors.brand.light : colors.brand.lightest)};
63
68
  color: ${colors.brand.primary};
64
69
  + ${WrapperForFolderChild} {
65
70
  opacity: 1;
@@ -82,52 +87,40 @@ const FolderName = styled('button', { shouldForwardProp: (name) => !['marked', '
82
87
 
83
88
  const FolderNameLink = FolderName.withComponent(SafeLink);
84
89
 
85
- interface Props {
86
- name: string;
87
- id: string;
88
- level: number;
89
- onCloseFolder: (id: string) => void;
90
- onOpenFolder: (id: string) => void;
91
- onMarkFolder: (id: string) => void;
92
- onSelectFolder?: (id: string) => void;
93
- isOpen: boolean;
94
- markedFolderId?: string;
95
- focusedFolderId?: string;
96
- visibleFolders: string[];
97
- loading?: boolean;
98
- openOnFolderClick?: boolean;
90
+ interface Props extends CommonFolderItemsProps {
99
91
  hideArrow?: boolean;
100
- setFocusedFolderId: SetFocusedFolderId;
101
- icon?: React.ReactNode;
92
+ isOpen: boolean;
93
+ folder: FolderType;
102
94
  noPaddingWhenArrowIsHidden?: boolean;
103
- folderChild?: FolderChildFuncType;
104
95
  }
105
96
 
106
97
  const FolderItem = ({
98
+ focusedFolderId,
99
+ menuItems,
107
100
  hideArrow,
108
- loading,
101
+ folder,
102
+ isOpen,
109
103
  level,
110
- name,
111
- id,
112
- visibleFolders,
104
+ loading,
105
+ selectedFolder,
106
+ noPaddingWhenArrowIsHidden,
113
107
  onCloseFolder,
114
108
  onOpenFolder,
115
- onMarkFolder,
116
109
  onSelectFolder,
117
- isOpen,
118
- markedFolderId,
119
- focusedFolderId,
120
110
  openOnFolderClick,
121
- setFocusedFolderId,
122
- icon,
123
- noPaddingWhenArrowIsHidden,
124
- folderChild,
111
+ setFocusedId,
112
+ setSelectedFolder,
113
+ visibleFolders,
125
114
  }: Props) => {
115
+ const { id, icon, name } = folder;
126
116
  const ref = useRef<HTMLButtonElement & HTMLAnchorElement>(null);
127
- const marked = markedFolderId === id;
117
+ const selected = selectedFolder && selectedFolder.id === id;
118
+ const focused = focusedFolderId === id;
128
119
 
129
- const handleMarkFolder = () => {
130
- onMarkFolder(id);
120
+ const handleClickFolder = () => {
121
+ setSelectedFolder(folder);
122
+ setFocusedId(id);
123
+ onSelectFolder?.(id);
131
124
  if (openOnFolderClick) {
132
125
  if (isOpen) {
133
126
  onCloseFolder(id);
@@ -139,12 +132,20 @@ const FolderItem = ({
139
132
 
140
133
  useEffect(() => {
141
134
  if (focusedFolderId === id) {
142
- if (ref.current) {
143
- ref.current.focus();
144
- }
135
+ ref.current?.focus();
145
136
  }
146
137
  }, [focusedFolderId, ref, id]);
147
138
 
139
+ const actions = menuItems?.map((item) => {
140
+ const { onClick } = item;
141
+ return {
142
+ ...item,
143
+ onClick: (e?: MouseEvent<HTMLDivElement>) => onClick(e, folder),
144
+ };
145
+ });
146
+
147
+ const linkPath = `/minndla${level > 1 ? '/folders' : ''}/${id}`;
148
+
148
149
  return (
149
150
  <FolderItemWrapper>
150
151
  {!hideArrow && (
@@ -160,24 +161,19 @@ const FolderItem = ({
160
161
  <>
161
162
  <FolderName
162
163
  ref={ref}
163
- onKeyDown={(e) => arrowNavigation(e, id, visibleFolders, setFocusedFolderId, onOpenFolder, onCloseFolder)}
164
+ onKeyDown={(e) => arrowNavigation(e, id, visibleFolders, setFocusedId, onOpenFolder, onCloseFolder)}
164
165
  noArrow={hideArrow && !noPaddingWhenArrowIsHidden}
165
- tabIndex={marked ? 0 : -1}
166
- marked={marked}
166
+ tabIndex={selected || focused ? 0 : -1}
167
+ selected={selected}
167
168
  disabled={loading}
168
- onFocus={() => {
169
- setFocusedFolderId(id);
170
- }}
171
- onClick={() => {
172
- handleMarkFolder();
173
- onSelectFolder(id);
174
- }}>
169
+ onFocus={() => setFocusedId(id)}
170
+ onClick={handleClickFolder}>
175
171
  {icon || <FolderOutlined />}
176
172
  {name}
177
173
  </FolderName>
178
- {folderChild && (
179
- <WrapperForFolderChild marked={marked}>
180
- {folderChild(id, marked || id === focusedFolderId ? 0 : -1)}
174
+ {actions && (
175
+ <WrapperForFolderChild selected={selected}>
176
+ <MenuButton size="xsmall" menuItems={actions} tabIndex={selected || id === focusedFolderId ? 0 : -1} />
181
177
  </WrapperForFolderChild>
182
178
  )}
183
179
  </>
@@ -185,18 +181,14 @@ const FolderItem = ({
185
181
  <FolderNameLink
186
182
  ref={ref}
187
183
  onKeyDown={(e: KeyboardEvent<HTMLElement>) =>
188
- arrowNavigation(e, id, visibleFolders, setFocusedFolderId, onOpenFolder, onCloseFolder)
184
+ arrowNavigation(e, id, visibleFolders, setFocusedId, onOpenFolder, onCloseFolder)
189
185
  }
190
186
  noArrow={hideArrow}
191
- to={loading ? '' : `/minndla/${level > 1 ? 'folders/' : ''}${id}`}
192
- tabIndex={marked || level === 1 ? 0 : -1}
193
- marked={marked}
194
- onFocus={() => {
195
- setFocusedFolderId(id);
196
- }}
197
- onClick={() => {
198
- handleMarkFolder();
199
- }}>
187
+ to={loading ? '' : linkPath}
188
+ tabIndex={selected || focused || level === 1 ? 0 : -1}
189
+ selected={selected}
190
+ onFocus={() => setFocusedId(id)}
191
+ onClick={handleClickFolder}>
200
192
  {icon || <FolderOutlined />}
201
193
  {name}
202
194
  </FolderNameLink>
@@ -11,7 +11,7 @@ import styled from '@emotion/styled';
11
11
  import { animations, spacing } from '@ndla/core';
12
12
  import FolderItem from './FolderItem';
13
13
  import FolderNameInput from './FolderNameInput';
14
- import { FolderItemsProps } from './TreeStructure.types';
14
+ import { CommonFolderItemsProps, FolderType } from './types';
15
15
 
16
16
  const StyledUL = styled.ul<{ firstLevel?: boolean }>`
17
17
  ${animations.fadeInLeft(animations.durations.fast)};
@@ -30,85 +30,65 @@ const StyledLI = styled.li`
30
30
  padding: 0;
31
31
  `;
32
32
 
33
+ export interface FolderItemsProps extends CommonFolderItemsProps {
34
+ folders: FolderType[];
35
+ editable?: boolean;
36
+ maximumLevelsOfFoldersAllowed: number;
37
+ newFolderParentId: string | undefined;
38
+ onCancelNewFolder: () => void;
39
+ onSaveNewFolder: (name: string, parentId: string) => void;
40
+ openFolders: string[];
41
+ }
42
+
33
43
  const FolderItems = ({
34
- loading,
44
+ editable,
35
45
  folders,
36
46
  level,
37
- editable,
38
- onSelectFolder,
39
- onCloseFolder,
40
- onOpenFolder,
41
- onCreateNewFolder,
47
+ loading,
48
+ maximumLevelsOfFoldersAllowed,
49
+ newFolderParentId,
42
50
  onCancelNewFolder,
43
51
  onSaveNewFolder,
44
- newFolderParentId,
45
- visibleFolders,
46
52
  openFolders,
47
- markedFolderId,
48
- onMarkFolder,
49
- openOnFolderClick,
50
- focusedFolderId,
51
- setFocusedFolderId,
52
- folderChild,
53
- maximumLevelsOfFoldersAllowed,
53
+ ...rest
54
54
  }: FolderItemsProps) => (
55
55
  <StyledUL role="group" firstLevel={level === 1}>
56
- {folders.map(({ name, subfolders, id, icon }, _index) => {
56
+ {folders.map((folder) => {
57
+ const { subfolders, id } = folder;
57
58
  const isOpen = openFolders?.includes(id);
58
59
  return (
59
60
  <StyledLI key={id} role="treeitem">
60
61
  <div>
61
62
  <FolderItem
63
+ hideArrow={subfolders?.length === 0 || level > maximumLevelsOfFoldersAllowed}
64
+ folder={folder}
65
+ isOpen={isOpen}
62
66
  level={level}
63
- icon={icon}
64
- onSelectFolder={onSelectFolder}
65
- openOnFolderClick={openOnFolderClick}
66
67
  loading={loading}
67
- isOpen={isOpen}
68
- id={id}
69
- visibleFolders={visibleFolders}
70
- name={name}
71
- markedFolderId={markedFolderId}
72
- focusedFolderId={focusedFolderId}
73
- onMarkFolder={onMarkFolder}
74
- onCloseFolder={onCloseFolder}
75
- onOpenFolder={onOpenFolder}
76
- hideArrow={subfolders?.length === 0 || level > maximumLevelsOfFoldersAllowed}
77
68
  noPaddingWhenArrowIsHidden={editable && level === 1 && subfolders?.length === 0}
78
- setFocusedFolderId={setFocusedFolderId}
79
- folderChild={folderChild}
69
+ {...rest}
80
70
  />
81
71
  </div>
82
72
  {newFolderParentId === id && (
83
73
  <FolderNameInput
84
- parentId={newFolderParentId}
85
74
  loading={loading}
86
75
  onCancelNewFolder={onCancelNewFolder}
87
76
  onSaveNewFolder={onSaveNewFolder}
77
+ parentId={newFolderParentId}
88
78
  />
89
79
  )}
90
80
  {subfolders && isOpen && (
91
81
  <FolderItems
92
- onSelectFolder={onSelectFolder}
93
- loading={loading}
94
- newFolderParentId={newFolderParentId}
95
- visibleFolders={visibleFolders}
96
- openFolders={openFolders}
97
- level={level + 1}
98
82
  editable={editable}
99
83
  folders={subfolders}
100
- onCloseFolder={onCloseFolder}
101
- onOpenFolder={onOpenFolder}
102
- onCreateNewFolder={onCreateNewFolder}
103
- onSaveNewFolder={onSaveNewFolder}
104
- onCancelNewFolder={onCancelNewFolder}
105
- markedFolderId={markedFolderId}
106
- onMarkFolder={onMarkFolder}
107
- openOnFolderClick={openOnFolderClick}
108
- focusedFolderId={focusedFolderId}
109
- setFocusedFolderId={setFocusedFolderId}
110
- folderChild={folderChild}
84
+ level={level + 1}
85
+ loading={loading}
111
86
  maximumLevelsOfFoldersAllowed={maximumLevelsOfFoldersAllowed}
87
+ newFolderParentId={newFolderParentId}
88
+ onCancelNewFolder={onCancelNewFolder}
89
+ onSaveNewFolder={onSaveNewFolder}
90
+ openFolders={openFolders}
91
+ {...rest}
112
92
  />
113
93
  )}
114
94
  </StyledLI>