@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.
- package/es/MyNdla/Resource/FolderInput.js +29 -36
- package/es/NDLAFilm/FilmSlideshow.js +8 -8
- package/es/Search/LoadingWrapper.js +2 -2
- package/es/Search/SearchResult.js +1 -1
- package/es/SearchTypeResult/SearchTypeResult.js +3 -3
- package/es/TreeStructure/FolderItem.js +54 -38
- package/es/TreeStructure/FolderItems.js +29 -35
- package/es/TreeStructure/FolderNameInput.js +12 -16
- package/es/TreeStructure/TreeStructure.js +64 -91
- package/es/TreeStructure/arrowNavigation.js +44 -0
- package/es/TreeStructure/helperFunctions.js +41 -35
- package/es/index.js +0 -1
- package/es/locale/messages-en.js +4 -1
- package/es/locale/messages-nb.js +4 -1
- package/es/locale/messages-nn.js +4 -1
- package/es/locale/messages-se.js +4 -1
- package/es/locale/messages-sma.js +4 -1
- package/lib/MyNdla/Resource/FolderInput.d.ts +2 -2
- package/lib/MyNdla/Resource/FolderInput.js +26 -33
- package/lib/NDLAFilm/FilmSlideshow.js +10 -10
- package/lib/Search/LoadingWrapper.js +3 -3
- package/lib/Search/SearchResult.js +2 -2
- package/lib/SearchTypeResult/SearchTypeResult.js +4 -4
- package/lib/TreeStructure/FolderItem.d.ts +6 -3
- package/lib/TreeStructure/FolderItem.js +55 -38
- package/lib/TreeStructure/FolderItems.d.ts +1 -1
- package/lib/TreeStructure/FolderItems.js +29 -35
- package/lib/TreeStructure/FolderNameInput.d.ts +3 -2
- package/lib/TreeStructure/FolderNameInput.js +13 -17
- package/lib/TreeStructure/TreeStructure.d.ts +1 -6
- package/lib/TreeStructure/TreeStructure.js +63 -92
- package/lib/TreeStructure/TreeStructure.types.d.ts +13 -20
- package/lib/TreeStructure/arrowNavigation.d.ts +9 -0
- package/lib/TreeStructure/arrowNavigation.js +54 -0
- package/lib/TreeStructure/helperFunctions.d.ts +3 -4
- package/lib/TreeStructure/helperFunctions.js +45 -35
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -9
- package/lib/locale/messages-en.d.ts +3 -0
- package/lib/locale/messages-en.js +4 -1
- package/lib/locale/messages-nb.d.ts +3 -0
- package/lib/locale/messages-nb.js +4 -1
- package/lib/locale/messages-nn.d.ts +3 -0
- package/lib/locale/messages-nn.js +4 -1
- package/lib/locale/messages-se.d.ts +3 -0
- package/lib/locale/messages-se.js +4 -1
- package/lib/locale/messages-sma.d.ts +3 -0
- package/lib/locale/messages-sma.js +4 -1
- package/package.json +11 -10
- package/src/MyNdla/Resource/FolderInput.tsx +41 -44
- package/src/NDLAFilm/FilmSlideshow.tsx +1 -1
- package/src/Search/LoadingWrapper.tsx +1 -1
- package/src/Search/SearchResult.jsx +1 -1
- package/src/SearchTypeResult/SearchTypeResult.tsx +1 -1
- package/src/TreeStructure/FolderItem.tsx +63 -40
- package/src/TreeStructure/FolderItems.tsx +26 -19
- package/src/TreeStructure/FolderNameInput.tsx +10 -12
- package/src/TreeStructure/TreeStructure.tsx +56 -71
- package/src/TreeStructure/TreeStructure.types.ts +13 -17
- package/src/TreeStructure/arrowNavigation.ts +53 -0
- package/src/TreeStructure/helperFunctions.ts +17 -25
- package/src/index.ts +0 -2
- package/src/locale/messages-en.ts +3 -0
- package/src/locale/messages-nb.ts +3 -0
- package/src/locale/messages-nn.ts +3 -0
- package/src/locale/messages-se.ts +3 -0
- package/src/locale/messages-sma.ts +3 -0
- package/es/Spinner/Spinner.js +0 -42
- package/es/Spinner/index.js +0 -2
- package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +0 -194
- package/es/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +0 -0
- package/lib/Spinner/Spinner.d.ts +0 -16
- package/lib/Spinner/Spinner.js +0 -54
- package/lib/Spinner/index.d.ts +0 -2
- package/lib/Spinner/index.js +0 -13
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.d.ts +0 -11
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.js +0 -198
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.d.ts +0 -26
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +0 -1
- package/src/Spinner/Spinner.tsx +0 -46
- package/src/Spinner/index.ts +0 -3
- package/src/TreeStructure/keyboardNavigation/keyboardNavigation.ts +0 -161
- 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
|
-
|
|
36
|
-
|
|
35
|
+
folders,
|
|
36
|
+
level,
|
|
37
37
|
editable,
|
|
38
|
-
|
|
38
|
+
onSelectFolder,
|
|
39
|
+
onCloseFolder,
|
|
40
|
+
onOpenFolder,
|
|
39
41
|
onCreateNewFolder,
|
|
40
42
|
onCancelNewFolder,
|
|
41
43
|
onSaveNewFolder,
|
|
42
|
-
|
|
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={
|
|
54
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
90
|
+
{subfolders && isOpen && (
|
|
86
91
|
<FolderItems
|
|
92
|
+
onSelectFolder={onSelectFolder}
|
|
87
93
|
loading={loading}
|
|
88
|
-
|
|
94
|
+
newFolderParentId={newFolderParentId}
|
|
95
|
+
visibleFolders={visibleFolders}
|
|
89
96
|
openFolders={openFolders}
|
|
90
|
-
|
|
97
|
+
level={level + 1}
|
|
91
98
|
editable={editable}
|
|
92
|
-
|
|
93
|
-
|
|
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 '
|
|
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: (
|
|
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 [
|
|
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={
|
|
86
|
+
value={name}
|
|
86
87
|
onBlur={() => onCancelNewFolder()}
|
|
87
88
|
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
88
89
|
if (e.key === 'Escape') {
|
|
89
90
|
onCancelNewFolder();
|
|
90
|
-
|
|
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
|
|
100
|
-
|
|
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 {
|
|
20
|
-
import
|
|
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
|
-
|
|
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 [
|
|
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 ||
|
|
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 =
|
|
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
|
-
|
|
70
|
+
setNewFolderParentId(undefined);
|
|
67
71
|
}
|
|
68
72
|
}, [loading]);
|
|
69
73
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
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
|
|
91
|
-
|
|
95
|
+
const onOpenFolder = (id: string) => {
|
|
96
|
+
setOpenFolders(uniq(openFolders.concat(id)));
|
|
92
97
|
};
|
|
93
98
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
143
|
-
|
|
130
|
+
onSelectFolder={onSelectFolder}
|
|
131
|
+
level={1}
|
|
132
|
+
folders={folders}
|
|
144
133
|
editable={editable}
|
|
145
|
-
|
|
146
|
-
|
|
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(
|
|
158
|
+
folderName: getFolderName(folders, markedFolderId),
|
|
169
159
|
})
|
|
170
|
-
: t('
|
|
160
|
+
: t('treeStructure.maxFoldersAlreadyAdded')
|
|
171
161
|
}>
|
|
172
162
|
<AddButton
|
|
173
163
|
disabled={!canAddFolder}
|
|
174
164
|
aria-label={t('myNdla.newFolder')}
|
|
175
165
|
onClick={() => {
|
|
176
|
-
|
|
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
|
-
|
|
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: (
|
|
33
|
+
onNewFolder: (name: string, parentId: string) => Promise<string>;
|
|
40
34
|
defaultOpenFolders?: string[];
|
|
41
35
|
folderChild?: FolderChildFuncType;
|
|
42
|
-
maximumLevelsOfFoldersAllowed
|
|
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
|
-
|
|
60
|
-
|
|
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:
|
|
63
|
-
|
|
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
|
-
|
|
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 = (
|
|
5
|
-
for (const { id,
|
|
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 (
|
|
9
|
-
return paths(
|
|
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
|
|
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,
|
|
23
|
+
dataChildren.some(({ id, name, subfolders }, _index) => {
|
|
39
24
|
if (id === findId) {
|
|
40
25
|
folderName = name;
|
|
41
26
|
return true;
|
|
42
|
-
} else if (
|
|
43
|
-
return paths(
|
|
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
|
|
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.',
|