@ndla/ui 26.1.0 → 27.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/Breadcrumb/Breadcrumb.js +3 -4
- package/es/MyNdla/Resource/Folder.js +29 -13
- package/es/Resource/BlockResource.js +42 -62
- package/es/Resource/ListResource.js +41 -22
- package/es/Resource/resourceComponents.js +64 -38
- package/es/TreeStructure/ComboboxButton.js +162 -0
- package/es/TreeStructure/FolderItem.js +98 -78
- package/es/TreeStructure/FolderItems.js +25 -14
- package/es/TreeStructure/FolderNameInput.js +40 -33
- package/es/TreeStructure/NavigationLink.js +18 -10
- package/es/TreeStructure/TreeStructure.js +92 -165
- package/es/TreeStructure/arrowNavigation.js +3 -3
- package/es/TreeStructure/helperFunctions.js +3 -0
- package/es/locale/messages-en.js +6 -1
- package/es/locale/messages-nb.js +6 -1
- package/es/locale/messages-nn.js +6 -1
- package/es/locale/messages-se.js +6 -1
- package/es/locale/messages-sma.js +6 -1
- package/lib/Breadcrumb/Breadcrumb.js +3 -5
- package/lib/MyNdla/Resource/Folder.js +34 -13
- package/lib/Resource/BlockResource.js +47 -62
- package/lib/Resource/ListResource.js +46 -22
- package/lib/Resource/resourceComponents.d.ts +6 -1
- package/lib/Resource/resourceComponents.js +64 -37
- package/lib/TreeStructure/ComboboxButton.d.ts +28 -0
- package/lib/TreeStructure/ComboboxButton.js +176 -0
- package/lib/TreeStructure/FolderItem.d.ts +1 -1
- package/lib/TreeStructure/FolderItem.js +99 -77
- package/lib/TreeStructure/FolderItems.d.ts +4 -2
- package/lib/TreeStructure/FolderItems.js +26 -14
- package/lib/TreeStructure/FolderNameInput.d.ts +3 -1
- package/lib/TreeStructure/FolderNameInput.js +41 -32
- package/lib/TreeStructure/NavigationLink.d.ts +1 -1
- package/lib/TreeStructure/NavigationLink.js +18 -10
- package/lib/TreeStructure/TreeStructure.d.ts +2 -2
- package/lib/TreeStructure/TreeStructure.js +92 -165
- package/lib/TreeStructure/arrowNavigation.d.ts +2 -1
- package/lib/TreeStructure/arrowNavigation.js +3 -3
- package/lib/TreeStructure/helperFunctions.d.ts +2 -1
- package/lib/TreeStructure/helperFunctions.js +8 -2
- package/lib/TreeStructure/types.d.ts +6 -7
- package/lib/locale/messages-en.d.ts +5 -0
- package/lib/locale/messages-en.js +6 -1
- package/lib/locale/messages-nb.d.ts +5 -0
- package/lib/locale/messages-nb.js +6 -1
- package/lib/locale/messages-nn.d.ts +5 -0
- package/lib/locale/messages-nn.js +6 -1
- package/lib/locale/messages-se.d.ts +5 -0
- package/lib/locale/messages-se.js +6 -1
- package/lib/locale/messages-sma.d.ts +5 -0
- package/lib/locale/messages-sma.js +6 -1
- package/package.json +11 -11
- package/src/Breadcrumb/Breadcrumb.tsx +1 -2
- package/src/MyNdla/Resource/Folder.tsx +19 -4
- package/src/Resource/BlockResource.tsx +41 -33
- package/src/Resource/ListResource.tsx +35 -29
- package/src/Resource/resourceComponents.tsx +60 -26
- package/src/TreeStructure/ComboboxButton.tsx +189 -0
- package/src/TreeStructure/FolderItem.tsx +89 -70
- package/src/TreeStructure/FolderItems.tsx +36 -16
- package/src/TreeStructure/FolderNameInput.tsx +43 -18
- package/src/TreeStructure/NavigationLink.tsx +17 -10
- package/src/TreeStructure/TreeStructure.tsx +63 -139
- package/src/TreeStructure/arrowNavigation.ts +7 -6
- package/src/TreeStructure/helperFunctions.ts +5 -1
- package/src/TreeStructure/types.ts +6 -7
- package/src/locale/messages-en.ts +6 -0
- package/src/locale/messages-nb.ts +5 -0
- package/src/locale/messages-nn.ts +5 -0
- package/src/locale/messages-se.ts +6 -0
- package/src/locale/messages-sma.ts +6 -0
- package/src/.DS_Store +0 -0
|
@@ -16,6 +16,7 @@ import { colors, spacing, animations, spacingUnit, misc, fonts } from '@ndla/cor
|
|
|
16
16
|
import SafeLink from '@ndla/safelink';
|
|
17
17
|
import { CommonFolderItemsProps, FolderType } from './types';
|
|
18
18
|
import { arrowNavigation } from './arrowNavigation';
|
|
19
|
+
import { treestructureId } from './helperFunctions';
|
|
19
20
|
|
|
20
21
|
const OpenButton = styled.span<{ isOpen: boolean }>`
|
|
21
22
|
display: flex;
|
|
@@ -40,22 +41,13 @@ const StyledName = styled.span`
|
|
|
40
41
|
text-align: left;
|
|
41
42
|
`;
|
|
42
43
|
|
|
43
|
-
const
|
|
44
|
-
display: flex;
|
|
45
|
-
flex-direction: row;
|
|
46
|
-
align-items: center;
|
|
47
|
-
gap: ${spacing.xsmall};
|
|
48
|
-
margin-left: auto;
|
|
49
|
-
`;
|
|
50
|
-
|
|
51
|
-
const shouldForwardProp = (name: string) => !['selected', 'noArrow', 'fullWidth', 'level'].includes(name);
|
|
44
|
+
const shouldForwardProp = (name: string) => !['selected', 'level', 'focused', 'isCreatingFolder'].includes(name);
|
|
52
45
|
|
|
53
46
|
interface FolderNameProps {
|
|
54
47
|
selected?: boolean;
|
|
55
|
-
noArrow?: boolean;
|
|
56
|
-
fullWidth?: boolean;
|
|
57
48
|
level: number;
|
|
58
49
|
isCreatingFolder?: boolean;
|
|
50
|
+
focused?: boolean;
|
|
59
51
|
}
|
|
60
52
|
|
|
61
53
|
const FolderName = styled(Button, { shouldForwardProp })<FolderNameProps>`
|
|
@@ -66,14 +58,15 @@ const FolderName = styled(Button, { shouldForwardProp })<FolderNameProps>`
|
|
|
66
58
|
gap: ${spacing.xxsmall};
|
|
67
59
|
border: none;
|
|
68
60
|
outline: none;
|
|
69
|
-
background: ${({ selected, isCreatingFolder }) =>
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
background: ${({ selected, isCreatingFolder, focused }) =>
|
|
62
|
+
isCreatingFolder ? 'none' : selected ? colors.brand.lighter : focused && colors.brand.lightest};
|
|
63
|
+
color: ${({ isCreatingFolder, focused }) =>
|
|
64
|
+
isCreatingFolder && focused ? colors.brand.primary : colors.text.primary};
|
|
72
65
|
transition: ${animations.durations.superFast};
|
|
73
66
|
line-height: 1;
|
|
74
67
|
word-break: break-word;
|
|
75
|
-
|
|
76
|
-
&:
|
|
68
|
+
|
|
69
|
+
&:hover {
|
|
77
70
|
box-shadow: none;
|
|
78
71
|
outline: none;
|
|
79
72
|
background: ${({ selected }) => (selected ? colors.brand.light : colors.brand.lightest)};
|
|
@@ -115,7 +108,7 @@ interface Props extends CommonFolderItemsProps {
|
|
|
115
108
|
}
|
|
116
109
|
|
|
117
110
|
const FolderItem = ({
|
|
118
|
-
|
|
111
|
+
focusedFolder,
|
|
119
112
|
folder,
|
|
120
113
|
isOpen,
|
|
121
114
|
level,
|
|
@@ -124,45 +117,48 @@ const FolderItem = ({
|
|
|
124
117
|
onCloseFolder,
|
|
125
118
|
onOpenFolder,
|
|
126
119
|
onSelectFolder,
|
|
127
|
-
|
|
128
|
-
setFocusedId,
|
|
120
|
+
setFocusedFolder,
|
|
129
121
|
setSelectedFolder,
|
|
130
122
|
targetResource,
|
|
131
123
|
visibleFolders,
|
|
132
|
-
framed,
|
|
133
124
|
maxLevel,
|
|
134
125
|
isCreatingFolder,
|
|
126
|
+
type,
|
|
127
|
+
closeTree,
|
|
135
128
|
}: Props) => {
|
|
136
129
|
const { t } = useTranslation();
|
|
137
|
-
const { id, name } = folder;
|
|
130
|
+
const { id, name, isNavigation } = folder;
|
|
138
131
|
const ref = useRef<HTMLButtonElement & HTMLAnchorElement>(null);
|
|
139
|
-
const selected = selectedFolder
|
|
140
|
-
|
|
132
|
+
const selected = selectedFolder ? selectedFolder.id === id : false;
|
|
133
|
+
|
|
134
|
+
const focused = focusedFolder?.id === id;
|
|
141
135
|
|
|
142
136
|
const handleClickFolder = () => {
|
|
143
|
-
if (openOnFolderClick) {
|
|
144
|
-
if (selected) {
|
|
145
|
-
if (isOpen) {
|
|
146
|
-
onCloseFolder(id);
|
|
147
|
-
} else {
|
|
148
|
-
onOpenFolder(id);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
137
|
if (!selected) {
|
|
153
138
|
setSelectedFolder(folder);
|
|
154
|
-
setFocusedId(id);
|
|
155
139
|
}
|
|
156
|
-
|
|
140
|
+
setFocusedFolder(folder);
|
|
141
|
+
if (type === 'picker') {
|
|
142
|
+
if (selected) {
|
|
143
|
+
closeTree();
|
|
144
|
+
}
|
|
145
|
+
onSelectFolder?.(id);
|
|
146
|
+
}
|
|
157
147
|
};
|
|
158
148
|
|
|
159
149
|
useEffect(() => {
|
|
160
|
-
if (
|
|
161
|
-
|
|
150
|
+
if (focusedFolder?.id === id && !isCreatingFolder) {
|
|
151
|
+
if (type === 'navigation') {
|
|
152
|
+
ref.current?.focus();
|
|
153
|
+
}
|
|
154
|
+
ref.current?.scrollIntoView({
|
|
155
|
+
behavior: 'smooth',
|
|
156
|
+
block: 'nearest',
|
|
157
|
+
});
|
|
162
158
|
}
|
|
163
|
-
}, [
|
|
159
|
+
}, [focusedFolder, ref, id, isCreatingFolder, type]);
|
|
164
160
|
|
|
165
|
-
const linkPath = `/minndla${
|
|
161
|
+
const linkPath = `/minndla${!isNavigation ? '/folders' : ''}/${id}`;
|
|
166
162
|
|
|
167
163
|
const containsResource =
|
|
168
164
|
targetResource && folder.resources.some((resource) => resource.resourceId === targetResource.resourceId);
|
|
@@ -173,28 +169,36 @@ const FolderItem = ({
|
|
|
173
169
|
|
|
174
170
|
const hideArrow = isMaxDepth || emptyFolder;
|
|
175
171
|
|
|
176
|
-
return
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
return type === 'navigation' ? (
|
|
173
|
+
<FolderNameLink
|
|
174
|
+
role="treeitem"
|
|
175
|
+
aria-owns={folder.subfolders.length ? treestructureId(type, `subfolders-${folder.id}`) : undefined}
|
|
176
|
+
aria-expanded={isMaxDepth || emptyFolder ? undefined : isOpen}
|
|
177
|
+
aria-current={selected ? 'page' : undefined}
|
|
178
|
+
aria-describedby={containsResource ? `alreadyAdded-${folder.id}` : undefined}
|
|
182
179
|
ref={ref}
|
|
183
180
|
level={level}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
onKeyDown={(e: KeyboardEvent<HTMLElement>) => {
|
|
182
|
+
if (e.key === 'Enter') {
|
|
183
|
+
setSelectedFolder(folder);
|
|
184
|
+
setFocusedFolder(folder);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
arrowNavigation(e, id, visibleFolders, setFocusedFolder, onOpenFolder, onCloseFolder);
|
|
188
|
+
}}
|
|
189
|
+
to={loading ? '' : linkPath}
|
|
187
190
|
tabIndex={selected || focused ? 0 : -1}
|
|
188
191
|
selected={selected}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
isCreatingFolder={isCreatingFolder}>
|
|
193
|
-
{!hideArrow && (
|
|
192
|
+
onFocus={() => setFocusedFolder(folder)}
|
|
193
|
+
onClick={handleClickFolder}>
|
|
194
|
+
{(!hideArrow || level === 0) && (
|
|
194
195
|
<OpenButton
|
|
196
|
+
aria-hidden
|
|
195
197
|
tabIndex={-1}
|
|
196
198
|
isOpen={isOpen}
|
|
197
|
-
onClick={() => {
|
|
199
|
+
onClick={(e) => {
|
|
200
|
+
e.stopPropagation();
|
|
201
|
+
e.preventDefault();
|
|
198
202
|
ref.current?.focus();
|
|
199
203
|
if (isOpen) {
|
|
200
204
|
onCloseFolder(id);
|
|
@@ -206,29 +210,37 @@ const FolderItem = ({
|
|
|
206
210
|
</OpenButton>
|
|
207
211
|
)}
|
|
208
212
|
<StyledName>{name}</StyledName>
|
|
209
|
-
|
|
210
|
-
{containsResource && <StyledDone title={t('myNdla.alreadyInFolder')} />}
|
|
211
|
-
</WrapperForFolderChild>
|
|
212
|
-
</FolderName>
|
|
213
|
+
</FolderNameLink>
|
|
213
214
|
) : (
|
|
214
|
-
<
|
|
215
|
+
<FolderName
|
|
216
|
+
tabIndex={-1}
|
|
217
|
+
role="treeitem"
|
|
218
|
+
id={treestructureId(type, folder.id)}
|
|
219
|
+
aria-expanded={isMaxDepth || emptyFolder ? undefined : isOpen}
|
|
220
|
+
aria-selected={selected}
|
|
221
|
+
focused={focusedFolder?.id === folder.id}
|
|
222
|
+
aria-describedby={containsResource ? `alreadyAdded-${folder.id}` : undefined}
|
|
223
|
+
variant="ghost"
|
|
224
|
+
shape="sharp"
|
|
225
|
+
fontWeight="normal"
|
|
226
|
+
colorTheme="light"
|
|
215
227
|
ref={ref}
|
|
216
228
|
level={level}
|
|
217
|
-
onKeyDown={(e: KeyboardEvent<HTMLElement>) =>
|
|
218
|
-
arrowNavigation(e, id, visibleFolders, setFocusedId, onOpenFolder, onCloseFolder)
|
|
219
|
-
}
|
|
220
|
-
noArrow={!isMaxDepth}
|
|
221
|
-
to={loading ? '' : linkPath}
|
|
222
|
-
tabIndex={selected || focused ? 0 : -1}
|
|
223
229
|
selected={selected}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
230
|
+
disabled={loading}
|
|
231
|
+
onFocus={(e) => {
|
|
232
|
+
setFocusedFolder(focusedFolder || folder, true);
|
|
233
|
+
}}
|
|
234
|
+
onClick={handleClickFolder}
|
|
235
|
+
isCreatingFolder={isCreatingFolder}>
|
|
236
|
+
{!hideArrow && (
|
|
227
237
|
<OpenButton
|
|
238
|
+
aria-hidden
|
|
228
239
|
tabIndex={-1}
|
|
229
240
|
isOpen={isOpen}
|
|
230
|
-
onClick={() => {
|
|
231
|
-
|
|
241
|
+
onClick={(e) => {
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
setFocusedFolder(folder, true);
|
|
232
244
|
if (isOpen) {
|
|
233
245
|
onCloseFolder(id);
|
|
234
246
|
} else {
|
|
@@ -239,7 +251,14 @@ const FolderItem = ({
|
|
|
239
251
|
</OpenButton>
|
|
240
252
|
)}
|
|
241
253
|
<StyledName>{name}</StyledName>
|
|
242
|
-
|
|
254
|
+
{containsResource && (
|
|
255
|
+
<StyledDone
|
|
256
|
+
aria-label={t('myNdla.alreadyInFolder')}
|
|
257
|
+
id={`alreadyAdded-${folder.id}`}
|
|
258
|
+
title={t('myNdla.alreadyInFolder')}
|
|
259
|
+
/>
|
|
260
|
+
)}
|
|
261
|
+
</FolderName>
|
|
243
262
|
);
|
|
244
263
|
};
|
|
245
264
|
|
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import React from 'react';
|
|
9
|
+
import React, { ReactNode } from 'react';
|
|
10
10
|
import styled from '@emotion/styled';
|
|
11
11
|
import { animations } from '@ndla/core';
|
|
12
12
|
import FolderItem from './FolderItem';
|
|
13
13
|
import FolderNameInput from './FolderNameInput';
|
|
14
14
|
import { CommonFolderItemsProps, FolderType, TreeStructureType } from './types';
|
|
15
15
|
import NavigationLink from './NavigationLink';
|
|
16
|
+
import { treestructureId } from './helperFunctions';
|
|
16
17
|
|
|
17
18
|
const StyledUL = styled.ul`
|
|
18
19
|
${animations.fadeInLeft(animations.durations.fast)};
|
|
@@ -43,6 +44,8 @@ export interface FolderItemsProps extends CommonFolderItemsProps {
|
|
|
43
44
|
onCancelNewFolder: () => void;
|
|
44
45
|
onSaveNewFolder: (name: string, parentId: string) => void;
|
|
45
46
|
openFolders: string[];
|
|
47
|
+
parentFolder?: FolderType;
|
|
48
|
+
children?: ReactNode;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
const FolderItems = ({
|
|
@@ -54,15 +57,28 @@ const FolderItems = ({
|
|
|
54
57
|
onSaveNewFolder,
|
|
55
58
|
openFolders,
|
|
56
59
|
type,
|
|
60
|
+
parentFolder,
|
|
61
|
+
children,
|
|
57
62
|
...rest
|
|
58
63
|
}: FolderItemsProps) => (
|
|
59
|
-
<StyledUL
|
|
64
|
+
<StyledUL
|
|
65
|
+
id={
|
|
66
|
+
level === 0 && type === 'picker'
|
|
67
|
+
? treestructureId(type, 'popup')
|
|
68
|
+
: parentFolder
|
|
69
|
+
? treestructureId(type, `subfolders-${parentFolder.id}`)
|
|
70
|
+
: undefined
|
|
71
|
+
}
|
|
72
|
+
tabIndex={-1}
|
|
73
|
+
aria-labelledby={level === 0 && type === 'picker' ? treestructureId(type, 'label') : undefined}
|
|
74
|
+
role={level === 0 ? 'tree' : 'group'}>
|
|
75
|
+
{children}
|
|
60
76
|
{folders.map((folder) => {
|
|
61
77
|
const { subfolders, id } = folder;
|
|
62
78
|
const isOpen = openFolders?.includes(id);
|
|
63
79
|
|
|
64
80
|
return (
|
|
65
|
-
<StyledLI key={id} role="
|
|
81
|
+
<StyledLI key={id} tabIndex={-1} role="none" type={type}>
|
|
66
82
|
{folder.isNavigation ? (
|
|
67
83
|
<NavigationLink folder={folder} isOpen={isOpen} level={level} type={type} loading={loading} {...rest} />
|
|
68
84
|
) : (
|
|
@@ -73,20 +89,12 @@ const FolderItems = ({
|
|
|
73
89
|
level={level}
|
|
74
90
|
loading={loading}
|
|
75
91
|
type={type}
|
|
76
|
-
isCreatingFolder={newFolderParentId
|
|
92
|
+
isCreatingFolder={!!newFolderParentId}
|
|
77
93
|
{...rest}
|
|
78
94
|
/>
|
|
79
|
-
{newFolderParentId === id && (
|
|
80
|
-
<FolderNameInput
|
|
81
|
-
loading={loading}
|
|
82
|
-
level={level}
|
|
83
|
-
onCancelNewFolder={onCancelNewFolder}
|
|
84
|
-
onSaveNewFolder={onSaveNewFolder}
|
|
85
|
-
parentId={newFolderParentId}
|
|
86
|
-
/>
|
|
87
|
-
)}
|
|
88
|
-
{subfolders && isOpen && (
|
|
95
|
+
{((subfolders && isOpen) || newFolderParentId === id) && (
|
|
89
96
|
<FolderItems
|
|
97
|
+
parentFolder={folder}
|
|
90
98
|
folders={subfolders}
|
|
91
99
|
level={level + 1}
|
|
92
100
|
loading={loading}
|
|
@@ -95,8 +103,20 @@ const FolderItems = ({
|
|
|
95
103
|
onCancelNewFolder={onCancelNewFolder}
|
|
96
104
|
onSaveNewFolder={onSaveNewFolder}
|
|
97
105
|
openFolders={openFolders}
|
|
98
|
-
{...rest}
|
|
99
|
-
|
|
106
|
+
{...rest}>
|
|
107
|
+
{newFolderParentId === id && (
|
|
108
|
+
<li role="none">
|
|
109
|
+
<FolderNameInput
|
|
110
|
+
loading={loading}
|
|
111
|
+
level={level}
|
|
112
|
+
onCancelNewFolder={onCancelNewFolder}
|
|
113
|
+
onSaveNewFolder={onSaveNewFolder}
|
|
114
|
+
parentId={newFolderParentId}
|
|
115
|
+
type={type}
|
|
116
|
+
/>
|
|
117
|
+
</li>
|
|
118
|
+
)}
|
|
119
|
+
</FolderItems>
|
|
100
120
|
)}
|
|
101
121
|
</>
|
|
102
122
|
)}
|
|
@@ -15,6 +15,9 @@ import { Spinner } from '@ndla/icons';
|
|
|
15
15
|
import { IconButton } from '@ndla/button';
|
|
16
16
|
import { Cross } from '@ndla/icons/action';
|
|
17
17
|
import { Done } from '@ndla/icons/editor';
|
|
18
|
+
import { InputV2 as Input } from '@ndla/forms';
|
|
19
|
+
import { TreeStructureType } from './types';
|
|
20
|
+
import { treestructureId } from './helperFunctions';
|
|
18
21
|
|
|
19
22
|
// Source: https://kovart.github.io/dashed-border-generator/
|
|
20
23
|
const borderStyle = `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='${encodeURIComponent(
|
|
@@ -57,7 +60,7 @@ const InputWrapper = styled.div<{ level: number }>`
|
|
|
57
60
|
}
|
|
58
61
|
`;
|
|
59
62
|
|
|
60
|
-
const StyledInput = styled
|
|
63
|
+
const StyledInput = styled(Input)`
|
|
61
64
|
padding: ${spacing.small};
|
|
62
65
|
flex-grow: 1;
|
|
63
66
|
border: 0;
|
|
@@ -74,54 +77,65 @@ interface FolderNameInputProps {
|
|
|
74
77
|
onCancelNewFolder: () => void;
|
|
75
78
|
loading?: boolean;
|
|
76
79
|
level: number;
|
|
80
|
+
type: TreeStructureType;
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
const FolderNameInput = ({
|
|
83
|
+
const FolderNameInput = ({
|
|
84
|
+
onSaveNewFolder,
|
|
85
|
+
parentId,
|
|
86
|
+
onCancelNewFolder,
|
|
87
|
+
loading,
|
|
88
|
+
level,
|
|
89
|
+
type,
|
|
90
|
+
}: FolderNameInputProps) => {
|
|
80
91
|
const { t } = useTranslation();
|
|
81
92
|
const [name, setName] = useState<string>('');
|
|
82
93
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
83
94
|
|
|
84
95
|
useEffect(() => {
|
|
85
|
-
inputRef.current?.select();
|
|
86
96
|
if (isMobile) {
|
|
87
97
|
inputRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
88
98
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
92
|
-
}, [onCancelNewFolder]);
|
|
99
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
100
|
+
}, []);
|
|
93
101
|
|
|
94
102
|
return (
|
|
95
103
|
<NewFolderWrapper>
|
|
96
104
|
<InputWrapper level={level}>
|
|
97
105
|
<StyledInput
|
|
106
|
+
name="name"
|
|
107
|
+
labelHidden
|
|
108
|
+
label={t('treeStructure.newFolder.folderName')}
|
|
109
|
+
aria-invalid={name.length === 0}
|
|
110
|
+
aria-disabled={loading ? true : undefined}
|
|
111
|
+
aria-describedby={loading ? treestructureId(type, 'spinner') : undefined}
|
|
98
112
|
ref={inputRef}
|
|
99
113
|
autoFocus
|
|
100
114
|
placeholder={t('treeStructure.newFolder.placeholder')}
|
|
101
|
-
disabled={loading}
|
|
102
115
|
value={name}
|
|
103
116
|
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
104
117
|
if (e.key === 'Escape') {
|
|
105
118
|
e.preventDefault();
|
|
106
119
|
onCancelNewFolder();
|
|
107
|
-
} else if (e.key === 'Enter'
|
|
120
|
+
} else if (e.key === 'Enter') {
|
|
108
121
|
e.preventDefault();
|
|
109
|
-
if (name === '') {
|
|
110
|
-
onCancelNewFolder();
|
|
122
|
+
if (name === '' || loading) {
|
|
111
123
|
return;
|
|
112
124
|
}
|
|
113
125
|
onSaveNewFolder(name, parentId);
|
|
114
126
|
}
|
|
115
127
|
}}
|
|
116
|
-
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
128
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
129
|
+
if (!loading) {
|
|
130
|
+
setName(e.target.value);
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
117
133
|
/>
|
|
118
134
|
<Row>
|
|
119
|
-
{!loading
|
|
135
|
+
{!loading && (
|
|
120
136
|
<>
|
|
121
|
-
<IconButton aria-label={t('close')} title={t('close')} size="small" ghostPill onClick={onCancelNewFolder}>
|
|
122
|
-
<Cross />
|
|
123
|
-
</IconButton>
|
|
124
137
|
<IconButton
|
|
138
|
+
tabIndex={0}
|
|
125
139
|
aria-label={t('save')}
|
|
126
140
|
title={t('save')}
|
|
127
141
|
size="small"
|
|
@@ -129,10 +143,21 @@ const FolderNameInput = ({ onSaveNewFolder, parentId, onCancelNewFolder, loading
|
|
|
129
143
|
onClick={() => onSaveNewFolder(name, parentId)}>
|
|
130
144
|
<Done />
|
|
131
145
|
</IconButton>
|
|
146
|
+
<IconButton aria-label={t('close')} title={t('close')} size="small" ghostPill onClick={onCancelNewFolder}>
|
|
147
|
+
<Cross />
|
|
148
|
+
</IconButton>
|
|
132
149
|
</>
|
|
133
|
-
) : (
|
|
134
|
-
<Spinner size="small" margin="0" />
|
|
135
150
|
)}
|
|
151
|
+
<div aria-live="assertive">
|
|
152
|
+
{loading && (
|
|
153
|
+
<Spinner
|
|
154
|
+
size="normal"
|
|
155
|
+
margin={spacing.small}
|
|
156
|
+
id={treestructureId(type, 'spinner')}
|
|
157
|
+
aria-label={t('loading')}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
136
161
|
</Row>
|
|
137
162
|
</InputWrapper>
|
|
138
163
|
</NewFolderWrapper>
|
|
@@ -55,9 +55,9 @@ const NavigationLink = ({
|
|
|
55
55
|
loading,
|
|
56
56
|
folder,
|
|
57
57
|
selectedFolder,
|
|
58
|
-
|
|
58
|
+
focusedFolder,
|
|
59
59
|
setSelectedFolder,
|
|
60
|
-
|
|
60
|
+
setFocusedFolder,
|
|
61
61
|
visibleFolders,
|
|
62
62
|
onOpenFolder,
|
|
63
63
|
onCloseFolder,
|
|
@@ -65,30 +65,37 @@ const NavigationLink = ({
|
|
|
65
65
|
const { id, icon, name } = folder;
|
|
66
66
|
const selected = selectedFolder && selectedFolder.id === id;
|
|
67
67
|
const ref = useRef<HTMLButtonElement & HTMLAnchorElement>(null);
|
|
68
|
-
const focused =
|
|
68
|
+
const focused = focusedFolder?.id === id;
|
|
69
69
|
|
|
70
70
|
const handleClick = () => {
|
|
71
71
|
if (!selected) {
|
|
72
72
|
setSelectedFolder(folder);
|
|
73
|
-
|
|
73
|
+
setFocusedFolder(folder);
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
useEffect(() => {
|
|
78
|
-
if (
|
|
78
|
+
if (focusedFolder?.id === id) {
|
|
79
79
|
ref.current?.focus();
|
|
80
80
|
}
|
|
81
|
-
}, [
|
|
81
|
+
}, [focusedFolder, ref, id]);
|
|
82
82
|
|
|
83
83
|
return (
|
|
84
84
|
<StyledSafeLink
|
|
85
|
+
role="treeitem"
|
|
85
86
|
ref={ref}
|
|
86
|
-
onKeyDown={(e: KeyboardEvent<HTMLElement>) =>
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
onKeyDown={(e: KeyboardEvent<HTMLElement>) => {
|
|
88
|
+
if (e.key === 'Enter') {
|
|
89
|
+
setSelectedFolder(folder);
|
|
90
|
+
setFocusedFolder(folder);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
arrowNavigation(e, id, visibleFolders, setFocusedFolder, onOpenFolder, onCloseFolder);
|
|
94
|
+
}}
|
|
95
|
+
aria-current={selected ? 'page' : undefined}
|
|
89
96
|
tabIndex={selected || focused ? 0 : -1}
|
|
90
97
|
selected={selected}
|
|
91
|
-
onFocus={() =>
|
|
98
|
+
onFocus={() => setFocusedFolder(folder)}
|
|
92
99
|
onClick={handleClick}
|
|
93
100
|
to={loading ? '' : `/minndla/${id}`}>
|
|
94
101
|
<IconWrapper>{icon}</IconWrapper>
|