@ndla/ui 25.3.0 → 26.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 (50) hide show
  1. package/es/LanguageSelector/LanguageSelector.js +12 -7
  2. package/es/TreeStructure/FolderItem.js +110 -94
  3. package/es/TreeStructure/FolderItems.js +26 -30
  4. package/es/TreeStructure/FolderNameInput.js +35 -27
  5. package/es/TreeStructure/NavigationLink.js +81 -0
  6. package/es/TreeStructure/TreeStructure.js +169 -45
  7. package/es/locale/messages-en.js +2 -0
  8. package/es/locale/messages-nb.js +2 -0
  9. package/es/locale/messages-nn.js +2 -0
  10. package/es/locale/messages-se.js +2 -0
  11. package/es/locale/messages-sma.js +2 -0
  12. package/lib/LanguageSelector/LanguageSelector.js +13 -7
  13. package/lib/TreeStructure/FolderItem.d.ts +2 -3
  14. package/lib/TreeStructure/FolderItem.js +107 -92
  15. package/lib/TreeStructure/FolderItems.d.ts +1 -3
  16. package/lib/TreeStructure/FolderItems.js +26 -29
  17. package/lib/TreeStructure/FolderNameInput.d.ts +2 -1
  18. package/lib/TreeStructure/FolderNameInput.js +33 -26
  19. package/lib/TreeStructure/NavigationLink.d.ts +15 -0
  20. package/lib/TreeStructure/NavigationLink.js +100 -0
  21. package/lib/TreeStructure/TreeStructure.d.ts +1 -2
  22. package/lib/TreeStructure/TreeStructure.js +163 -45
  23. package/lib/TreeStructure/types.d.ts +4 -1
  24. package/lib/locale/messages-en.d.ts +2 -0
  25. package/lib/locale/messages-en.js +2 -0
  26. package/lib/locale/messages-nb.d.ts +2 -0
  27. package/lib/locale/messages-nb.js +2 -0
  28. package/lib/locale/messages-nn.d.ts +2 -0
  29. package/lib/locale/messages-nn.js +2 -0
  30. package/lib/locale/messages-se.d.ts +2 -0
  31. package/lib/locale/messages-se.js +2 -0
  32. package/lib/locale/messages-sma.d.ts +2 -0
  33. package/lib/locale/messages-sma.js +2 -0
  34. package/package.json +11 -11
  35. package/src/LanguageSelector/LanguageSelector.tsx +4 -1
  36. package/src/TreeStructure/FolderItem.tsx +126 -104
  37. package/src/TreeStructure/FolderItems.tsx +51 -43
  38. package/src/TreeStructure/FolderNameInput.tsx +43 -28
  39. package/src/TreeStructure/NavigationLink.tsx +100 -0
  40. package/src/TreeStructure/TreeStructure.tsx +187 -61
  41. package/src/TreeStructure/types.ts +5 -1
  42. package/src/locale/messages-en.ts +2 -0
  43. package/src/locale/messages-nb.ts +2 -0
  44. package/src/locale/messages-nn.ts +2 -0
  45. package/src/locale/messages-se.ts +2 -0
  46. package/src/locale/messages-sma.ts +2 -0
  47. package/es/TreeStructure/TreeStructureWrapper.js +0 -13
  48. package/lib/TreeStructure/TreeStructureWrapper.d.ts +0 -12
  49. package/lib/TreeStructure/TreeStructureWrapper.js +0 -24
  50. package/src/TreeStructure/TreeStructureWrapper.tsx +0 -31
@@ -10,7 +10,9 @@ import { IFolder, IResource } from '@ndla/types-learningpath-api';
10
10
  import { MenuItemProps } from '@ndla/button';
11
11
  export interface FolderType extends IFolder {
12
12
  icon?: ReactNode;
13
+ isNavigation?: boolean;
13
14
  }
15
+ export declare type TreeStructureType = 'normal' | 'navigation' | 'picker';
14
16
  export interface TreeStructureMenuProps extends Omit<MenuItemProps, 'onClick'> {
15
17
  onClick: (e: MouseEvent<HTMLDivElement> | undefined, folder: FolderType) => void;
16
18
  }
@@ -18,13 +20,14 @@ export interface CommonTreeStructureProps {
18
20
  loading?: boolean;
19
21
  onSelectFolder?: (id: string) => void;
20
22
  openOnFolderClick?: boolean;
21
- menuItems?: TreeStructureMenuProps[];
22
23
  targetResource?: IResource;
23
24
  framed?: boolean;
25
+ type: TreeStructureType;
24
26
  }
