@ndla/ui 17.0.0 → 19.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/Article/ArticleFavoritesButton.js +4 -3
- package/es/Masthead/MastheadAuthModal.js +8 -3
- package/es/MyNdla/Resource/Folder.js +11 -10
- package/es/MyNdla/index.js +1 -2
- package/es/Resource/BlockResource.js +14 -8
- package/es/Resource/ListResource.js +15 -9
- package/es/Resource/resourceComponents.js +12 -11
- package/es/TagSelector/SuggestionInput.js +111 -56
- package/es/TagSelector/Suggestions.js +19 -15
- package/es/TagSelector/TagSelector.js +8 -7
- package/es/TreeStructure/FolderItem.js +5 -5
- package/es/TreeStructure/FolderItems.js +8 -7
- package/es/TreeStructure/TreeStructure.js +65 -80
- package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +23 -11
- package/es/index.js +1 -1
- package/es/locale/messages-en.js +10 -1
- package/es/locale/messages-nb.js +11 -2
- package/es/locale/messages-nn.js +12 -3
- package/es/locale/messages-se.js +11 -2
- package/es/locale/messages-sma.js +11 -2
- package/lib/Article/ArticleFavoritesButton.js +4 -3
- package/lib/Masthead/MastheadAuthModal.js +14 -7
- package/lib/MyNdla/Resource/Folder.d.ts +4 -3
- package/lib/MyNdla/Resource/Folder.js +11 -10
- package/lib/MyNdla/index.d.ts +1 -2
- package/lib/MyNdla/index.js +0 -8
- package/lib/Resource/BlockResource.d.ts +4 -3
- package/lib/Resource/BlockResource.js +14 -7
- package/lib/Resource/ListResource.d.ts +4 -3
- package/lib/Resource/ListResource.js +15 -8
- package/lib/Resource/resourceComponents.d.ts +4 -1
- package/lib/Resource/resourceComponents.js +14 -13
- package/lib/TagSelector/SuggestionInput.js +111 -57
- package/lib/TagSelector/Suggestions.js +26 -23
- package/lib/TagSelector/TagSelector.js +8 -7
- package/lib/TreeStructure/FolderItem.js +5 -5
- package/lib/TreeStructure/FolderItems.d.ts +1 -1
- package/lib/TreeStructure/FolderItems.js +8 -8
- package/lib/TreeStructure/TreeStructure.d.ts +6 -1
- package/lib/TreeStructure/TreeStructure.js +66 -80
- package/lib/TreeStructure/TreeStructure.types.d.ts +5 -3
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.js +23 -11
- package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.d.ts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +0 -7
- package/lib/locale/messages-en.d.ts +9 -0
- package/lib/locale/messages-en.js +10 -1
- package/lib/locale/messages-nb.d.ts +9 -0
- package/lib/locale/messages-nb.js +11 -2
- package/lib/locale/messages-nn.d.ts +9 -0
- package/lib/locale/messages-nn.js +12 -3
- package/lib/locale/messages-se.d.ts +9 -0
- package/lib/locale/messages-se.js +11 -2
- package/lib/locale/messages-sma.d.ts +9 -0
- package/lib/locale/messages-sma.js +11 -2
- package/package.json +5 -5
- package/src/Article/ArticleFavoritesButton.tsx +4 -3
- package/src/Masthead/MastheadAuthModal.tsx +9 -0
- package/src/MyNdla/Resource/Folder.tsx +7 -7
- package/src/MyNdla/index.ts +1 -2
- package/src/Resource/BlockResource.tsx +7 -6
- package/src/Resource/ListResource.tsx +8 -7
- package/src/Resource/resourceComponents.tsx +8 -1
- package/src/TagSelector/SuggestionInput.tsx +90 -24
- package/src/TagSelector/Suggestions.tsx +14 -0
- package/src/TagSelector/TagSelector.tsx +6 -4
- package/src/TreeStructure/FolderItem.tsx +5 -2
- package/src/TreeStructure/FolderItems.tsx +4 -3
- package/src/TreeStructure/TreeStructure.tsx +54 -42
- package/src/TreeStructure/TreeStructure.types.ts +5 -3
- package/src/TreeStructure/keyboardNavigation/keyboardNavigation.ts +7 -7
- package/src/TreeStructure/keyboardNavigation/keyboardNavigation.types.ts +1 -1
- package/src/index.ts +1 -1
- package/src/locale/messages-en.ts +11 -1
- package/src/locale/messages-nb.ts +11 -2
- package/src/locale/messages-nn.ts +12 -3
- package/src/locale/messages-se.ts +11 -2
- package/src/locale/messages-sma.ts +11 -2
- package/es/MyNdla/Navigation/VerticalNavigation.js +0 -51
- package/es/MyNdla/Navigation/index.js +0 -2
- package/lib/MyNdla/Navigation/VerticalNavigation.d.ts +0 -10
- package/lib/MyNdla/Navigation/VerticalNavigation.js +0 -61
- package/lib/MyNdla/Navigation/index.d.ts +0 -2
- package/lib/MyNdla/Navigation/index.js +0 -15
- package/src/MyNdla/Navigation/VerticalNavigation.tsx +0 -93
- package/src/MyNdla/Navigation/index.ts +0 -2
|
@@ -19,6 +19,39 @@ import { uuid } from '@ndla/util';
|
|
|
19
19
|
import Suggestions from './Suggestions';
|
|
20
20
|
import type { TagType } from './TagSelector';
|
|
21
21
|
|
|
22
|
+
const SuggestionTextWrapper = styled.div`
|
|
23
|
+
${fonts.sizes(18)};
|
|
24
|
+
position: absolute;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-grow: 1;
|
|
27
|
+
left: 0;
|
|
28
|
+
right: 0;
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
max-height: ${spacing.large};
|
|
31
|
+
padding: 8.333px;
|
|
32
|
+
padding-right: ${spacing.large};
|
|
33
|
+
span {
|
|
34
|
+
color: ${colors.brand.grey};
|
|
35
|
+
white-space: nowrap;
|
|
36
|
+
overflow: hidden !important;
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
&:first-of-type {
|
|
39
|
+
color: transparent;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const SuggestionText = ({ value, suggestionValue }: { value: string; suggestionValue: string }) => (
|
|
45
|
+
<SuggestionTextWrapper>
|
|
46
|
+
{!!value && (
|
|
47
|
+
<>
|
|
48
|
+
<span>{value}</span>
|
|
49
|
+
<span>{suggestionValue.substring(value.length)}</span>
|
|
50
|
+
</>
|
|
51
|
+
)}
|
|
52
|
+
</SuggestionTextWrapper>
|
|
53
|
+
);
|
|
54
|
+
|
|
22
55
|
const Cross = styled(CrossRaw)`
|
|
23
56
|
margin-left: ${spacing.xxsmall};
|
|
24
57
|
`;
|
|
@@ -31,15 +64,18 @@ const StyledInput = styled.input`
|
|
|
31
64
|
flex-grow: 1;
|
|
32
65
|
border: 0;
|
|
33
66
|
outline: none;
|
|
34
|
-
background: transparent;
|
|
35
67
|
${fonts.sizes(18)};
|
|
68
|
+
z-index: 1;
|
|
69
|
+
position: relative;
|
|
70
|
+
background: transparent;
|
|
36
71
|
`;
|
|
72
|
+
|
|
37
73
|
const StyledInputWrapper = styled.div`
|
|
38
74
|
display: flex;
|
|
39
75
|
flex-wrap: wrap;
|
|
40
76
|
gap: ${spacing.xsmall};
|
|
41
77
|
padding: ${spacing.small};
|
|
42
|
-
border: 1px solid ${colors.brand.
|
|
78
|
+
border: 1px solid ${colors.brand.neutral7};
|
|
43
79
|
transition: border-color ${animations.durations.normal} ease;
|
|
44
80
|
border-radius: ${misc.borderRadius};
|
|
45
81
|
&:focus-within {
|
|
@@ -50,6 +86,12 @@ const StyledInputWrapper = styled.div`
|
|
|
50
86
|
const CombinedInputAndDropdownWrapper = styled.div`
|
|
51
87
|
display: flex;
|
|
52
88
|
flex-grow: 1;
|
|
89
|
+
position: relative;
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const StyledTagButton = styled(Button)<{ enableTagButtonAnimation: boolean }>`
|
|
93
|
+
${({ enableTagButtonAnimation }) =>
|
|
94
|
+
enableTagButtonAnimation ? animations.fadeInScaled(animations.durations.slow) : ''}
|
|
53
95
|
`;
|
|
54
96
|
|
|
55
97
|
interface SuggestionInputProps {
|
|
@@ -90,6 +132,7 @@ const SuggestionInput = ({
|
|
|
90
132
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
91
133
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
92
134
|
const suggestionIdRef = useRef<string>(uuid());
|
|
135
|
+
const initalTags = useRef<string[]>(addedTags.map(({ id }) => id));
|
|
93
136
|
|
|
94
137
|
useEffect(() => {
|
|
95
138
|
setCurrentHighlightedIndex(0);
|
|
@@ -122,7 +165,8 @@ const SuggestionInput = ({
|
|
|
122
165
|
<SuggestionInputContainer ref={containerRef}>
|
|
123
166
|
<StyledInputWrapper>
|
|
124
167
|
{addedTags.map(({ id, name }) => (
|
|
125
|
-
<
|
|
168
|
+
<StyledTagButton
|
|
169
|
+
enableTagButtonAnimation={!initalTags.current.includes(id)}
|
|
126
170
|
aria-label={t('tagSelector.removeTag', { name })}
|
|
127
171
|
onClick={() => onToggleTag(id)}
|
|
128
172
|
light
|
|
@@ -132,9 +176,12 @@ const SuggestionInput = ({
|
|
|
132
176
|
{prefix}
|
|
133
177
|
{name}
|
|
134
178
|
<Cross />
|
|
135
|
-
</
|
|
179
|
+
</StyledTagButton>
|
|
136
180
|
))}
|
|
137
181
|
<CombinedInputAndDropdownWrapper>
|
|
182
|
+
{suggestions[currentHighlightedIndex] && (
|
|
183
|
+
<SuggestionText value={value} suggestionValue={suggestions[currentHighlightedIndex].name} />
|
|
184
|
+
)}
|
|
138
185
|
<StyledInput
|
|
139
186
|
placeholder={t('tagSelector.placeholder')}
|
|
140
187
|
value={value}
|
|
@@ -157,38 +204,57 @@ const SuggestionInput = ({
|
|
|
157
204
|
}}
|
|
158
205
|
ref={inputRef}
|
|
159
206
|
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
207
|
+
if (!['Enter', ' ', 'Tab', 'ArrowDown', 'ArrowUp', 'Backspace'].includes(e.key)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const trimmedValue = value.replace(/\s/g, '');
|
|
160
211
|
if (e.key === 'Escape') {
|
|
161
212
|
setExpanded(false);
|
|
162
213
|
e.preventDefault();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
e.preventDefault();
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
onCreateTag(value);
|
|
175
|
-
setInputValue('');
|
|
176
|
-
e.preventDefault();
|
|
177
|
-
}
|
|
178
|
-
} else if (e.key === 'Enter') {
|
|
179
|
-
e.preventDefault();
|
|
180
|
-
}
|
|
181
|
-
} else if (e.key === 'ArrowUp') {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (e.key === 'Backspace' && trimmedValue === '' && addedTags.length) {
|
|
217
|
+
// Remove the added last tag
|
|
218
|
+
onToggleTag(addedTags[addedTags.length - 1].id);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (e.key === 'ArrowUp') {
|
|
182
222
|
setCurrentHighlightedIndex(
|
|
183
223
|
currentHighlightedIndex - 1 < 0 ? suggestions.length - 1 : currentHighlightedIndex - 1,
|
|
184
224
|
);
|
|
185
225
|
e.preventDefault();
|
|
186
|
-
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (e.key === 'ArrowDown') {
|
|
187
229
|
setCurrentHighlightedIndex(
|
|
188
230
|
currentHighlightedIndex + 1 >= suggestions.length ? 0 : currentHighlightedIndex + 1,
|
|
189
231
|
);
|
|
190
232
|
e.preventDefault();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (trimmedValue === '' && !expanded) {
|
|
236
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (e.key === 'Enter' || e.key === 'Tab' || e.key === ' ') {
|
|
242
|
+
if (suggestions.length > 0) {
|
|
243
|
+
if (!hasBeenAdded(suggestions[currentHighlightedIndex].id)) {
|
|
244
|
+
onToggleTag(suggestions[currentHighlightedIndex].id);
|
|
245
|
+
} else if (trimmedValue.length < suggestions[currentHighlightedIndex].name.length) {
|
|
246
|
+
onCreateTag(trimmedValue);
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
}
|
|
249
|
+
setInputValue('');
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
onCreateTag(trimmedValue);
|
|
254
|
+
setInputValue('');
|
|
255
|
+
e.preventDefault();
|
|
191
256
|
}
|
|
257
|
+
return;
|
|
192
258
|
}}
|
|
193
259
|
/>
|
|
194
260
|
<Tooltip tooltip={expanded ? t('tagSelector.hideAllTags') : t('tagSelector.showAllTags')}>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import styled from '@emotion/styled';
|
|
11
|
+
import { css } from '@emotion/core';
|
|
11
12
|
import { Check } from '@ndla/icons/editor';
|
|
12
13
|
import { spacing, colors, misc, animations, fonts, shadows } from '@ndla/core';
|
|
13
14
|
import Button from '@ndla/button';
|
|
@@ -60,6 +61,7 @@ interface SuggestionButtonProps {
|
|
|
60
61
|
const SuggestionButton = styled(Button)<SuggestionButtonProps>`
|
|
61
62
|
display: flex;
|
|
62
63
|
justify-content: space-between;
|
|
64
|
+
align-items: center;
|
|
63
65
|
${fonts.sizes(18)};
|
|
64
66
|
transition: ${misc.transition.default};
|
|
65
67
|
font-weight: 400;
|
|
@@ -72,6 +74,18 @@ const SuggestionButton = styled(Button)<SuggestionButtonProps>`
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
}
|
|
77
|
+
${({ isHighlighted }) =>
|
|
78
|
+
isHighlighted
|
|
79
|
+
? css`
|
|
80
|
+
background: ${colors.brand.lighter};
|
|
81
|
+
&:disabled {
|
|
82
|
+
background: ${colors.brand.greyLighter};
|
|
83
|
+
svg {
|
|
84
|
+
fill: ${colors.brand.grey};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`
|
|
88
|
+
: ''}
|
|
75
89
|
`;
|
|
76
90
|
|
|
77
91
|
interface Props {
|
|
@@ -33,10 +33,12 @@ interface Props {
|
|
|
33
33
|
prefix?: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const sortedTags = (tags: TagType[], selectedTags: string[]): TagType[] =>
|
|
37
|
-
|
|
38
|
-
.
|
|
39
|
-
.
|
|
36
|
+
const sortedTags = (tags: TagType[], selectedTags: string[]): TagType[] => {
|
|
37
|
+
const returnTags = selectedTags
|
|
38
|
+
.map((selectedId) => tags.find(({ id }) => selectedId === id))
|
|
39
|
+
.filter((notUndefined) => notUndefined) as unknown as TagType[];
|
|
40
|
+
return returnTags;
|
|
41
|
+
};
|
|
40
42
|
|
|
41
43
|
const getSuggestions = (tags: TagType[], inputValue: string): TagType[] => {
|
|
42
44
|
if (inputValue === '') {
|
|
@@ -42,7 +42,8 @@ const WrapperForFolderChild = styled.div<{ marked: boolean }>`
|
|
|
42
42
|
right: ${spacing.xsmall};
|
|
43
43
|
opacity: ${({ marked }) => (marked ? 1 : 0.25)};
|
|
44
44
|
&:hover,
|
|
45
|
-
&:focus
|
|
45
|
+
&:focus,
|
|
46
|
+
&:focus-within {
|
|
46
47
|
opacity: 1;
|
|
47
48
|
}
|
|
48
49
|
`;
|
|
@@ -170,7 +171,9 @@ const FolderItem = ({
|
|
|
170
171
|
{name}
|
|
171
172
|
</FolderName>
|
|
172
173
|
{folderChild && (
|
|
173
|
-
<WrapperForFolderChild marked={marked}>
|
|
174
|
+
<WrapperForFolderChild marked={marked}>
|
|
175
|
+
{folderChild(id, marked || id === focusedFolderId ? 0 : -1)}
|
|
176
|
+
</WrapperForFolderChild>
|
|
174
177
|
)}
|
|
175
178
|
</>
|
|
176
179
|
)}
|
|
@@ -12,7 +12,6 @@ import { animations, spacing } from '@ndla/core';
|
|
|
12
12
|
import FolderItem from './FolderItem';
|
|
13
13
|
import FolderNameInput from './FolderNameInput';
|
|
14
14
|
import { FolderItemsProps } from './TreeStructure.types';
|
|
15
|
-
import { MAX_LEVEL_FOR_FOLDERS } from './TreeStructure';
|
|
16
15
|
|
|
17
16
|
const StyledUL = styled.ul<{ firstLevel?: boolean }>`
|
|
18
17
|
${animations.fadeInLeft(animations.durations.fast)};
|
|
@@ -49,11 +48,12 @@ const FolderItems = ({
|
|
|
49
48
|
setFocusedFolderId,
|
|
50
49
|
firstLevel,
|
|
51
50
|
folderChild,
|
|
51
|
+
maximumLevelsOfFoldersAllowed,
|
|
52
52
|
}: FolderItemsProps) => (
|
|
53
53
|
<StyledUL role="group" firstLevel={firstLevel}>
|
|
54
54
|
{data.map(({ name, data: dataChildren, id, url, icon }, _index) => {
|
|
55
55
|
const newIdPaths = [...idPaths, _index];
|
|
56
|
-
const isOpen = openFolders?.
|
|
56
|
+
const isOpen = openFolders?.includes(id);
|
|
57
57
|
return (
|
|
58
58
|
<StyledLI key={id} role="treeitem">
|
|
59
59
|
<div>
|
|
@@ -69,7 +69,7 @@ const FolderItems = ({
|
|
|
69
69
|
focusedFolderId={focusedFolderId}
|
|
70
70
|
onToggleOpen={onToggleOpen}
|
|
71
71
|
onMarkFolder={onMarkFolder}
|
|
72
|
-
hideArrow={dataChildren?.length === 0 || newIdPaths.length >=
|
|
72
|
+
hideArrow={dataChildren?.length === 0 || newIdPaths.length >= maximumLevelsOfFoldersAllowed}
|
|
73
73
|
noPaddingWhenArrowIsHidden={editable && firstLevel && dataChildren?.length === 0}
|
|
74
74
|
setFocusedFolderId={setFocusedFolderId}
|
|
75
75
|
folderChild={folderChild}
|
|
@@ -101,6 +101,7 @@ const FolderItems = ({
|
|
|
101
101
|
setFocusedFolderId={setFocusedFolderId}
|
|
102
102
|
firstLevel={false}
|
|
103
103
|
folderChild={folderChild}
|
|
104
|
+
maximumLevelsOfFoldersAllowed={maximumLevelsOfFoldersAllowed}
|
|
104
105
|
/>
|
|
105
106
|
)}
|
|
106
107
|
</StyledLI>
|
|
@@ -13,6 +13,7 @@ import Tooltip from '@ndla/tooltip';
|
|
|
13
13
|
import { useTranslation } from 'react-i18next';
|
|
14
14
|
import styled from '@emotion/styled';
|
|
15
15
|
import { spacing, fonts } from '@ndla/core';
|
|
16
|
+
import { uniq } from 'lodash';
|
|
16
17
|
import TreeStructureStyledWrapper from './TreeStructureWrapper';
|
|
17
18
|
import FolderItems from './FolderItems';
|
|
18
19
|
import { getIdPathsOfFolder, getPathOfFolder, getFolderName } from './helperFunctions';
|
|
@@ -41,21 +42,23 @@ const TreeStructure = ({
|
|
|
41
42
|
folderIdMarkedByDefault,
|
|
42
43
|
defaultOpenFolders,
|
|
43
44
|
folderChild,
|
|
45
|
+
maximumLevelsOfFoldersAllowed,
|
|
44
46
|
}: TreeStructureProps) => {
|
|
45
47
|
const { t } = useTranslation();
|
|
46
48
|
const [newFolder, setNewFolder] = useState<NewFolderProps | undefined>();
|
|
47
|
-
const [openFolders, setOpenFolders] = useState<
|
|
49
|
+
const [openFolders, setOpenFolders] = useState<string[]>(defaultOpenFolders || []);
|
|
48
50
|
const [focusedFolderId, setFocusedFolderId] = useState<string | undefined>();
|
|
49
|
-
const [markedFolderId, setMarkedFolderId] = useState<string | undefined>(folderIdMarkedByDefault || data[0]
|
|
51
|
+
const [markedFolderId, setMarkedFolderId] = useState<string | undefined>(folderIdMarkedByDefault || data[0]?.id);
|
|
50
52
|
const treestructureRef = useRef<HTMLDivElement>(null);
|
|
51
53
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
52
54
|
const rootLevelId = useMemo(() => uuid(), []); // TODO: use useId hook when we update to React 18
|
|
53
55
|
|
|
54
56
|
useEffect(() => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
if (defaultOpenFolders) {
|
|
58
|
+
setOpenFolders((prev) => {
|
|
59
|
+
return uniq([...defaultOpenFolders, ...prev]);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
59
62
|
}, [defaultOpenFolders]);
|
|
60
63
|
|
|
61
64
|
useEffect(() => {
|
|
@@ -65,48 +68,44 @@ const TreeStructure = ({
|
|
|
65
68
|
}, [loading]);
|
|
66
69
|
|
|
67
70
|
const onToggleOpen = (id: string) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
);
|
|
79
|
-
if (markedFolderIsSubPath) {
|
|
80
|
-
setMarkedFolderId(closingFolderPath[closingFolderPath.length - 1]);
|
|
81
|
-
}
|
|
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) {
|
|
81
|
+
setMarkedFolderId(closingFolderPath[closingFolderPath.length - 1]);
|
|
82
82
|
}
|
|
83
|
-
} else {
|
|
84
|
-
prev.add(id);
|
|
85
83
|
}
|
|
86
|
-
|
|
87
|
-
}
|
|
84
|
+
setOpenFolders(openFolders.filter((folder) => folder !== id));
|
|
85
|
+
} else {
|
|
86
|
+
setOpenFolders(uniq([...openFolders, id]));
|
|
87
|
+
}
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
const onCreateNewFolder = (props: { idPaths: number[]; parentId?: string }) => {
|
|
91
91
|
setNewFolder(props);
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
const onSaveNewFolder =
|
|
94
|
+
const onSaveNewFolder = (value: string) => {
|
|
95
95
|
if (newFolder) {
|
|
96
96
|
// We would like to create a new folder with the name of value.
|
|
97
97
|
// Its location in structure is based on newFolder object
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
104
|
if (newFolder.parentId) {
|
|
105
|
-
|
|
105
|
+
setOpenFolders(uniq([...openFolders, newFolder.parentId]));
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
110
109
|
}
|
|
111
110
|
};
|
|
112
111
|
|
|
@@ -119,6 +118,9 @@ const TreeStructure = ({
|
|
|
119
118
|
setFocusedFolderId(id);
|
|
120
119
|
};
|
|
121
120
|
|
|
121
|
+
const paths = getPathOfFolder(data, markedFolderId || '');
|
|
122
|
+
const canAddFolder = editable && paths.length < (maximumLevelsOfFoldersAllowed || 1);
|
|
123
|
+
|
|
122
124
|
return (
|
|
123
125
|
<div
|
|
124
126
|
ref={treestructureRef}
|
|
@@ -134,7 +136,7 @@ const TreeStructure = ({
|
|
|
134
136
|
});
|
|
135
137
|
}
|
|
136
138
|
}}>
|
|
137
|
-
<StyledLabel htmlFor={rootLevelId}>{label}</StyledLabel>
|
|
139
|
+
{label && <StyledLabel htmlFor={rootLevelId}>{label}</StyledLabel>}
|
|
138
140
|
<TreeStructureStyledWrapper ref={wrapperRef} id={rootLevelId} aria-label="Menu tree" role="tree" framed={framed}>
|
|
139
141
|
<FolderItems
|
|
140
142
|
idPaths={[]}
|
|
@@ -154,22 +156,28 @@ const TreeStructure = ({
|
|
|
154
156
|
setFocusedFolderId={setFocusedFolderId}
|
|
155
157
|
firstLevel
|
|
156
158
|
folderChild={folderChild}
|
|
159
|
+
maximumLevelsOfFoldersAllowed={maximumLevelsOfFoldersAllowed}
|
|
157
160
|
/>
|
|
158
161
|
</TreeStructureStyledWrapper>
|
|
159
162
|
{editable && (
|
|
160
163
|
<AddFolderWrapper>
|
|
161
164
|
<Tooltip
|
|
162
|
-
tooltip={
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
tooltip={
|
|
166
|
+
canAddFolder
|
|
167
|
+
? t('myNdla.newFolderUnder', {
|
|
168
|
+
folderName: getFolderName(data, markedFolderId),
|
|
169
|
+
})
|
|
170
|
+
: t('myNdla.maxFoldersAlreadyAdded')
|
|
171
|
+
}>
|
|
165
172
|
<AddButton
|
|
173
|
+
disabled={!canAddFolder}
|
|
166
174
|
aria-label={t('myNdla.newFolder')}
|
|
167
175
|
onClick={() => {
|
|
168
|
-
const paths = getPathOfFolder(data, markedFolderId || '');
|
|
169
176
|
const idPaths = getIdPathsOfFolder(data, markedFolderId || '');
|
|
170
177
|
setNewFolder({ idPaths, parentId: paths[paths.length - 1] });
|
|
171
|
-
}}
|
|
172
|
-
|
|
178
|
+
}}>
|
|
179
|
+
{t('myNdla.newFolder')}
|
|
180
|
+
</AddButton>
|
|
173
181
|
</Tooltip>
|
|
174
182
|
</AddFolderWrapper>
|
|
175
183
|
)}
|
|
@@ -177,4 +185,8 @@ const TreeStructure = ({
|
|
|
177
185
|
);
|
|
178
186
|
};
|
|
179
187
|
|
|
188
|
+
TreeStructure.defaultProps = {
|
|
189
|
+
maximumLevelsOfFoldersAllowed: MAX_LEVEL_FOR_FOLDERS,
|
|
190
|
+
};
|
|
191
|
+
|
|
180
192
|
export default TreeStructure;
|
|
@@ -34,11 +34,12 @@ interface CommonFolderProps {
|
|
|
34
34
|
|
|
35
35
|
export interface TreeStructureProps extends CommonFolderProps {
|
|
36
36
|
framed?: boolean;
|
|
37
|
-
label
|
|
37
|
+
label?: string;
|
|
38
38
|
folderIdMarkedByDefault?: string;
|
|
39
39
|
onNewFolder: (props: { value: string; parentId?: string; idPaths: number[] }) => Promise<string>;
|
|
40
40
|
defaultOpenFolders?: string[];
|
|
41
41
|
folderChild?: FolderChildFuncType;
|
|
42
|
+
maximumLevelsOfFoldersAllowed: number;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export type onCreateNewFolderProp = ({
|
|
@@ -49,7 +50,7 @@ export type onCreateNewFolderProp = ({
|
|
|
49
50
|
parentId: string | undefined;
|
|
50
51
|
}) => void;
|
|
51
52
|
|
|
52
|
-
export type SetOpenFolderProp = React.Dispatch<React.SetStateAction<
|
|
53
|
+
export type SetOpenFolderProp = React.Dispatch<React.SetStateAction<string[]>>;
|
|
53
54
|
export type SetFocusedFolderId = React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
54
55
|
|
|
55
56
|
export type FolderChildFuncType = (id: string, tabIndex: number) => ReactNode;
|
|
@@ -60,7 +61,7 @@ export interface FolderItemsProps extends CommonFolderProps {
|
|
|
60
61
|
onCancelNewFolder: () => void;
|
|
61
62
|
onCreateNewFolder: onCreateNewFolderProp;
|
|
62
63
|
newFolder: NewFolderProps | undefined;
|
|
63
|
-
openFolders:
|
|
64
|
+
openFolders: string[];
|
|
64
65
|
markedFolderId?: string;
|
|
65
66
|
onMarkFolder: (id: string) => void;
|
|
66
67
|
idPaths: number[];
|
|
@@ -70,4 +71,5 @@ export interface FolderItemsProps extends CommonFolderProps {
|
|
|
70
71
|
keyNavigationFocusIsCreateFolderButton?: boolean;
|
|
71
72
|
icon?: ReactNode;
|
|
72
73
|
folderChild?: FolderChildFuncType;
|
|
74
|
+
maximumLevelsOfFoldersAllowed: number;
|
|
73
75
|
}
|
|
@@ -66,7 +66,7 @@ const keyboardNavigation = ({
|
|
|
66
66
|
if (dataId === id) {
|
|
67
67
|
elementWithKeyFocus.paths = paths;
|
|
68
68
|
elementWithKeyFocus.index = _index;
|
|
69
|
-
elementWithKeyFocus.isOpen = openFolders.
|
|
69
|
+
elementWithKeyFocus.isOpen = openFolders.includes(dataId) && childData && childData.length > 0;
|
|
70
70
|
elementWithKeyFocus.data = childData;
|
|
71
71
|
elementWithKeyFocus.parent = parent;
|
|
72
72
|
elementWithKeyFocus.parentId = parentId;
|
|
@@ -78,7 +78,7 @@ const keyboardNavigation = ({
|
|
|
78
78
|
if (!updatePathToElementWithKeyFocus(data, [], data)) {
|
|
79
79
|
// Couldn't find its location in the tree.
|
|
80
80
|
// This should not happen, reset its value to root.
|
|
81
|
-
setFocusedFolderId(e.key === 'ArrowDown' ? data[0]
|
|
81
|
+
setFocusedFolderId(e.key === 'ArrowDown' ? data[0]?.id : undefined);
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
84
|
e.preventDefault();
|
|
@@ -113,7 +113,7 @@ const keyboardNavigation = ({
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
if (!id && e.key === 'ArrowDown') {
|
|
116
|
-
setFocusedFolderId(data[0]
|
|
116
|
+
setFocusedFolderId(data[0]?.id);
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
if (!id) {
|
|
@@ -124,7 +124,7 @@ const keyboardNavigation = ({
|
|
|
124
124
|
if (elementWithKeyFocus.index > 0) {
|
|
125
125
|
// Move upwards to the parent folder
|
|
126
126
|
setFocusedFolderId(
|
|
127
|
-
elementWithKeyFocus.parent ? elementWithKeyFocus.parent[elementWithKeyFocus.index - 1]
|
|
127
|
+
elementWithKeyFocus.parent ? elementWithKeyFocus.parent[elementWithKeyFocus.index - 1]?.id : undefined,
|
|
128
128
|
);
|
|
129
129
|
} else if (elementWithKeyFocus.paths.length > 0) {
|
|
130
130
|
elementWithKeyFocus.paths.pop();
|
|
@@ -133,14 +133,14 @@ const keyboardNavigation = ({
|
|
|
133
133
|
findParent = findParent[index].data as FolderStructureProps[];
|
|
134
134
|
});
|
|
135
135
|
const parentsCurrentIndex = findParent.findIndex(({ id }) => id === elementWithKeyFocus.parentId);
|
|
136
|
-
setFocusedFolderId(findParent[parentsCurrentIndex]
|
|
136
|
+
setFocusedFolderId(findParent[parentsCurrentIndex]?.id);
|
|
137
137
|
}
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
if (elementWithKeyFocus.isOpen) {
|
|
142
142
|
if (elementWithKeyFocus.data?.length) {
|
|
143
|
-
setFocusedFolderId(elementWithKeyFocus.data[0]
|
|
143
|
+
setFocusedFolderId(elementWithKeyFocus.data[0]?.id);
|
|
144
144
|
} else {
|
|
145
145
|
// move to next child of parent if any... need new traverse :-/
|
|
146
146
|
traverseUpwards(data, setFocusedFolderId, elementWithKeyFocus.paths, elementWithKeyFocus.index);
|
|
@@ -150,7 +150,7 @@ const keyboardNavigation = ({
|
|
|
150
150
|
|
|
151
151
|
if (elementWithKeyFocus.parent && elementWithKeyFocus.index < elementWithKeyFocus.parent?.length - 1) {
|
|
152
152
|
// Move downwards to the next child
|
|
153
|
-
setFocusedFolderId(elementWithKeyFocus.parent[elementWithKeyFocus.index + 1]
|
|
153
|
+
setFocusedFolderId(elementWithKeyFocus.parent[elementWithKeyFocus.index + 1]?.id);
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -12,7 +12,7 @@ export interface KeyboardNavigationProps {
|
|
|
12
12
|
e: React.KeyboardEvent<HTMLElement>;
|
|
13
13
|
data: FolderStructureProps[];
|
|
14
14
|
setFocusedFolderId: SetFocusedFolderId;
|
|
15
|
-
openFolders:
|
|
15
|
+
openFolders: string[];
|
|
16
16
|
onToggleOpen: (id: string) => void;
|
|
17
17
|
focusedFolderId: string | undefined;
|
|
18
18
|
}
|
package/src/index.ts
CHANGED
|
@@ -236,7 +236,7 @@ export { Notion, ConceptNotion } from './Notion';
|
|
|
236
236
|
export type { NotionVisualElementType, ConceptNotionType } from './Notion';
|
|
237
237
|
|
|
238
238
|
export { BannerCard } from './BannerCard';
|
|
239
|
-
export {
|
|
239
|
+
export { Folder, FolderInput } from './MyNdla';
|
|
240
240
|
export { ListResource, BlockResource } from './Resource';
|
|
241
241
|
export type { ListResourceProps } from './Resource';
|
|
242
242
|
export type { TagType } from './TagSelector';
|
|
@@ -15,14 +15,20 @@ const titleTemplate = ' - NDLA';
|
|
|
15
15
|
|
|
16
16
|
const messages = {
|
|
17
17
|
treeStructure: {
|
|
18
|
+
folderChildOptions: {
|
|
19
|
+
edit: 'Edit foldername',
|
|
20
|
+
delete: 'Delete',
|
|
21
|
+
},
|
|
18
22
|
createFolder: 'Create folder',
|
|
23
|
+
maxFoldersAlreadyAdded: 'Maximum subfolders reached',
|
|
19
24
|
newFolder: {
|
|
20
25
|
placeholder: 'Add foldername',
|
|
21
26
|
defaultName: 'New folder',
|
|
22
27
|
},
|
|
23
28
|
},
|
|
24
29
|
tagSelector: {
|
|
25
|
-
|
|
30
|
+
label: 'Add tag',
|
|
31
|
+
placeholder: 'Enter tag name',
|
|
26
32
|
removeTag: 'Remove tag {{name}}',
|
|
27
33
|
hideAllTags: 'Hide all tags',
|
|
28
34
|
showAllTags: 'Show all tags',
|
|
@@ -978,6 +984,7 @@ const messages = {
|
|
|
978
984
|
close: 'Close fact box',
|
|
979
985
|
},
|
|
980
986
|
myNdla: {
|
|
987
|
+
myNDLA: 'My NDLA',
|
|
981
988
|
resources: '{{count}} Resource',
|
|
982
989
|
resources_plural: '{{count}} Resources',
|
|
983
990
|
folders: '{{count}} Folder',
|
|
@@ -989,6 +996,8 @@ const messages = {
|
|
|
989
996
|
newFolderUnder: 'Create new folder under {{folderName}}',
|
|
990
997
|
myAccount: 'My account',
|
|
991
998
|
favourites: 'Favourites',
|
|
999
|
+
addToFavourites: 'Add to my favourites',
|
|
1000
|
+
alreadyFavourited: 'Already in my favourites',
|
|
992
1001
|
help: 'Help',
|
|
993
1002
|
more: 'More options',
|
|
994
1003
|
listView: 'List view',
|
|
@@ -1007,6 +1016,7 @@ const messages = {
|
|
|
1007
1016
|
terms: 'terms of use',
|
|
1008
1017
|
feide: 'We have retrieved this information from Feide',
|
|
1009
1018
|
newFavourite: 'Recently favourited',
|
|
1019
|
+
|
|
1010
1020
|
storageInfo: {
|
|
1011
1021
|
title: 'How to save your favourite resources from NDLA',
|
|
1012
1022
|
text: 'When you wish to save a resource, you can do so by clicking the heart on the top right corner of the page. You will then get an option to store the resource in a folder',
|
|
@@ -15,15 +15,21 @@ const titleTemplate = ' - NDLA';
|
|
|
15
15
|
|
|
16
16
|
const messages = {
|
|
17
17
|
treeStructure: {
|
|
18
|
+
folderChildOptions: {
|
|
19
|
+
edit: 'Endre mappenavn',
|
|
20
|
+
delete: 'Slett',
|
|
21
|
+
},
|
|
18
22
|
createFolder: 'Lag mappe',
|
|
23
|
+
maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
|
|
19
24
|
newFolder: {
|
|
20
25
|
placeholder: 'Skriv navn på mappe',
|
|
21
26
|
defaultName: 'Ny mappe',
|
|
22
27
|
},
|
|
23
28
|
},
|
|
24
29
|
tagSelector: {
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
label: 'Legg til tag',
|
|
31
|
+
placeholder: 'Skriv tag navn',
|
|
32
|
+
removeTag: 'Ta vekk {{name}}',
|
|
27
33
|
hideAllTags: 'Skjul alle tagger',
|
|
28
34
|
showAllTags: 'Vis alle tagger',
|
|
29
35
|
},
|
|
@@ -976,6 +982,7 @@ const messages = {
|
|
|
976
982
|
close: 'Lukk faktaboks',
|
|
977
983
|
},
|
|
978
984
|
myNdla: {
|
|
985
|
+
myNDLA: 'Min NDLA',
|
|
979
986
|
resources: '{{count}} ressurs',
|
|
980
987
|
resources_plural: '{{count}} ressurser',
|
|
981
988
|
folders: '{{count}} mappe',
|
|
@@ -987,6 +994,8 @@ const messages = {
|
|
|
987
994
|
newFolderUnder: 'Lag ny mappe under {{folderName}}',
|
|
988
995
|
myAccount: 'Min konto',
|
|
989
996
|
favourites: 'Favoritter',
|
|
997
|
+
addToFavourites: 'Legg til i mine favoritter',
|
|
998
|
+
alreadyFavourited: 'Allerede lagt til i mine favoritter',
|
|
990
999
|
help: 'Hjelp',
|
|
991
1000
|
more: 'Flere valg',
|
|
992
1001
|
listView: 'Listevisning',
|