@ndla/ui 19.1.0 → 20.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 (83) hide show
  1. package/es/MyNdla/Resource/FolderInput.js +29 -36
  2. package/es/NDLAFilm/FilmSlideshow.js +8 -8
  3. package/es/Search/LoadingWrapper.js +2 -2
  4. package/es/Search/SearchResult.js +1 -1
  5. package/es/SearchTypeResult/SearchTypeResult.js +3 -3
  6. package/es/TreeStructure/FolderItem.js +54 -38
  7. package/es/TreeStructure/FolderItems.js +29 -35
  8. package/es/TreeStructure/FolderNameInput.js +12 -16
  9. package/es/TreeStructure/TreeStructure.js +64 -91
  10. package/es/TreeStructure/arrowNavigation.js +44 -0
  11. package/es/TreeStructure/helperFunctions.js +41 -35
  12. package/es/index.js +0 -1
  13. package/es/locale/messages-en.js +4 -1
  14. package/es/locale/messages-nb.js +4 -1
  15. package/es/locale/messages-nn.js +4 -1
  16. package/es/locale/messages-se.js +4 -1
  17. package/es/locale/messages-sma.js +4 -1
  18. package/lib/MyNdla/Resource/FolderInput.d.ts +2 -2
  19. package/lib/MyNdla/Resource/FolderInput.js +26 -33
  20. package/lib/NDLAFilm/FilmSlideshow.js +10 -10
  21. package/lib/Search/LoadingWrapper.js +3 -3
  22. package/lib/Search/SearchResult.js +2 -2
  23. package/lib/SearchTypeResult/SearchTypeResult.js +4 -4
  24. package/lib/TreeStructure/FolderItem.d.ts +6 -3
  25. package/lib/TreeStructure/FolderItem.js +55 -38
  26. package/lib/TreeStructure/FolderItems.d.ts +1 -1
  27. package/lib/TreeStructure/FolderItems.js +29 -35
  28. package/lib/TreeStructure/FolderNameInput.d.ts +3 -2
  29. package/lib/TreeStructure/FolderNameInput.js +13 -17
  30. package/lib/TreeStructure/TreeStructure.d.ts +1 -6
  31. package/lib/TreeStructure/TreeStructure.js +63 -92
  32. package/lib/TreeStructure/TreeStructure.types.d.ts +13 -20
  33. package/lib/TreeStructure/arrowNavigation.d.ts +9 -0
  34. package/lib/TreeStructure/arrowNavigation.js +54 -0
  35. package/lib/TreeStructure/helperFunctions.d.ts +3 -4
  36. package/lib/TreeStructure/helperFunctions.js +45 -35
  37. package/lib/index.d.ts +0 -1
  38. package/lib/index.js +0 -9
  39. package/lib/locale/messages-en.d.ts +3 -0
  40. package/lib/locale/messages-en.js +4 -1
  41. package/lib/locale/messages-nb.d.ts +3 -0
  42. package/lib/locale/messages-nb.js +4 -1
  43. package/lib/locale/messages-nn.d.ts +3 -0
  44. package/lib/locale/messages-nn.js +4 -1
  45. package/lib/locale/messages-se.d.ts +3 -0
  46. package/lib/locale/messages-se.js +4 -1
  47. package/lib/locale/messages-sma.d.ts +3 -0
  48. package/lib/locale/messages-sma.js +4 -1
  49. package/package.json +11 -10
  50. package/src/MyNdla/Resource/FolderInput.tsx +41 -44
  51. package/src/NDLAFilm/FilmSlideshow.tsx +1 -1
  52. package/src/Search/LoadingWrapper.tsx +1 -1
  53. package/src/Search/SearchResult.jsx +1 -1
  54. package/src/SearchTypeResult/SearchTypeResult.tsx +1 -1
  55. package/src/TreeStructure/FolderItem.tsx +63 -40
  56. package/src/TreeStructure/FolderItems.tsx +26 -19
  57. package/src/TreeStructure/FolderNameInput.tsx +10 -12
  58. package/src/TreeStructure/TreeStructure.tsx +56 -71
  59. package/src/TreeStructure/TreeStructure.types.ts +13 -17
  60. package/src/TreeStructure/arrowNavigation.ts +53 -0
  61. package/src/TreeStructure/helperFunctions.ts +17 -25
  62. package/src/index.ts +0 -2
  63. package/src/locale/messages-en.ts +3 -0
  64. package/src/locale/messages-nb.ts +3 -0
  65. package/src/locale/messages-nn.ts +3 -0
  66. package/src/locale/messages-se.ts +3 -0
  67. package/src/locale/messages-sma.ts +3 -0
  68. package/es/Spinner/Spinner.js +0 -42
  69. package/es/Spinner/index.js +0 -2
  70. package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +0 -194
  71. package/es/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +0 -0
  72. package/lib/Spinner/Spinner.d.ts +0 -16
  73. package/lib/Spinner/Spinner.js +0 -54
  74. package/lib/Spinner/index.d.ts +0 -2
  75. package/lib/Spinner/index.js +0 -13
  76. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.d.ts +0 -11
  77. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.js +0 -198
  78. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.d.ts +0 -26
  79. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +0 -1
  80. package/src/Spinner/Spinner.tsx +0 -46
  81. package/src/Spinner/index.ts +0 -3
  82. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.ts +0 -161
  83. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.types.ts +0 -28