25
27
  export interface CommonFolderItemsProps extends CommonTreeStructureProps {
26
28
  focusedFolderId?: string;
27
29
  level: number;
30
+ maxLevel: number;
28
31
  selectedFolder?: FolderType;
29
32
  onCloseFolder: (id: string) => void;
30
33
  onOpenFolder: (id: string) => void;
@@ -169,6 +169,8 @@ declare const messages: {
169
169
  edit: string;
170
170
  delete: string;
171
171
  };
172
+ hideFolders: string;
173
+ showFolders: string;
172
174
  createFolder: string;
173
175
  maxFoldersAlreadyAdded: string;
174
176
  newFolder: {
@@ -33,6 +33,8 @@ var messages = _objectSpread(_objectSpread({
33
33
  edit: 'Edit foldername',
34
34
  "delete": 'Delete'
35
35
  },
36
+ hideFolders: 'Hide all folders',
37
+ showFolders: 'Show all folders',
36
38
  createFolder: 'Create folder',
37
39
  maxFoldersAlreadyAdded: 'Maximum subfolders reached',
38
40
  newFolder: {
@@ -169,6 +169,8 @@ declare const messages: {
169
169
  edit: string;
170
170
  delete: string;
171
171
  };
172
+ hideFolders: string;
173
+ showFolders: string;
172
174
  createFolder: string;
173
175
  maxFoldersAlreadyAdded: string;
174
176
  newFolder: {
@@ -33,6 +33,8 @@ var messages = _objectSpread(_objectSpread({
33
33
  edit: 'Endre mappenavn',
34
34
  "delete": 'Slett'
35
35
  },
36
+ hideFolders: 'Skjul alle mapper',
37
+ showFolders: 'Vis alle mapper',
36
38
  createFolder: 'Lag mappe',
37
39
  maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
38
40
  newFolder: {
@@ -169,6 +169,8 @@ declare const messages: {
169
169
  edit: string;
170
170
  delete: string;
171
171
  };
172
+ hideFolders: string;
173
+ showFolders: string;
172
174
  createFolder: string;
173
175
  maxFoldersAlreadyAdded: string;
174
176
  newFolder: {
@@ -33,6 +33,8 @@ var messages = _objectSpread(_objectSpread({
33
33
  edit: 'Endre mappenamn',
34
34
  "delete": 'Slett'
35
35
  },
36
+ hideFolders: 'Skjul alle mapper',
37
+ showFolders: 'Vis alle mapper',
36
38
  createFolder: 'Lag mappe',
37
39
  maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
38
40
  newFolder: {
@@ -169,6 +169,8 @@ declare const messages: {
169
169
  edit: string;
170
170
  delete: string;
171
171
  };
172
+ hideFolders: string;
173
+ showFolders: string;
172
174
  createFolder: string;
173
175
  maxFoldersAlreadyAdded: string;
174
176
  newFolder: {
@@ -33,6 +33,8 @@ var messages = _objectSpread(_objectSpread({
33
33
  edit: 'Rievdat máhpa nama',
34
34
  "delete": 'Sihko'
35
35
  },
36
+ hideFolders: 'Skjul alle mapper',
37
+ showFolders: 'Vis alle mapper',
36
38
  createFolder: 'Ráhkat máhpa',
37
39
  maxFoldersAlreadyAdded: 'Vuollemáhpaid badjerádji lea olahuvvon',
38
40
  newFolder: {
@@ -169,6 +169,8 @@ declare const messages: {
169
169
  edit: string;
170
170
  delete: string;
171
171
  };
172
+ hideFolders: string;
173
+ showFolders: string;
172
174
  createFolder: string;
173
175
  maxFoldersAlreadyAdded: string;
174
176
  newFolder: {
@@ -33,6 +33,8 @@ var messages = _objectSpread(_objectSpread({
33
33
  edit: 'Endre mappenavn',
34
34
  "delete": 'Slett'
35
35
  },
36
+ hideFolders: 'Skjul alle mapper',
37
+ showFolders: 'Vis alle mapper',
36
38
  createFolder: 'Lag mappe',
37
39
  maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
38
40
  newFolder: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
- "version": "25.3.0",
3
+ "version": "26.0.0",
4
4
  "description": "UI component library for NDLA.",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -32,18 +32,18 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@ndla/article-scripts": "^3.0.2",
35
- "@ndla/button": "^3.4.1",
36
- "@ndla/carousel": "^1.2.21",
35
+ "@ndla/button": "^3.5.0",
36
+ "@ndla/carousel": "^1.2.22",
37
37
  "@ndla/core": "^2.3.5",
38
- "@ndla/forms": "^3.2.0",
38
+ "@ndla/forms": "^3.2.1",
39
39
  "@ndla/hooks": "^1.1.6",
40
- "@ndla/icons": "^1.12.0",
41
- "@ndla/licenses": "^5.0.15",
42
- "@ndla/modal": "^1.3.5",
43
- "@ndla/notion": "^3.1.39",
44
- "@ndla/safelink": "^2.2.13",
40
+ "@ndla/icons": "^1.12.1",
41
+ "@ndla/licenses": "^5.0.16",
42
+ "@ndla/modal": "^1.3.6",
43
+ "@ndla/notion": "^3.1.40",
44
+ "@ndla/safelink": "^2.2.14",
45
45
  "@ndla/switch": "^0.1.13",
46
- "@ndla/tabs": "^1.1.20",
46
+ "@ndla/tabs": "^1.1.21",
47
47
  "@ndla/tooltip": "^2.2.1",
48
48
  "@ndla/types-learningpath-api": "^0.0.13",
49
49
  "@ndla/util": "^3.1.0",
@@ -85,5 +85,5 @@
85
85
  "publishConfig": {
86
86
  "access": "public"
87
87
  },
88
- "gitHead": "b00cf1eb600a2fa9b3113b51abd0c7b2f02d0860"
88
+ "gitHead": "e2029bce0a42c485f247a60ee895b2320fddc6ba"
89
89
  }
@@ -115,7 +115,9 @@ interface StyledButtonProps {
115
115
  inverted?: boolean;
116
116
  }
117
117
 
118
- const StyledButton = styled(Button)<StyledButtonProps>`
118
+ const shouldForwardProp = (name: string) => name !== 'outline';
119
+
120
+ const StyledButton = styled(Button, { shouldForwardProp })<StyledButtonProps>`
119
121
  border-color: ${({ inverted, outline }) =>
120
122
  outline ? (inverted ? colors.white : colors.brand.primary) : 'transparent'};
121
123
  `;
@@ -139,6 +141,7 @@ const LanguageSelector = ({ currentLanguage, outline, center, inverted, alwaysVi
139
141
  </StyledSpan>
140
142
  <ChevronDown />
141
143
  </StyledButton>
144
+
142
145
  {isOpen && (
143
146
  <FocusTrapReact
144
147
  active
@@ -6,40 +6,38 @@
6
6
  *
7
7
  */
8
8
 
9
- import React, { KeyboardEvent, MouseEvent, useEffect, useRef } from 'react';
9
+ import React, { KeyboardEvent, useEffect, useRef } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import styled from '@emotion/styled';
12
- import { ArrowDropDown } from '@ndla/icons/common';
12
+ import { ArrowDropDownRounded } from '@ndla/icons/common';
13
13
  import { Done } from '@ndla/icons/editor';
14
- import { MenuButton } from '@ndla/button';
15
- import { FolderOutlined } from '@ndla/icons/contentType';
16
- import { colors, spacing, misc, animations } from '@ndla/core';
14
+ import { ButtonV2 as Button } from '@ndla/button';
15
+ import { colors, spacing, animations, spacingUnit, misc, fonts } from '@ndla/core';
17
16
  import SafeLink from '@ndla/safelink';
18
17
  import { CommonFolderItemsProps, FolderType } from './types';
19
18
  import { arrowNavigation } from './arrowNavigation';
20
19
 
21
- const OpenButton = styled.button<{ isOpen: boolean }>`
22
- background: transparent;
23
- border: 0;
24
- transform: rotate(${({ isOpen }) => (isOpen ? '0' : '-90')}deg);
25
- padding: ${spacing.xsmall};
20
+ const OpenButton = styled.span<{ isOpen: boolean }>`
26
21
  display: flex;
27
- margin: 0;
28
- color: ${colors.brand.secondary};
22
+ align-items: center;
23
+ justify-content: center;
24
+ align-self: stretch;
25
+ color: ${colors.brand.tertiary};
26
+ ${misc.transition.default};
29
27
  cursor: pointer;
30
28
  &:hover {
31
29
  color: ${colors.brand.primary};
32
30
  }
33
31
  svg {
34
- width: 16px;
35
- height: 16px;
36
- transform: ${({ isOpen }) => (isOpen ? 'translateX(3px)' : 'translateY(3px)')};
32
+ width: 24px;
33
+ height: 24px;
34
+ transform: rotate(${({ isOpen }) => (isOpen ? '0' : '-90')}deg);
37
35
  }
38
36
  `;
39
37
 
40
- const FolderItemWrapper = styled.div`
41
- display: flex;
42
- align-items: center;
38
+ const StyledName = styled.span`
39
+ grid-column-start: 2;
40
+ text-align: left;
43
41
  `;
44
42
 
45
43
  const WrapperForFolderChild = styled.div`
@@ -47,44 +45,39 @@ const WrapperForFolderChild = styled.div`
47
45
  flex-direction: row;
48
46
  align-items: center;
49
47
  gap: ${spacing.xsmall};
48
+ margin-left: auto;
50
49
  `;
51
50
 
52
- const shouldForwardProp = (name: string) => !['selected', 'noArrow', 'fullWidth'].includes(name);
51
+ const shouldForwardProp = (name: string) => !['selected', 'noArrow', 'fullWidth', 'level'].includes(name);
53
52
 
54
53
  interface FolderNameProps {
55
54
  selected?: boolean;
56
55
  noArrow?: boolean;
57
56
  fullWidth?: boolean;
57
+ level: number;
58
+ isCreatingFolder?: boolean;
58
59
  }
59
60
 
60
- const FolderName = styled('button', { shouldForwardProp })<FolderNameProps>`
61
- cursor: pointer;
62
- padding: ${spacing.xsmall};
63
- margin: 0;
64
- outline-offset: -2px;
65
- outline-color: ${colors.brand.primary};
66
- margin-left: ${({ noArrow }) => (noArrow ? `29px` : `0px`)};
67
- flex-grow: ${({ fullWidth }) => fullWidth && 1};
61
+ const FolderName = styled(Button, { shouldForwardProp })<FolderNameProps>`
68
62
  display: grid;
69
- grid-template-columns: auto 1fr auto;
70
- align-items: center;
63
+ grid-template-columns: ${spacing.medium} 1fr auto;
64
+
65
+ padding-left: ${({ level }) => 0.75 * spacingUnit * level}px;
71
66
  gap: ${spacing.xxsmall};
72
- border: 0;
73
- border-radius: ${misc.borderRadius};
74
- box-shadow: none;
75
- background: ${({ selected }) => (selected ? colors.brand.lighter : 'transparent')};
76
- color: ${colors.text.primary};
67
+ border: none;
68
+ outline: none;
69
+ background: ${({ selected, isCreatingFolder }) => selected && !isCreatingFolder && colors.brand.lighter};
70
+ color: ${({ isCreatingFolder, selected }) =>
71
+ isCreatingFolder && selected ? colors.brand.primary : colors.text.primary};
77
72
  transition: ${animations.durations.superFast};
78
- text-align: left;
79
73
  line-height: 1;
80
74
  word-break: break-word;
81
75
  &:hover,
82
76
  &:focus {
77
+ box-shadow: none;
78
+ outline: none;
83
79
  background: ${({ selected }) => (selected ? colors.brand.light : colors.brand.lightest)};
84
- color: ${colors.brand.primary};
85
- + ${WrapperForFolderChild} {
86
- opacity: 1;
87
- }
80
+ color: ${colors.text.primary};
88
81
  }
89
82
  `;
90
83
 
@@ -92,25 +85,42 @@ const StyledDone = styled(Done)`
92
85
  color: ${colors.support.green};
93
86
  `;
94
87
 
95
- const FolderNameLink = FolderName.withComponent(SafeLink);
88
+ const FolderNameLink = styled(SafeLink, { shouldForwardProp })<FolderNameProps>`
89
+ display: grid;
90
+ align-items: center;
91
+ grid-template-columns: ${spacing.medium} 1fr auto;
92
+ padding: ${spacing.small} ${spacing.xxsmall};
93
+ margin-left: ${({ level }) => 0.75 * spacingUnit * level}px;
94
+ gap: ${spacing.xxsmall};
95
+ cursor: pointer;
96
+
97
+ border: none;
98
+ box-shadow: none;
99
+ color: ${({ selected }) => (selected ? colors.brand.primary : colors.text.primary)};
100
+ font-weight: ${({ selected }) => selected && fonts.weight.semibold};
101
+ font-size: ${fonts.sizes('16px')};
102
+ transition: ${animations.durations.superFast};
103
+ line-height: 1;
104
+ word-break: break-word;
105
+ &:hover,
106
+ &:focus {
107
+ color: ${colors.brand.primary};
108
+ }
109
+ `;
96
110
 
97
111
  interface Props extends CommonFolderItemsProps {
98
- hideArrow?: boolean;
99
112
  isOpen: boolean;
100
113
  folder: FolderType;
101
- noPaddingWhenArrowIsHidden?: boolean;
114
+ isCreatingFolder?: boolean;
102
115
  }
103
116
 
104
117
  const FolderItem = ({
105
118
  focusedFolderId,
106
- menuItems,
107
- hideArrow,
108
119
  folder,
109
120
  isOpen,
110
121
  level,
111
122
  loading,
112
123
  selectedFolder,
113
- noPaddingWhenArrowIsHidden,
114
124
  onCloseFolder,
115
125
  onOpenFolder,
116
126
  onSelectFolder,
@@ -120,9 +130,11 @@ const FolderItem = ({
120
130
  targetResource,
121
131
  visibleFolders,
122
132
  framed,
133
+ maxLevel,
134
+ isCreatingFolder,
123
135
  }: Props) => {
124
136
  const { t } = useTranslation();
125
- const { id, icon, name } = folder;
137
+ const { id, name } = folder;
126
138
  const ref = useRef<HTMLButtonElement & HTMLAnchorElement>(null);
127
139
  const selected = selectedFolder && selectedFolder.id === id;
128
140
  const focused = focusedFolderId === id;
@@ -145,79 +157,89 @@ const FolderItem = ({
145
157
  };
146
158
 
147
159
  useEffect(() => {
148
- if (focusedFolderId === id) {
160
+ if (focusedFolderId === id && !isCreatingFolder) {
149
161
  ref.current?.focus();
150
162
  }
151
- }, [focusedFolderId, ref, id]);
152
-
153
- const actions = menuItems?.map((item) => {
154
- const { onClick } = item;
155
- return {
156
- ...item,
157
- onClick: (e?: MouseEvent<HTMLDivElement>) => onClick(e, folder),
158
- };
159
- });
163
+ }, [focusedFolderId, ref, id, isCreatingFolder]);
160
164
 
161
165
  const linkPath = `/minndla${level > 0 ? '/folders' : ''}/${id}`;
162
166
 
163
167
  const containsResource =
164
168
  targetResource && folder.resources.some((resource) => resource.resourceId === targetResource.resourceId);
165
169
 
166
- return (
167
- <FolderItemWrapper>
170
+ const emptyFolder = folder.subfolders.length === 0;
171
+
172
+ const isMaxDepth = level > maxLevel;
173
+
174
+ const hideArrow = isMaxDepth || emptyFolder;
175
+
176
+ return onSelectFolder ? (
177
+ <FolderName
178
+ variant="ghost"
179
+ shape="sharp"
180
+ fontWeight="normal"
181
+ colorTheme="light"
182
+ ref={ref}
183
+ level={level}
184
+ fullWidth={framed}
185
+ onKeyDown={(e) => arrowNavigation(e, id, visibleFolders, setFocusedId, onOpenFolder, onCloseFolder)}
186
+ noArrow={hideArrow}
187
+ tabIndex={selected || focused ? 0 : -1}
188
+ selected={selected}
189
+ disabled={loading}
190
+ onFocus={() => setFocusedId(id)}
191
+ onClick={handleClickFolder}
192
+ isCreatingFolder={isCreatingFolder}>
168
193
  {!hideArrow && (
169
194
  <OpenButton
170
195
  tabIndex={-1}
171
196
  isOpen={isOpen}
172
- disabled={loading}
173
- onClick={() => (isOpen ? onCloseFolder(id) : onOpenFolder(id))}>
174
- <ArrowDropDown />
197
+ onClick={() => {
198
+ ref.current?.focus();
199
+ if (isOpen) {
200
+ onCloseFolder(id);
201
+ } else {
202
+ onOpenFolder(id);
203
+ }
204
+ }}>
205
+ <ArrowDropDownRounded />
175
206
  </OpenButton>
176
207
  )}
177
- {onSelectFolder ? (
178
- <>
179
- <FolderName
180
- fullWidth={framed}
181
- ref={ref}
182
- onKeyDown={(e) => arrowNavigation(e, id, visibleFolders, setFocusedId, onOpenFolder, onCloseFolder)}
183
- noArrow={hideArrow && !noPaddingWhenArrowIsHidden}
184
- tabIndex={selected || focused ? 0 : -1}
185
- selected={selected}
186
- disabled={loading}
187
- onFocus={() => setFocusedId(id)}
188
- onClick={handleClickFolder}>
189
- {icon || <FolderOutlined />}
190
- {name}
191
- <WrapperForFolderChild>
192
- {containsResource && <StyledDone title={t('myNdla.alreadyInFolder')} />}
193
- {actions && (
194
- <MenuButton
195
- onClick={(e) => e.stopPropagation()}
196
- size="xsmall"
197
- menuItems={actions}
198
- tabIndex={selected || id === focusedFolderId ? 0 : -1}
199
- />
200
- )}
201
- </WrapperForFolderChild>
202
- </FolderName>
203
- </>
204
- ) : (
205
- <FolderNameLink
206
- ref={ref}
207
- onKeyDown={(e: KeyboardEvent<HTMLElement>) =>
208
- arrowNavigation(e, id, visibleFolders, setFocusedId, onOpenFolder, onCloseFolder)
209
- }
210
- noArrow={hideArrow}
211
- to={loading ? '' : linkPath}
212
- tabIndex={selected || focused || level === 0 ? 0 : -1}
213
- selected={selected}
214
- onFocus={() => setFocusedId(id)}
215
- onClick={handleClickFolder}>
216
- {icon || <FolderOutlined />}
217
- {name}
218
- </FolderNameLink>
208
+ <StyledName>{name}</StyledName>
209
+ <WrapperForFolderChild>
210
+ {containsResource && <StyledDone title={t('myNdla.alreadyInFolder')} />}
211
+ </WrapperForFolderChild>
212
+ </FolderName>
213
+ ) : (
214
+ <FolderNameLink
215
+ ref={ref}
216
+ level={level}
217
+ onKeyDown={(e: KeyboardEvent<HTMLElement>) =>
218
+ arrowNavigation(e, id, visibleFolders, setFocusedId, onOpenFolder, onCloseFolder)
219
+ }
220
+ noArrow={!isMaxDepth}
221
+ to={loading ? '' : linkPath}
222
+ tabIndex={selected || focused ? 0 : -1}
223
+ selected={selected}
224
+ onFocus={() => setFocusedId(id)}
225
+ onClick={handleClickFolder}>
226
+ {(!hideArrow || level === 0) && (
227
+ <OpenButton
228
+ tabIndex={-1}
229
+ isOpen={isOpen}
230
+ onClick={() => {
231
+ ref.current?.focus();
232
+ if (isOpen) {
233
+ onCloseFolder(id);
234
+ } else {
235
+ onOpenFolder(id);
236
+ }
237
+ }}>
238
+ <ArrowDropDownRounded />
239
+ </OpenButton>
219
240
  )}
220
- </FolderItemWrapper>
241
+ <StyledName>{name}</StyledName>
242
+ </FolderNameLink>
221
243
  );
222
244
  };
223
245
 
@@ -8,12 +8,13 @@
8
8
 
9
9
  import React from 'react';
10
10
  import styled from '@emotion/styled';
11
- import { animations, spacing } from '@ndla/core';
11
+ import { animations } from '@ndla/core';
12
12
  import FolderItem from './FolderItem';
13
13
  import FolderNameInput from './FolderNameInput';
14
- import { CommonFolderItemsProps, FolderType } from './types';
14
+ import { CommonFolderItemsProps, FolderType, TreeStructureType } from './types';
15
+ import NavigationLink from './NavigationLink';
15
16
 
16
- const StyledUL = styled.ul<{ firstLevel?: boolean }>`
17
+ const StyledUL = styled.ul`
17
18
  ${animations.fadeInLeft(animations.durations.fast)};
18
19
  animation-fill-mode: forwards;
19
20
  @media (prefers-reduced-motion: reduce) {
@@ -22,18 +23,22 @@ const StyledUL = styled.ul<{ firstLevel?: boolean }>`
22
23
  list-style: none;
23
24
  margin: 0;
24
25
  padding: 0;
25
- margin-left: ${({ firstLevel }) => (firstLevel ? `-${spacing.xsmall}` : spacing.small)};
26
26
  `;
27
27
 
28
- const StyledLI = styled.li`
28
+ interface StyledLiProps {
29
+ type?: TreeStructureType;
30
+ }
31
+
32
+ const StyledLI = styled.li<StyledLiProps>`
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: ${({ type }) => type === 'navigation' && 'flex-start'};
29
36
  margin: 0;
30
37
  padding: 0;
31
38
  `;
32
39
 
33
40
  export interface FolderItemsProps extends CommonFolderItemsProps {
34
41
  folders: FolderType[];
35
- editable?: boolean;
36
- maximumLevelsOfFoldersAllowed: number;
37
42
  newFolderParentId: string | undefined;
38
43
  onCancelNewFolder: () => void;
39
44
  onSaveNewFolder: (name: string, parentId: string) => void;
@@ -41,56 +46,59 @@ export interface FolderItemsProps extends CommonFolderItemsProps {
41
46
  }
42
47
 
43
48
  const FolderItems = ({
44
- editable,
45
49
  folders,
46
50
  level,
47
51
  loading,
48
- maximumLevelsOfFoldersAllowed,
49
52
  newFolderParentId,
50
53
  onCancelNewFolder,
51
54
  onSaveNewFolder,
52
55
  openFolders,
56
+ type,
53
57
  ...rest
54
58
  }: FolderItemsProps) => (
55
- <StyledUL role="group" firstLevel={level === 0}>
59
+ <StyledUL role={level === 0 ? 'tree' : 'group'}>
56
60
  {folders.map((folder) => {
57
61
  const { subfolders, id } = folder;
58
62
  const isOpen = openFolders?.includes(id);
59
63
 
60
64
  return (
61
- <StyledLI key={id} role="treeitem">
62
- <div>
63
- <FolderItem
64
- hideArrow={subfolders?.length === 0 || level > maximumLevelsOfFoldersAllowed}
65
- folder={folder}
66
- isOpen={isOpen}
67
- level={level}
68
- loading={loading}
69
- noPaddingWhenArrowIsHidden={editable && level === 0 && subfolders?.length === 0}
70
- {...rest}
71
- />
72
- </div>
73
- {newFolderParentId === id && (
74
- <FolderNameInput
75
- loading={loading}
76
- onCancelNewFolder={onCancelNewFolder}
77
- onSaveNewFolder={onSaveNewFolder}
78
- parentId={newFolderParentId}
79
- />
80
- )}
81
- {subfolders && isOpen && (
82
- <FolderItems
83
- editable={editable}
84
- folders={subfolders}
85
- level={level + 1}
86
- loading={loading}
87
- maximumLevelsOfFoldersAllowed={maximumLevelsOfFoldersAllowed}
88
- newFolderParentId={newFolderParentId}
89
- onCancelNewFolder={onCancelNewFolder}
90
- onSaveNewFolder={onSaveNewFolder}
91
- openFolders={openFolders}
92
- {...rest}
93
- />
65
+ <StyledLI key={id} role="treeitem" type={type}>
66
+ {folder.isNavigation ? (
67
+ <NavigationLink folder={folder} isOpen={isOpen} level={level} type={type} loading={loading} {...rest} />
68
+ ) : (
69
+ <>
70
+ <FolderItem
71
+ folder={folder}
72
+ isOpen={isOpen}
73
+ level={level}
74
+ loading={loading}
75
+ type={type}
76
+ isCreatingFolder={newFolderParentId === folder.id}
77
+ {...rest}
78
+ />
79
+ {newFolderParentId === id && (
80
+ <FolderNameInput
81
+ loading={loading}
82
+ level={level}
83
+ onCancelNewFolder={onCancelNewFolder}
84
+ onSaveNewFolder={onSaveNewFolder}
85
+ parentId={newFolderParentId}
86
+ />
87
+ )}
88
+ {subfolders && isOpen && (
89
+ <FolderItems
90
+ folders={subfolders}
91
+ level={level + 1}
92
+ loading={loading}
93
+ type={type}
94
+ newFolderParentId={newFolderParentId}
95
+ onCancelNewFolder={onCancelNewFolder}
96
+ onSaveNewFolder={onSaveNewFolder}
97
+ openFolders={openFolders}
98
+ {...rest}
99
+ />
100
+ )}
101
+ </>
94
102
  )}
95
103
  </StyledLI>
96
104
  );