@ndla/ui 13.2.2 → 15.1.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/Article/Article.js +22 -3
- package/es/Article/ArticleFavoritesButton.js +38 -0
- package/es/Article/index.js +2 -1
- package/es/Breadcrumb/ActionBreadcrumb.js +57 -0
- package/es/Breadcrumb/index.js +1 -0
- package/es/Footer/FooterAuth.js +15 -22
- package/es/InfoBlock/InfoBlock.js +55 -0
- package/es/InfoBlock/index.js +1 -0
- package/es/LearningPaths/LearningPathMenu.js +3 -4
- package/es/Masthead/MastheadAuthModal.js +2 -2
- package/es/MyNdla/Navigation/VerticalNavigation.js +51 -0
- package/es/MyNdla/Navigation/index.js +2 -0
- package/es/MyNdla/Resource/Folder.js +86 -0
- package/es/MyNdla/Resource/FolderInput.js +96 -0
- package/{lib/MyNdla/ResourceDash/ResourcesView.d.ts → es/MyNdla/Resource/index.js} +3 -3
- package/es/MyNdla/index.js +4 -4
- package/es/Resource/BlockResource.js +73 -0
- package/es/Resource/ListResource.js +66 -0
- package/es/Resource/index.js +10 -0
- package/es/Resource/resourceComponents.js +97 -0
- package/es/ResourceGroup/ResourceGroup.js +7 -5
- package/es/ResourceGroup/ResourceItem.js +28 -30
- package/es/ResourceGroup/ResourceList.js +18 -6
- package/es/Search/ActiveFilters.js +6 -7
- package/es/Search/ContentTypeResult.js +6 -8
- package/es/SearchTypeResult/ActiveFilters.js +6 -10
- package/es/SnackBar/SnackBar.js +117 -0
- package/es/SnackBar/index.js +9 -0
- package/es/TagSelector/SuggestionInput.js +240 -0
- package/es/TagSelector/Suggestions.js +93 -0
- package/es/TagSelector/TagSelector.js +137 -0
- package/es/TagSelector/index.js +9 -0
- package/es/TopicIntroductionList/TopicIntroduction.js +2 -4
- package/es/TopicIntroductionList/TopicShortcutItem.js +1 -3
- package/es/TreeStructure/FolderItem.js +130 -0
- package/es/TreeStructure/FolderItems.js +123 -0
- package/es/TreeStructure/FolderNameInput.js +112 -0
- package/es/TreeStructure/TreeStructure.js +254 -0
- package/es/TreeStructure/TreeStructure.types.js +0 -0
- package/es/TreeStructure/TreeStructureWrapper.js +13 -0
- package/es/TreeStructure/helperFunctions.js +92 -0
- package/es/TreeStructure/index.js +9 -0
- package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +182 -0
- package/es/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +0 -0
- package/es/User/AuthModal.js +15 -24
- package/es/User/UserInfo.js +70 -0
- package/es/User/apiTypes.js +0 -0
- package/es/User/index.js +2 -0
- package/es/User/parseUserObject.js +102 -0
- package/es/all.css +90 -0
- package/es/index.js +9 -3
- package/es/locale/messages-en.js +71 -4
- package/es/locale/messages-nb.js +70 -3
- package/es/locale/messages-nn.js +70 -3
- package/es/locale/messages-se.js +70 -3
- package/es/locale/messages-sma.js +70 -3
- package/lib/Article/Article.d.ts +3 -1
- package/lib/Article/Article.js +43 -23
- package/lib/Article/ArticleFavoritesButton.d.ts +15 -0
- package/lib/Article/ArticleFavoritesButton.js +56 -0
- package/lib/Article/index.d.ts +2 -1
- package/lib/Article/index.js +8 -0
- package/lib/Breadcrumb/ActionBreadcrumb.d.ts +16 -0
- package/lib/Breadcrumb/ActionBreadcrumb.js +72 -0
- package/lib/Breadcrumb/index.d.ts +1 -0
- package/lib/Breadcrumb/index.js +8 -0
- package/lib/Footer/FooterAuth.d.ts +1 -1
- package/lib/Footer/FooterAuth.js +17 -17
- package/lib/InfoBlock/InfoBlock.d.ts +8 -0
- package/lib/InfoBlock/InfoBlock.js +58 -0
- package/lib/InfoBlock/index.d.ts +1 -0
- package/lib/InfoBlock/index.js +13 -0
- package/lib/LearningPaths/LearningPathMenu.js +3 -4
- package/lib/Masthead/MastheadAuthModal.d.ts +3 -3
- package/lib/Masthead/MastheadAuthModal.js +3 -3
- package/lib/MyNdla/Navigation/VerticalNavigation.d.ts +10 -0
- package/lib/MyNdla/Navigation/VerticalNavigation.js +61 -0
- package/lib/MyNdla/Navigation/index.d.ts +2 -0
- package/lib/MyNdla/Navigation/index.js +15 -0
- package/lib/MyNdla/Resource/Folder.d.ts +20 -0
- package/lib/MyNdla/Resource/Folder.js +100 -0
- package/lib/MyNdla/Resource/FolderInput.d.ts +15 -0
- package/lib/MyNdla/Resource/FolderInput.js +116 -0
- package/lib/MyNdla/Resource/index.d.ts +10 -0
- package/lib/MyNdla/Resource/index.js +23 -0
- package/lib/MyNdla/index.d.ts +4 -4
- package/lib/MyNdla/index.js +13 -7
- package/lib/Resource/BlockResource.d.ts +20 -0
- package/lib/Resource/BlockResource.js +84 -0
- package/lib/Resource/ListResource.d.ts +20 -0
- package/lib/Resource/ListResource.js +78 -0
- package/lib/Resource/index.d.ts +11 -0
- package/lib/Resource/index.js +29 -0
- package/lib/Resource/resourceComponents.d.ts +24 -0
- package/lib/Resource/resourceComponents.js +106 -0
- package/lib/ResourceGroup/ResourceGroup.d.ts +2 -1
- package/lib/ResourceGroup/ResourceGroup.js +7 -5
- package/lib/ResourceGroup/ResourceItem.d.ts +5 -1
- package/lib/ResourceGroup/ResourceItem.js +29 -30
- package/lib/ResourceGroup/ResourceList.d.ts +3 -1
- package/lib/ResourceGroup/ResourceList.js +18 -6
- package/lib/Search/ActiveFilters.js +6 -7
- package/lib/Search/ContentTypeResult.js +6 -8
- package/lib/SearchTypeResult/ActiveFilters.js +6 -10
- package/lib/SnackBar/SnackBar.d.ts +23 -0
- package/lib/SnackBar/SnackBar.js +127 -0
- package/lib/SnackBar/index.d.ts +10 -0
- package/lib/SnackBar/index.js +15 -0
- package/lib/TagSelector/SuggestionInput.d.ts +19 -0
- package/lib/TagSelector/SuggestionInput.js +255 -0
- package/lib/TagSelector/Suggestions.d.ts +12 -0
- package/lib/TagSelector/Suggestions.js +96 -0
- package/lib/TagSelector/TagSelector.d.ts +16 -0
- package/lib/TagSelector/TagSelector.js +150 -0
- package/lib/TagSelector/index.d.ts +10 -0
- package/lib/TagSelector/index.js +19 -0
- package/lib/TopicIntroductionList/TopicIntroduction.js +2 -4
- package/lib/TopicIntroductionList/TopicShortcutItem.js +1 -3
- package/lib/TreeStructure/FolderItem.d.ts +27 -0
- package/lib/TreeStructure/FolderItem.js +140 -0
- package/lib/TreeStructure/FolderItems.d.ts +11 -0
- package/lib/TreeStructure/FolderItems.js +130 -0
- package/lib/TreeStructure/FolderNameInput.d.ts +15 -0
- package/lib/TreeStructure/FolderNameInput.js +125 -0
- package/lib/TreeStructure/TreeStructure.d.ts +12 -0
- package/lib/TreeStructure/TreeStructure.js +273 -0
- package/lib/TreeStructure/TreeStructure.types.d.ts +63 -0
- package/lib/TreeStructure/TreeStructure.types.js +1 -0
- package/lib/TreeStructure/TreeStructureWrapper.d.ts +12 -0
- package/lib/TreeStructure/TreeStructureWrapper.js +24 -0
- package/lib/TreeStructure/helperFunctions.d.ts +5 -0
- package/lib/TreeStructure/helperFunctions.js +103 -0
- package/lib/TreeStructure/index.d.ts +10 -0
- package/lib/TreeStructure/index.js +15 -0
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.d.ts +11 -0
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.js +186 -0
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.d.ts +26 -0
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.js +1 -0
- package/lib/User/AuthModal.d.ts +3 -3
- package/lib/User/AuthModal.js +16 -23
- package/lib/User/UserInfo.d.ts +13 -0
- package/lib/User/UserInfo.js +84 -0
- package/lib/User/apiTypes.d.ts +61 -0
- package/lib/User/apiTypes.js +1 -0
- package/lib/User/index.d.ts +4 -0
- package/lib/User/index.js +8 -0
- package/lib/User/parseUserObject.d.ts +32 -0
- package/lib/User/parseUserObject.js +105 -0
- package/lib/all.css +90 -0
- package/lib/index.d.ts +14 -3
- package/lib/index.js +83 -10
- package/lib/locale/messages-en.d.ts +67 -0
- package/lib/locale/messages-en.js +71 -4
- package/lib/locale/messages-nb.d.ts +67 -0
- package/lib/locale/messages-nb.js +70 -3
- package/lib/locale/messages-nn.d.ts +67 -0
- package/lib/locale/messages-nn.js +70 -3
- package/lib/locale/messages-se.d.ts +67 -0
- package/lib/locale/messages-se.js +70 -3
- package/lib/locale/messages-sma.d.ts +67 -0
- package/lib/locale/messages-sma.js +70 -3
- package/lib/types.d.ts +1 -1
- package/package.json +11 -11
- package/src/Article/Article.tsx +31 -0
- package/src/Article/ArticleFavoritesButton.tsx +40 -0
- package/src/Article/index.ts +2 -0
- package/src/Breadcrumb/ActionBreadcrumb.tsx +68 -0
- package/src/Breadcrumb/index.ts +2 -0
- package/src/Footer/FooterAuth.tsx +7 -9
- package/src/InfoBlock/InfoBlock.tsx +61 -0
- package/src/InfoBlock/index.ts +1 -0
- package/src/LearningPaths/LearningPathMenu.tsx +1 -1
- package/src/Masthead/MastheadAuthModal.tsx +4 -5
- package/src/MyNdla/Navigation/VerticalNavigation.tsx +93 -0
- package/src/MyNdla/Navigation/index.ts +2 -0
- package/src/MyNdla/Resource/Folder.tsx +145 -0
- package/src/MyNdla/Resource/FolderInput.tsx +104 -0
- package/src/MyNdla/Resource/index.ts +11 -0
- package/src/MyNdla/index.ts +4 -5
- package/src/Resource/BlockResource.tsx +101 -0
- package/src/Resource/ListResource.tsx +111 -0
- package/src/Resource/index.ts +12 -0
- package/src/Resource/resourceComponents.tsx +143 -0
- package/src/ResourceGroup/ResourceGroup.tsx +3 -0
- package/src/ResourceGroup/ResourceItem.tsx +20 -3
- package/src/ResourceGroup/ResourceList.tsx +16 -3
- package/src/Search/ActiveFilters.jsx +0 -1
- package/src/Search/ContentTypeResult.tsx +8 -9
- package/src/SearchTypeResult/ActiveFilters.tsx +1 -3
- package/src/SnackBar/SnackBar.tsx +183 -0
- package/src/SnackBar/index.ts +13 -0
- package/src/TagSelector/SuggestionInput.tsx +230 -0
- package/src/TagSelector/Suggestions.tsx +125 -0
- package/src/TagSelector/TagSelector.tsx +111 -0
- package/src/TagSelector/index.ts +13 -0
- package/src/TopicIntroductionList/TopicIntroduction.tsx +2 -2
- package/src/TopicIntroductionList/TopicShortcutItem.tsx +1 -5
- package/src/TreeStructure/FolderItem.tsx +160 -0
- package/src/TreeStructure/FolderItems.tsx +109 -0
- package/src/TreeStructure/FolderNameInput.tsx +109 -0
- package/src/TreeStructure/TreeStructure.tsx +184 -0
- package/src/TreeStructure/TreeStructure.types.ts +69 -0
- package/src/TreeStructure/TreeStructureWrapper.tsx +34 -0
- package/src/TreeStructure/helperFunctions.ts +52 -0
- package/src/TreeStructure/index.ts +11 -0
- package/src/TreeStructure/keyboardNavigation/keyboardNavigation.ts +161 -0
- package/src/TreeStructure/keyboardNavigation/keyboardNavigation.types.ts +28 -0
- package/src/User/AuthModal.tsx +5 -26
- package/src/User/UserInfo.tsx +80 -0
- package/src/User/__tests__/parseUserObject-test.ts +315 -0
- package/src/User/apiTypes.ts +74 -0
- package/src/User/index.ts +4 -0
- package/src/User/parseUserObject.ts +83 -0
- package/src/all.scss +2 -0
- package/src/index.ts +15 -4
- package/src/locale/messages-en.ts +65 -3
- package/src/locale/messages-nb.ts +64 -2
- package/src/locale/messages-nn.ts +64 -2
- package/src/locale/messages-se.ts +64 -2
- package/src/locale/messages-sma.ts +64 -2
- package/src/types.ts +1 -1
- package/es/MyNdla/ResourceDash/Breadcrumbs.js +0 -22
- package/es/MyNdla/ResourceDash/ResourceElement.js +0 -27
- package/es/MyNdla/ResourceDash/ResourcesView.js +0 -43
- package/es/MyNdla/ResourceDash/index.js +0 -4
- package/lib/MyNdla/ResourceDash/Breadcrumbs.d.ts +0 -15
- package/lib/MyNdla/ResourceDash/Breadcrumbs.js +0 -35
- package/lib/MyNdla/ResourceDash/ResourceElement.d.ts +0 -18
- package/lib/MyNdla/ResourceDash/ResourceElement.js +0 -38
- package/lib/MyNdla/ResourceDash/ResourcesView.js +0 -57
- package/lib/MyNdla/ResourceDash/index.d.ts +0 -4
- package/lib/MyNdla/ResourceDash/index.js +0 -31
- package/src/MyNdla/ResourceDash/Breadcrumbs.tsx +0 -31
- package/src/MyNdla/ResourceDash/ResourceElement.tsx +0 -50
- package/src/MyNdla/ResourceDash/ResourcesView.tsx +0 -42
- package/src/MyNdla/ResourceDash/index.ts +0 -5
|
@@ -0,0 +1,109 @@
|
|
|
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 React from 'react';
|
|
10
|
+
import styled from '@emotion/styled';
|
|
11
|
+
import { animations, spacing } from '@ndla/core';
|
|
12
|
+
import FolderItem from './FolderItem';
|
|
13
|
+
import FolderNameInput from './FolderNameInput';
|
|
14
|
+
import { FolderItemsProps } from './TreeStructure.types';
|
|
15
|
+
import { MAX_LEVEL_FOR_FOLDERS } from './TreeStructure';
|
|
16
|
+
|
|
17
|
+
const StyledUL = styled.ul<{ firstLevel?: boolean }>`
|
|
18
|
+
${animations.fadeInLeft(animations.durations.fast)};
|
|
19
|
+
animation-fill-mode: forwards;
|
|
20
|
+
@media (prefers-reduced-motion: reduce) {
|
|
21
|
+
animation: none;
|
|
22
|
+
}
|
|
23
|
+
list-style: none;
|
|
24
|
+
margin: 0;
|
|
25
|
+
padding: 0;
|
|
26
|
+
margin-left: ${({ firstLevel }) => (firstLevel ? `-${spacing.xsmall}` : spacing.normal)};
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const StyledLI = styled.li`
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const FolderItems = ({
|
|
35
|
+
loading,
|
|
36
|
+
data,
|
|
37
|
+
idPaths,
|
|
38
|
+
editable,
|
|
39
|
+
onToggleOpen,
|
|
40
|
+
onCreateNewFolder,
|
|
41
|
+
onCancelNewFolder,
|
|
42
|
+
onSaveNewFolder,
|
|
43
|
+
newFolder,
|
|
44
|
+
openFolders,
|
|
45
|
+
markedFolderId,
|
|
46
|
+
onMarkFolder,
|
|
47
|
+
openOnFolderClick,
|
|
48
|
+
focusedFolderId,
|
|
49
|
+
setFocusedFolderId,
|
|
50
|
+
firstLevel,
|
|
51
|
+
}: FolderItemsProps) => (
|
|
52
|
+
<StyledUL role="group" firstLevel={firstLevel}>
|
|
53
|
+
{data.map(({ name, data: dataChildren, id, url, icon }, _index) => {
|
|
54
|
+
const newIdPaths = [...idPaths, _index];
|
|
55
|
+
const isOpen = openFolders?.has(id);
|
|
56
|
+
return (
|
|
57
|
+
<StyledLI key={id} role="treeitem">
|
|
58
|
+
<div>
|
|
59
|
+
<FolderItem
|
|
60
|
+
icon={icon}
|
|
61
|
+
url={url}
|
|
62
|
+
openOnFolderClick={openOnFolderClick}
|
|
63
|
+
loading={loading}
|
|
64
|
+
isOpen={isOpen}
|
|
65
|
+
id={id}
|
|
66
|
+
name={name}
|
|
67
|
+
markedFolderId={markedFolderId}
|
|
68
|
+
focusedFolderId={focusedFolderId}
|
|
69
|
+
onToggleOpen={onToggleOpen}
|
|
70
|
+
onMarkFolder={onMarkFolder}
|
|
71
|
+
hideArrow={dataChildren?.length === 0 || newIdPaths.length >= MAX_LEVEL_FOR_FOLDERS}
|
|
72
|
+
noPaddingWhenArrowIsHidden={editable && firstLevel && dataChildren?.length === 0}
|
|
73
|
+
setFocusedFolderId={setFocusedFolderId}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
{newFolder?.parentId === id && (
|
|
77
|
+
<FolderNameInput
|
|
78
|
+
loading={loading}
|
|
79
|
+
onCancelNewFolder={onCancelNewFolder}
|
|
80
|
+
onSaveNewFolder={onSaveNewFolder}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
{dataChildren && isOpen && (
|
|
84
|
+
<FolderItems
|
|
85
|
+
loading={loading}
|
|
86
|
+
newFolder={newFolder}
|
|
87
|
+
openFolders={openFolders}
|
|
88
|
+
idPaths={newIdPaths}
|
|
89
|
+
editable={editable}
|
|
90
|
+
data={dataChildren}
|
|
91
|
+
onToggleOpen={onToggleOpen}
|
|
92
|
+
onCreateNewFolder={onCreateNewFolder}
|
|
93
|
+
onSaveNewFolder={onSaveNewFolder}
|
|
94
|
+
onCancelNewFolder={onCancelNewFolder}
|
|
95
|
+
markedFolderId={markedFolderId}
|
|
96
|
+
onMarkFolder={onMarkFolder}
|
|
97
|
+
openOnFolderClick={openOnFolderClick}
|
|
98
|
+
focusedFolderId={focusedFolderId}
|
|
99
|
+
setFocusedFolderId={setFocusedFolderId}
|
|
100
|
+
firstLevel={false}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
</StyledLI>
|
|
104
|
+
);
|
|
105
|
+
})}
|
|
106
|
+
</StyledUL>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
export default FolderItems;
|
|
@@ -0,0 +1,109 @@
|
|
|
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 React, { useEffect, useState, useRef } from 'react';
|
|
10
|
+
import styled from '@emotion/styled';
|
|
11
|
+
import { FolderOutlined } from '@ndla/icons/contentType';
|
|
12
|
+
import { ArrowDropDown as ArrowDropDownRaw } from '@ndla/icons/common';
|
|
13
|
+
import { Spinner } from '@ndla/editor';
|
|
14
|
+
import { spacing, colors, misc, animations } from '@ndla/core';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import { isMobile } from 'react-device-detect';
|
|
17
|
+
|
|
18
|
+
const ArrowRight = styled(ArrowDropDownRaw)`
|
|
19
|
+
color: ${colors.text.primary};
|
|
20
|
+
transform: rotate(-90deg);
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const NewFolderWrapper = styled.div`
|
|
24
|
+
padding-left: ${spacing.normal};
|
|
25
|
+
${animations.fadeInLeft(animations.durations.fast)};
|
|
26
|
+
animation-fill-mode: forwards;
|
|
27
|
+
@media (prefers-reduced-motion: reduce) {
|
|
28
|
+
animation: none;
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const InputWrapper = styled.div<{ loading?: boolean }>`
|
|
33
|
+
margin: ${spacing.xxsmall} ${spacing.small} ${spacing.xxsmall} 0;
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
border: 1px solid ${({ loading }) => (loading ? colors.brand.lighter : colors.brand.primary)};
|
|
37
|
+
border-style: dashed;
|
|
38
|
+
border-radius: ${misc.borderRadius};
|
|
39
|
+
padding-right: ${spacing.normal};
|
|
40
|
+
padding-left: ${spacing.xsmall};
|
|
41
|
+
color: ${colors.brand.primary};
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const StyledInput = styled.input`
|
|
45
|
+
flex-grow: 1;
|
|
46
|
+
border: 0;
|
|
47
|
+
outline: none;
|
|
48
|
+
padding: ${spacing.small};
|
|
49
|
+
padding-left: ${spacing.xsmall};
|
|
50
|
+
background: transparent;
|
|
51
|
+
color: ${colors.text.primary};
|
|
52
|
+
scroll-margin-top: 100px;
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
interface FolderNameInputProps {
|
|
56
|
+
onSaveNewFolder: (value: string) => void;
|
|
57
|
+
onCancelNewFolder: () => void;
|
|
58
|
+
loading?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const FolderNameInput = ({ onSaveNewFolder, onCancelNewFolder, loading }: FolderNameInputProps) => {
|
|
62
|
+
const { t } = useTranslation();
|
|
63
|
+
const [value, setValue] = useState<string>(t('treeStructure.newFolder.defaultName'));
|
|
64
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (inputRef.current) {
|
|
68
|
+
inputRef.current.select();
|
|
69
|
+
if (isMobile) {
|
|
70
|
+
inputRef.current.scrollIntoView({ behavior: 'smooth' });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<NewFolderWrapper>
|
|
77
|
+
<InputWrapper loading={loading}>
|
|
78
|
+
<ArrowRight />
|
|
79
|
+
<FolderOutlined />
|
|
80
|
+
<StyledInput
|
|
81
|
+
ref={inputRef}
|
|
82
|
+
autoFocus
|
|
83
|
+
placeholder={t('treeStructure.newFolder.placeholder')}
|
|
84
|
+
disabled={loading}
|
|
85
|
+
value={value}
|
|
86
|
+
onBlur={() => onCancelNewFolder()}
|
|
87
|
+
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
88
|
+
if (e.key === 'Escape') {
|
|
89
|
+
onCancelNewFolder();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (e.key === 'Enter' || e.key === 'Tab') {
|
|
93
|
+
onSaveNewFolder(value);
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}}
|
|
98
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
99
|
+
const target = e.target as HTMLInputElement;
|
|
100
|
+
setValue(target.value);
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
{loading && <Spinner size="small" />}
|
|
104
|
+
</InputWrapper>
|
|
105
|
+
</NewFolderWrapper>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default FolderNameInput;
|
|
@@ -0,0 +1,184 @@
|
|
|
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 React, { useEffect, useState, useRef, useMemo } from 'react';
|
|
10
|
+
import { uuid } from '@ndla/util';
|
|
11
|
+
import Button from '@ndla/button';
|
|
12
|
+
import Tooltip from '@ndla/tooltip';
|
|
13
|
+
import { useTranslation } from 'react-i18next';
|
|
14
|
+
import styled from '@emotion/styled';
|
|
15
|
+
import { spacing, fonts } from '@ndla/core';
|
|
16
|
+
import TreeStructureStyledWrapper from './TreeStructureWrapper';
|
|
17
|
+
import FolderItems from './FolderItems';
|
|
18
|
+
import { getIdPathsOfFolder, getPathOfFolder, getFolderName } from './helperFunctions';
|
|
19
|
+
import keyboardNavigation, { KEYBOARD_KEYS_OF_INTEREST } from './keyboardNavigation/keyboardNavigation';
|
|
20
|
+
import { NewFolderProps, TreeStructureProps } from './TreeStructure.types';
|
|
21
|
+
|
|
22
|
+
export const MAX_LEVEL_FOR_FOLDERS = 4;
|
|
23
|
+
|
|
24
|
+
const StyledLabel = styled.label`
|
|
25
|
+
font-weight: ${fonts.weight.semibold};
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const AddFolderWrapper = styled.div`
|
|
29
|
+
display: flex;
|
|
30
|
+
margin-top: ${spacing.xsmall};
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const TreeStructure = ({
|
|
34
|
+
data,
|
|
35
|
+
label,
|
|
36
|
+
editable,
|
|
37
|
+
loading,
|
|
38
|
+
onNewFolder,
|
|
39
|
+
openOnFolderClick,
|
|
40
|
+
framed,
|
|
41
|
+
folderIdMarkedByDefault,
|
|
42
|
+
defaultOpenFolders,
|
|
43
|
+
}: TreeStructureProps) => {
|
|
44
|
+
const { t } = useTranslation();
|
|
45
|
+
const [newFolder, setNewFolder] = useState<NewFolderProps | undefined>();
|
|
46
|
+
const [openFolders, setOpenFolders] = useState<Set<string>>(new Set(defaultOpenFolders || []));
|
|
47
|
+
const [focusedFolderId, setFocusedFolderId] = useState<string | undefined>();
|
|
48
|
+
const [markedFolderId, setMarkedFolderId] = useState<string | undefined>(folderIdMarkedByDefault || data[0].id);
|
|
49
|
+
const treestructureRef = useRef<HTMLDivElement>(null);
|
|
50
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
51
|
+
const rootLevelId = useMemo(() => uuid(), []); // TODO: use useId hook when we update to React 18
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
setOpenFolders((prev) => {
|
|
55
|
+
defaultOpenFolders?.forEach((id) => prev.add(id));
|
|
56
|
+
return new Set(prev);
|
|
57
|
+
});
|
|
58
|
+
}, [defaultOpenFolders]);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!loading) {
|
|
62
|
+
setNewFolder(undefined);
|
|
63
|
+
}
|
|
64
|
+
}, [loading]);
|
|
65
|
+
|
|
66
|
+
const onToggleOpen = (id: string) => {
|
|
67
|
+
setOpenFolders((prev) => {
|
|
68
|
+
if (prev.has(id)) {
|
|
69
|
+
prev.delete(id);
|
|
70
|
+
// Did we just closed a folder with a marked folder inside it?
|
|
71
|
+
// If so, we need to mark the folder we just closed.
|
|
72
|
+
if (markedFolderId) {
|
|
73
|
+
const closingFolderPath = getPathOfFolder(data, id);
|
|
74
|
+
const markedFolderPath = getPathOfFolder(data, markedFolderId);
|
|
75
|
+
const markedFolderIsSubPath = closingFolderPath.every(
|
|
76
|
+
(folderId, _index) => markedFolderPath[_index] === folderId,
|
|
77
|
+
);
|
|
78
|
+
if (markedFolderIsSubPath) {
|
|
79
|
+
setMarkedFolderId(closingFolderPath[closingFolderPath.length - 1]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
prev.add(id);
|
|
84
|
+
}
|
|
85
|
+
return new Set(prev);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const onCreateNewFolder = (props: { idPaths: number[]; parentId?: string }) => {
|
|
90
|
+
setNewFolder(props);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const onSaveNewFolder = async (value: string) => {
|
|
94
|
+
if (newFolder) {
|
|
95
|
+
// We would like to create a new folder with the name of value.
|
|
96
|
+
// Its location in structure is based on newFolder object
|
|
97
|
+
const newFolderId = await onNewFolder({ ...newFolder, value });
|
|
98
|
+
if (newFolderId) {
|
|
99
|
+
setMarkedFolderId(newFolderId);
|
|
100
|
+
setFocusedFolderId(newFolderId);
|
|
101
|
+
// Open current folder in case it was closed..
|
|
102
|
+
setOpenFolders((prev) => {
|
|
103
|
+
if (newFolder.parentId) {
|
|
104
|
+
prev.add(newFolder.parentId);
|
|
105
|
+
}
|
|
106
|
+
return new Set(prev);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const onCancelNewFolder = () => {
|
|
113
|
+
setNewFolder(undefined);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const onMarkFolder = (id: string) => {
|
|
117
|
+
setMarkedFolderId(id);
|
|
118
|
+
setFocusedFolderId(id);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const disableAddFolderButton =
|
|
122
|
+
markedFolderId === undefined || getPathOfFolder(data, markedFolderId).length >= MAX_LEVEL_FOR_FOLDERS;
|
|
123
|
+
|
|
124
|
+
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
|
+
}}>
|
|
139
|
+
<StyledLabel htmlFor={rootLevelId}>{label}</StyledLabel>
|
|
140
|
+
<TreeStructureStyledWrapper ref={wrapperRef} id={rootLevelId} aria-label="Menu tree" role="tree" framed={framed}>
|
|
141
|
+
<FolderItems
|
|
142
|
+
idPaths={[]}
|
|
143
|
+
data={data}
|
|
144
|
+
editable={editable}
|
|
145
|
+
onToggleOpen={onToggleOpen}
|
|
146
|
+
newFolder={newFolder}
|
|
147
|
+
onCreateNewFolder={onCreateNewFolder}
|
|
148
|
+
onCancelNewFolder={onCancelNewFolder}
|
|
149
|
+
onSaveNewFolder={onSaveNewFolder}
|
|
150
|
+
openFolders={openFolders}
|
|
151
|
+
markedFolderId={markedFolderId}
|
|
152
|
+
onMarkFolder={onMarkFolder}
|
|
153
|
+
openOnFolderClick={openOnFolderClick}
|
|
154
|
+
loading={loading}
|
|
155
|
+
focusedFolderId={focusedFolderId}
|
|
156
|
+
setFocusedFolderId={setFocusedFolderId}
|
|
157
|
+
firstLevel
|
|
158
|
+
/>
|
|
159
|
+
</TreeStructureStyledWrapper>
|
|
160
|
+
{editable && (
|
|
161
|
+
<AddFolderWrapper>
|
|
162
|
+
<Tooltip
|
|
163
|
+
tooltip={t('myNdla.newFolderUnder', {
|
|
164
|
+
folderName: getFolderName(data, markedFolderId),
|
|
165
|
+
})}>
|
|
166
|
+
<Button
|
|
167
|
+
size="small"
|
|
168
|
+
light
|
|
169
|
+
disabled={disableAddFolderButton}
|
|
170
|
+
onClick={() => {
|
|
171
|
+
const paths = getPathOfFolder(data, markedFolderId || '');
|
|
172
|
+
const idPaths = getIdPathsOfFolder(data, markedFolderId || '');
|
|
173
|
+
setNewFolder({ idPaths, parentId: paths[paths.length - 1] });
|
|
174
|
+
}}>
|
|
175
|
+
{t('myNdla.newFolder')}
|
|
176
|
+
</Button>
|
|
177
|
+
</Tooltip>
|
|
178
|
+
</AddFolderWrapper>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export default TreeStructure;
|
|
@@ -0,0 +1,69 @@
|
|
|
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 React from 'react';
|
|
10
|
+
|
|
11
|
+
export interface FolderStructureProps {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
isOpen?: boolean;
|
|
15
|
+
data?: FolderStructureProps[];
|
|
16
|
+
isFavorite?: boolean;
|
|
17
|
+
status?: string;
|
|
18
|
+
openAsDefault?: boolean;
|
|
19
|
+
url?: string;
|
|
20
|
+
icon?: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface NewFolderProps {
|
|
24
|
+
parentId?: string;
|
|
25
|
+
idPaths: number[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface CommonFolderProps {
|
|
29
|
+
data: FolderStructureProps[];
|
|
30
|
+
editable?: boolean;
|
|
31
|
+
loading?: boolean;
|
|
32
|
+
openOnFolderClick?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TreeStructureProps extends CommonFolderProps {
|
|
36
|
+
framed?: boolean;
|
|
37
|
+
label: string;
|
|
38
|
+
folderIdMarkedByDefault?: string;
|
|
39
|
+
onNewFolder: (props: { value: string; parentId?: string; idPaths: number[] }) => Promise<string>;
|
|
40
|
+
defaultOpenFolders?: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type onCreateNewFolderProp = ({
|
|
44
|
+
idPaths,
|
|
45
|
+
parentId,
|
|
46
|
+
}: {
|
|
47
|
+
idPaths: number[];
|
|
48
|
+
parentId: string | undefined;
|
|
49
|
+
}) => void;
|
|
50
|
+
|
|
51
|
+
export type SetOpenFolderProp = React.Dispatch<React.SetStateAction<Set<string>>>;
|
|
52
|
+
export type SetFocusedFolderId = React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
53
|
+
|
|
54
|
+
export interface FolderItemsProps extends CommonFolderProps {
|
|
55
|
+
onToggleOpen: (id: string) => void;
|
|
56
|
+
onSaveNewFolder: (value: string) => void;
|
|
57
|
+
onCancelNewFolder: () => void;
|
|
58
|
+
onCreateNewFolder: onCreateNewFolderProp;
|
|
59
|
+
newFolder: NewFolderProps | undefined;
|
|
60
|
+
openFolders: Set<string>;
|
|
61
|
+
markedFolderId?: string;
|
|
62
|
+
onMarkFolder: (id: string) => void;
|
|
63
|
+
idPaths: number[];
|
|
64
|
+
focusedFolderId: string | undefined;
|
|
65
|
+
setFocusedFolderId: SetFocusedFolderId;
|
|
66
|
+
firstLevel: boolean;
|
|
67
|
+
keyNavigationFocusIsCreateFolderButton?: boolean;
|
|
68
|
+
icon?: React.ReactElement;
|
|
69
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 styled from '@emotion/styled';
|
|
10
|
+
import { css } from '@emotion/core';
|
|
11
|
+
import { colors, misc, spacing } from '@ndla/core';
|
|
12
|
+
|
|
13
|
+
const TreeStructureWrapper = styled.div<{ framed?: boolean }>`
|
|
14
|
+
padding: ${spacing.xsmall};
|
|
15
|
+
${({ framed }) =>
|
|
16
|
+
framed
|
|
17
|
+
? css`
|
|
18
|
+
border: 1px solid ${colors.brand.greyLighter};
|
|
19
|
+
border-radius: ${misc.borderRadius};
|
|
20
|
+
max-height: 400px;
|
|
21
|
+
overflow-y: scroll;
|
|
22
|
+
scroll-behavior: smooth;
|
|
23
|
+
padding: ${spacing.small};
|
|
24
|
+
`
|
|
25
|
+
: css`
|
|
26
|
+
margin-left: -${spacing.medium};
|
|
27
|
+
`}
|
|
28
|
+
transition: ${misc.transition.default};
|
|
29
|
+
&:focus-within {
|
|
30
|
+
border-color: ${colors.brand.primary};
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
export default TreeStructureWrapper;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FolderStructureProps } from './TreeStructure.types';
|
|
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) {
|
|
6
|
+
if (id === findId) {
|
|
7
|
+
return [...path, id];
|
|
8
|
+
} else if (dataChildrenSub?.length) {
|
|
9
|
+
return paths(dataChildrenSub, [...path, id]);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return [];
|
|
13
|
+
};
|
|
14
|
+
return paths(data, []);
|
|
15
|
+
};
|
|
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 => {
|
|
33
|
+
if (!findId) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
let folderName: string | undefined;
|
|
37
|
+
const paths = (dataChildren: FolderStructureProps[]) => {
|
|
38
|
+
dataChildren.some(({ id, name, data: dataChildrenSub }, _index) => {
|
|
39
|
+
if (id === findId) {
|
|
40
|
+
folderName = name;
|
|
41
|
+
return true;
|
|
42
|
+
} else if (dataChildrenSub?.length) {
|
|
43
|
+
return paths(dataChildrenSub);
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
paths(data);
|
|
49
|
+
return folderName;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export { getPathOfFolder, getIdPathsOfFolder, getFolderName };
|
|
@@ -0,0 +1,11 @@
|
|
|
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 TreeStructure from './TreeStructure';
|
|
10
|
+
export type { FolderStructureProps, TreeStructureProps } from './TreeStructure.types';
|
|
11
|
+
export { TreeStructure };
|