@@ -32,65 +32,73 @@ const StyledLI = styled.li`
32
32
 
33
33
  const FolderItems = ({
34
34
  loading,
35
- data,
36
- idPaths,
35
+ folders,
36
+ level,
37
37
  editable,
38
- onToggleOpen,
38
+ onSelectFolder,
39
+ onCloseFolder,
40
+ onOpenFolder,
39
41
  onCreateNewFolder,
40
42
  onCancelNewFolder,
41
43
  onSaveNewFolder,
42
- newFolder,
44
+ newFolderParentId,
45
+ visibleFolders,
43
46
  openFolders,
44
47
  markedFolderId,
45
48
  onMarkFolder,
46
49
  openOnFolderClick,
47
50
  focusedFolderId,
48
51
  setFocusedFolderId,
49
- firstLevel,
50
52
  folderChild,
51
53
  maximumLevelsOfFoldersAllowed,
52
54
  }: FolderItemsProps) => (
53
- <StyledUL role="group" firstLevel={firstLevel}>
54
- {data.map(({ name, data: dataChildren, id, url, icon }, _index) => {
55
- const newIdPaths = [...idPaths, _index];
55
+ <StyledUL role="group" firstLevel={level === 1}>
56
+ {folders.map(({ name, subfolders, id, icon }, _index) => {
56
57
  const isOpen = openFolders?.includes(id);
57
58
  return (
58
59
  <StyledLI key={id} role="treeitem">
59
60
  <div>
60
61
  <FolderItem
62
+ level={level}
61
63
  icon={icon}
62
- url={url}
64
+ onSelectFolder={onSelectFolder}
63
65
  openOnFolderClick={openOnFolderClick}
64
66
  loading={loading}
65
67
  isOpen={isOpen}
66
68
  id={id}
69
+ visibleFolders={visibleFolders}
67
70
  name={name}
68
71
  markedFolderId={markedFolderId}
69
72
  focusedFolderId={focusedFolderId}
70
- onToggleOpen={onToggleOpen}
71
73
  onMarkFolder={onMarkFolder}
72
- hideArrow={dataChildren?.length === 0 || newIdPaths.length >= maximumLevelsOfFoldersAllowed}
73
- noPaddingWhenArrowIsHidden={editable && firstLevel && dataChildren?.length === 0}
74
+ onCloseFolder={onCloseFolder}
75
+ onOpenFolder={onOpenFolder}
76
+ hideArrow={subfolders?.length === 0 || level > maximumLevelsOfFoldersAllowed}
77
+ noPaddingWhenArrowIsHidden={editable && level === 1 && subfolders?.length === 0}
74
78
  setFocusedFolderId={setFocusedFolderId}
75
79
  folderChild={folderChild}
76
80
  />
77
81
  </div>
78
- {newFolder?.parentId === id && (
82
+ {newFolderParentId === id && (
79
83
  <FolderNameInput
84
+ parentId={newFolderParentId}
80
85
  loading={loading}
81
86
  onCancelNewFolder={onCancelNewFolder}
82
87
  onSaveNewFolder={onSaveNewFolder}
83
88
  />
84
89
  )}
85
- {dataChildren && isOpen && (
90
+ {subfolders && isOpen && (
86
91
  <FolderItems
92
+ onSelectFolder={onSelectFolder}
87
93
  loading={loading}
88
- newFolder={newFolder}
94
+ newFolderParentId={newFolderParentId}
95
+ visibleFolders={visibleFolders}
89
96
  openFolders={openFolders}
90
- idPaths={newIdPaths}
97
+ level={level + 1}
91
98
  editable={editable}
92
- data={dataChildren}
93
- onToggleOpen={onToggleOpen}
99
+ folders={subfolders}
100
+ onCloseFolder={onCloseFolder}
101
+ onOpenFolder={onOpenFolder}
94
102
  onCreateNewFolder={onCreateNewFolder}
95
103
  onSaveNewFolder={onSaveNewFolder}
96
104
  onCancelNewFolder={onCancelNewFolder}
@@ -99,7 +107,6 @@ const FolderItems = ({
99
107
  openOnFolderClick={openOnFolderClick}
100
108
  focusedFolderId={focusedFolderId}
101
109
  setFocusedFolderId={setFocusedFolderId}
102
- firstLevel={false}
103
110
  folderChild={folderChild}
104
111
  maximumLevelsOfFoldersAllowed={maximumLevelsOfFoldersAllowed}
105
112
  />
@@ -13,7 +13,7 @@ import { ArrowDropDown as ArrowDropDownRaw } from '@ndla/icons/common';
13
13
  import { spacing, colors, misc, animations } from '@ndla/core';
14
14
  import { useTranslation } from 'react-i18next';
15
15
  import { isMobile } from 'react-device-detect';
16
- import Spinner from '../Spinner';
16
+ import { Spinner } from '@ndla/icons';
17
17
 
18
18
  const ArrowRight = styled(ArrowDropDownRaw)`
19
19
  color: ${colors.text.primary};
@@ -53,14 +53,15 @@ const StyledInput = styled.input`
53
53
  `;
54
54
 
55
55
  interface FolderNameInputProps {
56
- onSaveNewFolder: (value: string) => void;
56
+ onSaveNewFolder: (name: string, parentId: string) => void;
57
+ parentId: string;
57
58
  onCancelNewFolder: () => void;
58
59
  loading?: boolean;
59
60
  }
60
61
 
61
- const FolderNameInput = ({ onSaveNewFolder, onCancelNewFolder, loading }: FolderNameInputProps) => {
62
+ const FolderNameInput = ({ onSaveNewFolder, parentId, onCancelNewFolder, loading }: FolderNameInputProps) => {
62
63
  const { t } = useTranslation();
63
- const [value, setValue] = useState<string>(t('treeStructure.newFolder.defaultName'));
64
+ const [name, setName] = useState<string>(t('treeStructure.newFolder.defaultName'));
64
65
  const inputRef = useRef<HTMLInputElement>(null);
65
66
 
66
67
  useEffect(() => {
@@ -82,22 +83,19 @@ const FolderNameInput = ({ onSaveNewFolder, onCancelNewFolder, loading }: Folder
82
83
  autoFocus
83
84
  placeholder={t('treeStructure.newFolder.placeholder')}
84
85
  disabled={loading}
85
- value={value}
86
+ value={name}
86
87
  onBlur={() => onCancelNewFolder()}
87
88
  onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
88
89
  if (e.key === 'Escape') {
89
90
  onCancelNewFolder();
90
- return;
91
- }
92
- if (e.key === 'Enter' || e.key === 'Tab') {
93
- onSaveNewFolder(value);
91
+ } else if (e.key === 'Enter' || e.key === 'Tab') {
94
92
  e.preventDefault();
93
+ onSaveNewFolder(name, parentId);
95
94
  }
96
- return;
97
95
  }}
98
96
  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
99
- const target = e.target as HTMLInputElement;
100
- setValue(target.value);
97
+ const target = e.target;
98
+ setName(target.value);
101
99
  }}
102
100
  />
103
101
  {loading && <Spinner size="small" />}
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import React, { useEffect, useState, useRef, useMemo } from 'react';
10
- import { uuid } from '@ndla/util';
11
10
  import { AddButton } from '@ndla/button';
12
11
  import Tooltip from '@ndla/tooltip';
13
12
  import { useTranslation } from 'react-i18next';
@@ -16,9 +15,8 @@ import { spacing, fonts } from '@ndla/core';
16
15
  import { uniq } from 'lodash';
17
16
  import TreeStructureStyledWrapper from './TreeStructureWrapper';
18
17
  import FolderItems from './FolderItems';
19
- import { getIdPathsOfFolder, getPathOfFolder, getFolderName } from './helperFunctions';
20
- import keyboardNavigation, { KEYBOARD_KEYS_OF_INTEREST } from './keyboardNavigation/keyboardNavigation';
21
- import { NewFolderProps, TreeStructureProps } from './TreeStructure.types';
18
+ import { getPathOfFolder, getFolderName, flattenFolders } from './helperFunctions';
19
+ import { TreeStructureProps } from './TreeStructure.types';
22
20
 
23
21
  export const MAX_LEVEL_FOR_FOLDERS = 4;
24
22
 
@@ -32,26 +30,32 @@ const AddFolderWrapper = styled.div`
32
30
  `;
33
31
 
34
32
  const TreeStructure = ({
35
- data,
33
+ folders,
36
34
  label,
37
35
  editable,
38
36
  loading,
39
37
  onNewFolder,
38
+ onSelectFolder,
40
39
  openOnFolderClick,
41
40
  framed,
42
41
  folderIdMarkedByDefault,
43
42
  defaultOpenFolders,
44
43
  folderChild,
45
- maximumLevelsOfFoldersAllowed,
44
+ maximumLevelsOfFoldersAllowed = MAX_LEVEL_FOR_FOLDERS,
46
45
  }: TreeStructureProps) => {
47
46
  const { t } = useTranslation();
48
- const [newFolder, setNewFolder] = useState<NewFolderProps | undefined>();
47
+ const [newFolderParentId, setNewFolderParentId] = useState<string | undefined>();
49
48
  const [openFolders, setOpenFolders] = useState<string[]>(defaultOpenFolders || []);
50
49
  const [focusedFolderId, setFocusedFolderId] = useState<string | undefined>();
51
- const [markedFolderId, setMarkedFolderId] = useState<string | undefined>(folderIdMarkedByDefault || data[0]?.id);
50
+ const [markedFolderId, setMarkedFolderId] = useState<string | undefined>(folderIdMarkedByDefault || folders[0]?.id);
52
51
  const treestructureRef = useRef<HTMLDivElement>(null);
53
52
  const wrapperRef = useRef<HTMLDivElement>(null);
54
- const rootLevelId = useMemo(() => uuid(), []); // TODO: use useId hook when we update to React 18
53
+ const rootLevelId = 'treestructure-root';
54
+
55
+ const visibleFolders = useMemo(
56
+ () => flattenFolders(folders, openFolders).map((folder) => folder.id),
57
+ [folders, openFolders],
58
+ );
55
59
 
56
60
  useEffect(() => {
57
61
  if (defaultOpenFolders) {
@@ -63,54 +67,51 @@ const TreeStructure = ({
63
67
 
64
68
  useEffect(() => {
65
69
  if (!loading) {
66
- setNewFolder(undefined);
70
+ setNewFolderParentId(undefined);
67
71
  }
68
72
  }, [loading]);
69
73
 
70
- const onToggleOpen = (id: string) => {
71
- if (openFolders.includes(id)) {
72
- // Did we just closed a folder with a marked folder inside it?
73
- // If so, we need to mark the folder we just closed.
74
- if (markedFolderId) {
75
- const closingFolderPath = getPathOfFolder(data, id);
76
- const markedFolderPath = getPathOfFolder(data, markedFolderId);
77
- const markedFolderIsSubPath = closingFolderPath.every(
78
- (folderId, _index) => markedFolderPath[_index] === folderId,
79
- );
80
- if (markedFolderIsSubPath) {
74
+ const onCloseFolder = (id: string) => {
75
+ // Did we just closed a folder with a marked folder inside it?
76
+ // If so, we need to mark the folder we just closed.
77
+ if (markedFolderId) {
78
+ const closingFolderPath = getPathOfFolder(folders, id);
79
+ const markedFolderPath = getPathOfFolder(folders, markedFolderId);
80
+ const markedFolderIsSubPath = closingFolderPath.every(
81
+ (folderId, _index) => markedFolderPath[_index] === folderId,
82
+ );
83
+ if (markedFolderIsSubPath) {
84
+ if (onSelectFolder) {
81
85
  setMarkedFolderId(closingFolderPath[closingFolderPath.length - 1]);
86
+ onSelectFolder(closingFolderPath[closingFolderPath.length - 1]);
87
+ } else {
88
+ setFocusedFolderId(closingFolderPath[closingFolderPath.length - 1]);
82
89
  }
83
90
  }
84
- setOpenFolders(openFolders.filter((folder) => folder !== id));
85
- } else {
86
- setOpenFolders(uniq([...openFolders, id]));
87
91
  }
92
+ setOpenFolders(openFolders.filter((folder) => folder !== id));
88
93
  };
89
94
 
90
- const onCreateNewFolder = (props: { idPaths: number[]; parentId?: string }) => {
91
- setNewFolder(props);
95
+ const onOpenFolder = (id: string) => {
96
+ setOpenFolders(uniq(openFolders.concat(id)));
92
97
  };
93
98
 
94
- const onSaveNewFolder = (value: string) => {
95
- if (newFolder) {
96
- // We would like to create a new folder with the name of value.
97
- // Its location in structure is based on newFolder object
98
- onNewFolder({ ...newFolder, value }).then((newFolderId) => {
99
- if (newFolderId) {
100
- setMarkedFolderId(newFolderId);
101
- setFocusedFolderId(newFolderId);
102
- // Open current folder in case it was closed..
103
-
104
- if (newFolder.parentId) {
105
- setOpenFolders(uniq([...openFolders, newFolder.parentId]));
106
- }
107
- }
108
- });
109
- }
99
+ const onCreateNewFolder = (parentId: string) => {
100
+ setNewFolderParentId(parentId);
101
+ };
102
+
103
+ const onSaveNewFolder = (name: string, parentId: string) => {
104
+ onNewFolder(name, parentId).then((newFolderId) => {
105
+ if (newFolderId) {
106
+ setMarkedFolderId(newFolderId);
107
+ setFocusedFolderId(newFolderId);
108
+ setOpenFolders(uniq(openFolders.concat(parentId)));
109
+ }
110
+ });
110
111
  };
111
112
 
112
113
  const onCancelNewFolder = () => {
113
- setNewFolder(undefined);
114
+ setNewFolderParentId(undefined);
114
115
  };
115
116
 
116
117
  const onMarkFolder = (id: string) => {
@@ -118,35 +119,25 @@ const TreeStructure = ({
118
119
  setFocusedFolderId(id);
119
120
  };
120
121
 
121
- const paths = getPathOfFolder(data, markedFolderId || '');
122
+ const paths = getPathOfFolder(folders, markedFolderId || '');
122
123
  const canAddFolder = editable && paths.length < (maximumLevelsOfFoldersAllowed || 1);
123
124
 
124
125
  return (
125
- <div
126
- ref={treestructureRef}
127
- onKeyDown={(e) => {
128
- if (wrapperRef.current?.contains(document.activeElement) && KEYBOARD_KEYS_OF_INTEREST.includes(e.key)) {
129
- keyboardNavigation({
130
- e,
131
- data,
132
- setFocusedFolderId,
133
- focusedFolderId,
134
- onToggleOpen,
135
- openFolders,
136
- });
137
- }
138
- }}>
126
+ <div ref={treestructureRef}>
139
127
  {label && <StyledLabel htmlFor={rootLevelId}>{label}</StyledLabel>}
140
128
  <TreeStructureStyledWrapper ref={wrapperRef} id={rootLevelId} aria-label="Menu tree" role="tree" framed={framed}>
141
129
  <FolderItems
142
- idPaths={[]}
143
- data={data}
130
+ onSelectFolder={onSelectFolder}
131
+ level={1}
132
+ folders={folders}
144
133
  editable={editable}
145
- onToggleOpen={onToggleOpen}
146
- newFolder={newFolder}
134
+ onOpenFolder={onOpenFolder}
135
+ onCloseFolder={onCloseFolder}
136
+ newFolderParentId={newFolderParentId}
147
137
  onCreateNewFolder={onCreateNewFolder}
148
138
  onCancelNewFolder={onCancelNewFolder}
149
139
  onSaveNewFolder={onSaveNewFolder}
140
+ visibleFolders={visibleFolders}
150
141
  openFolders={openFolders}
151
142
  markedFolderId={markedFolderId}
152
143
  onMarkFolder={onMarkFolder}
@@ -154,7 +145,6 @@ const TreeStructure = ({
154
145
  loading={loading}
155
146
  focusedFolderId={focusedFolderId}
156
147
  setFocusedFolderId={setFocusedFolderId}
157
- firstLevel
158
148
  folderChild={folderChild}
159
149
  maximumLevelsOfFoldersAllowed={maximumLevelsOfFoldersAllowed}
160
150
  />
@@ -165,16 +155,15 @@ const TreeStructure = ({
165
155
  tooltip={
166
156
  canAddFolder
167
157
  ? t('myNdla.newFolderUnder', {
168
- folderName: getFolderName(data, markedFolderId),
158
+ folderName: getFolderName(folders, markedFolderId),
169
159
  })
170
- : t('myNdla.maxFoldersAlreadyAdded')
160
+ : t('treeStructure.maxFoldersAlreadyAdded')
171
161
  }>
172
162
  <AddButton
173
163
  disabled={!canAddFolder}
174
164
  aria-label={t('myNdla.newFolder')}
175
165
  onClick={() => {
176
- const idPaths = getIdPathsOfFolder(data, markedFolderId || '');
177
- setNewFolder({ idPaths, parentId: paths[paths.length - 1] });
166
+ setNewFolderParentId(markedFolderId);
178
167
  }}>
179
168
  {t('myNdla.newFolder')}
180
169
  </AddButton>
@@ -185,8 +174,4 @@ const TreeStructure = ({
185
174
  );
186
175
  };
187
176
 
188
- TreeStructure.defaultProps = {
189
- maximumLevelsOfFoldersAllowed: MAX_LEVEL_FOR_FOLDERS,
190
- };
191
-
192
177
  export default TreeStructure;
@@ -11,35 +11,29 @@ import React, { ReactNode } from 'react';
11
11
  export interface FolderStructureProps {
12
12
  id: string;
13
13
  name: string;
14
- isOpen?: boolean;
15
- data?: FolderStructureProps[];
14
+ subfolders: FolderStructureProps[];
16
15
  isFavorite?: boolean;
17
16
  status?: string;
18
17
  openAsDefault?: boolean;
19
- url?: string;
20
18
  icon?: ReactNode;
21
19
  }
22
20
 
23
- export interface NewFolderProps {
24
- parentId?: string;
25
- idPaths: number[];
26
- }
27
-
28
21
  interface CommonFolderProps {
29
- data: FolderStructureProps[];
30
22
  editable?: boolean;
31
23
  loading?: boolean;
32
24
  openOnFolderClick?: boolean;
25
+ onSelectFolder?: (id: string) => void;
33
26
  }
34
27
 
35
28
  export interface TreeStructureProps extends CommonFolderProps {
29
+ folders: FolderStructureProps[];
36
30
  framed?: boolean;
37
31
  label?: string;
38
32
  folderIdMarkedByDefault?: string;
39
- onNewFolder: (props: { value: string; parentId?: string; idPaths: number[] }) => Promise<string>;
33
+ onNewFolder: (name: string, parentId: string) => Promise<string>;
40
34
  defaultOpenFolders?: string[];
41
35
  folderChild?: FolderChildFuncType;
42
- maximumLevelsOfFoldersAllowed: number;
36
+ maximumLevelsOfFoldersAllowed?: number;
43
37
  }
44
38
 
45
39
  export type onCreateNewFolderProp = ({
@@ -56,18 +50,20 @@ export type SetFocusedFolderId = React.Dispatch<React.SetStateAction<string | un
56
50
  export type FolderChildFuncType = (id: string, tabIndex: number) => ReactNode;
57
51
 
58
52
  export interface FolderItemsProps extends CommonFolderProps {
59
- onToggleOpen: (id: string) => void;
60
- onSaveNewFolder: (value: string) => void;
53
+ folders: FolderStructureProps[];
54
+ onCloseFolder: (id: string) => void;
55
+ onOpenFolder: (id: string) => void;
56
+ onSaveNewFolder: (name: string, parentId: string) => void;
61
57
  onCancelNewFolder: () => void;
62
- onCreateNewFolder: onCreateNewFolderProp;
63
- newFolder: NewFolderProps | undefined;
58
+ onCreateNewFolder: (parentId: string) => void;
59
+ newFolderParentId: string | undefined;
60
+ visibleFolders: string[];
64
61
  openFolders: string[];
65
62
  markedFolderId?: string;
66
63
  onMarkFolder: (id: string) => void;
67
- idPaths: number[];
64
+ level: number;
68
65
  focusedFolderId: string | undefined;
69
66
  setFocusedFolderId: SetFocusedFolderId;
70
- firstLevel: boolean;
71
67
  keyNavigationFocusIsCreateFolderButton?: boolean;
72
68
  icon?: ReactNode;
73
69
  folderChild?: FolderChildFuncType;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) 2022-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 { KeyboardEvent } from 'react';
10
+
11
+ const navigateVertical = (
12
+ visibleFolders: string[],
13
+ folderId: string,
14
+ setFocusedFolderId: (id: string) => void,
15
+ direction: 1 | -1,
16
+ ) => {
17
+ const currentIndex = visibleFolders.findIndex((id) => id === folderId);
18
+ const target = visibleFolders[currentIndex + direction];
19
+ if (target !== undefined) {
20
+ setFocusedFolderId(target);
21
+ }
22
+ };
23
+
24
+ const arrowKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
25
+
26
+ export const arrowNavigation = (
27
+ e: KeyboardEvent<HTMLElement>,
28
+ id: string,
29
+ visibleFolders: string[],
30
+ setFocusedFolderId: (id: string) => void,
31
+ onOpen: (id: string) => void,
32
+ onClose: (id: string) => void,
33
+ ) => {
34
+ if (!arrowKeys.includes(e.key)) {
35
+ return;
36
+ }
37
+
38
+ e.preventDefault();
39
+ e.stopPropagation();
40
+
41
+ switch (e.key) {
42
+ case 'ArrowUp':
43
+ return navigateVertical(visibleFolders, id, setFocusedFolderId, -1);
44
+ case 'ArrowDown':
45
+ return navigateVertical(visibleFolders, id, setFocusedFolderId, 1);
46
+ case 'ArrowLeft':
47
+ return onClose(id);
48
+ case 'ArrowRight':
49
+ return onOpen(id);
50
+ default:
51
+ return;
52
+ }
53
+ };
@@ -1,12 +1,12 @@
1
1
  import { FolderStructureProps } from './TreeStructure.types';
2
2
 
3
- const getPathOfFolder = (data: FolderStructureProps[], findId: string): string[] => {
4
- const paths = (dataChildren: FolderStructureProps[], path: string[]): string[] => {
5
- for (const { id, data: dataChildrenSub } of dataChildren) {
3
+ export const getPathOfFolder = (data: FolderStructureProps[], findId: string): string[] => {
4
+ const paths = (folders: FolderStructureProps[], path: string[]): string[] => {
5
+ for (const { id, subfolders } of folders) {
6
6
  if (id === findId) {
7
7
  return [...path, id];
8
- } else if (dataChildrenSub?.length) {
9
- return paths(dataChildrenSub, [...path, id]);
8
+ } else if (subfolders?.length) {
9
+ return paths(subfolders, [...path, id]);
10
10
  }
11
11
  }
12
12
  return [];
@@ -14,33 +14,18 @@ const getPathOfFolder = (data: FolderStructureProps[], findId: string): string[]
14
14
  return paths(data, []);
15
15
  };
16
16
 
17
- const getIdPathsOfFolder = (data: FolderStructureProps[], findId: string): number[] => {
18
- let currentPath: number[] = [];
19
- const paths = (dataChildren: FolderStructureProps[], path: number[]) => {
20
- dataChildren.forEach(({ id, data: dataChildrenSub }, _index) => {
21
- if (id === findId) {
22
- currentPath = [...path, _index];
23
- } else if (dataChildrenSub?.length) {
24
- paths(dataChildrenSub, [...path, _index]);
25
- }
26
- });
27
- };
28
- paths(data, []);
29
- return currentPath;
30
- };
31
-
32
- const getFolderName = (data: FolderStructureProps[], findId: string | undefined): string | undefined => {
17
+ export const getFolderName = (data: FolderStructureProps[], findId: string | undefined): string | undefined => {
33
18
  if (!findId) {
34
19
  return undefined;
35
20
  }
36
21
  let folderName: string | undefined;
37
22
  const paths = (dataChildren: FolderStructureProps[]) => {
38
- dataChildren.some(({ id, name, data: dataChildrenSub }, _index) => {
23
+ dataChildren.some(({ id, name, subfolders }, _index) => {
39
24
  if (id === findId) {
40
25
  folderName = name;
41
26
  return true;
42
- } else if (dataChildrenSub?.length) {
43
- return paths(dataChildrenSub);
27
+ } else if (subfolders?.length) {
28
+ return paths(subfolders);
44
29
  }
45
30
  return false;
46
31
  });
@@ -49,4 +34,11 @@ const getFolderName = (data: FolderStructureProps[], findId: string | undefined)
49
34
  return folderName;
50
35
  };
51
36
 
52
- export { getPathOfFolder, getIdPathsOfFolder, getFolderName };
37
+ export const flattenFolders = (folders: FolderStructureProps[], openFolders?: string[]): FolderStructureProps[] => {
38
+ return folders.reduce((acc, { subfolders, id, ...rest }) => {
39
+ if (!subfolders || (openFolders && !openFolders.includes(id))) {
40
+ return acc.concat({ subfolders, id, ...rest });
41
+ }
42
+ return acc.concat({ subfolders, id, ...rest }, flattenFolders(subfolders, openFolders));
43
+ }, [] as FolderStructureProps[]);
44
+ };
package/src/index.ts CHANGED
@@ -109,8 +109,6 @@ export {
109
109
  } from './LearningPaths';
110
110
  export { Translation, TranslationLine, TranslationBox } from './Translation';
111
111
 
112
- export { default as Spinner } from './Spinner';
113
-
114
112
  export { default as SearchResultSleeve } from './Search/SearchResultSleeve';
115
113
 
116
114
  export { default as ContentTypeResult } from './Search/ContentTypeResult';
@@ -861,8 +861,10 @@ const messages = {
861
861
  open: 'Open menu',
862
862
  close: 'Close menu',
863
863
  },
864
+ cancel: 'Cancel',
864
865
  close: 'Close',
865
866
  title: 'Title',
867
+ save: 'Save',
866
868
  image: {
867
869
  altText: 'Alt-text',
868
870
  caption: 'Caption',
@@ -993,6 +995,7 @@ const messages = {
993
995
  folder: 'Folder',
994
996
  delete: 'Delete',
995
997
  edit: 'Edit',
998
+ missingName: 'Folder name required',
996
999
  },
997
1000
  confirmDeleteFolder: 'Are you sure you want to delete this folder? This process cannot be undone.',
998
1001
  confirmDeleteTag: 'Are you sure you want to delete this tag? This process cannot be undone.',
@@ -861,6 +861,8 @@ const messages = {
861
861
  },
862
862
  close: 'Lukk',
863
863
  title: 'Tittel',
864
+ cancel: 'Avbryt',
865
+ save: 'Lagre',
864
866
  image: {
865
867
  altText: 'Alt-tekst',
866
868
  caption: 'Bildetekst',
@@ -991,6 +993,7 @@ const messages = {
991
993
  folder: 'Mappe',
992
994
  delete: 'Slett',
993
995
  edit: 'Rediger',
996
+ missingName: 'Mappenavn er påkrevd',
994
997
  },
995
998
  confirmDeleteFolder: 'Er du sikker på at du vil slette mappen? Denne handlingen kan ikke angres.',
996
999
  confirmDeleteTag: 'Er du sikker på at du vil slette tag? Denne handlingen kan ikke angres.',
@@ -860,8 +860,10 @@ const messages = {
860
860
  open: 'Åpne meny',
861
861
  close: 'Lukk meny',
862
862
  },
863
+ cancel: 'Avbryt',
863
864
  close: 'Lukk',
864
865
  title: 'Tittel',
866
+ save: 'Lagre',
865
867
  image: {
866
868
  altText: 'Alt-tekst',
867
869
  caption: 'Bilettekst',
@@ -992,6 +994,7 @@ const messages = {
992
994
  folder: 'Mappe',
993
995
  delete: 'Slett',
994
996
  edit: 'Rediger',
997
+ missingName: 'Mappenavn er påkrevd',
995
998
  },
996
999
  confirmDeleteFolder: 'Er du sikker på at du vil slette mappa? Denne handlinga kan ikkje angres.',
997
1000
  confirmDeleteTag: 'Er du sikker på at du vil slette tag? Denne handlinga kan ikkje angres.',
@@ -859,8 +859,10 @@ const messages = {
859
859
  open: 'Åpne meny',
860
860
  close: 'Lukk meny',
861
861
  },
862
+ cancel: 'Avbryt',
862
863
  close: 'Lukk',
863
864
  title: 'Tittel',
865
+ save: 'Lagre',
864
866
  image: {
865
867
  altText: 'Alt-tekst',
866
868
  caption: 'Bilettekst',
@@ -991,6 +993,7 @@ const messages = {
991
993
  folder: 'Mappe',
992
994
  delete: 'Slett',
993
995
  edit: 'Rediger',
996
+ missingName: 'Mappenavn er påkrevd',
994
997
  },
995
998
  confirmDeleteFolder: 'Er du sikker på at du vil slette mappen? Denne handlingen kan ikke angres.',
996
999
  confirmDeleteTag: 'Er du sikker på at du vil slette tag? Denne handlingen kan ikke angres.',