@ndla/ui 56.0.13-alpha.0 → 56.0.15-alpha.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/dist/panda.buildinfo.json +7 -2
- package/dist/styles.css +24 -4
- package/es/Article/Article.js +4 -1
- package/es/ContentTypeBadge/ContentTypeBadge.js +2 -0
- package/es/TreeStructure/TreeStructure.js +292 -181
- package/es/TreeStructure/helperFunctions.js +0 -3
- package/es/TreeStructure/index.js +1 -2
- package/es/index.js +0 -1
- package/es/locale/messages-en.js +1 -1
- package/es/locale/messages-nb.js +1 -1
- package/es/locale/messages-nn.js +1 -1
- package/es/locale/messages-se.js +1 -1
- package/es/locale/messages-sma.js +1 -1
- package/es/styles.css +24 -4
- package/lib/Article/Article.js +4 -1
- package/lib/ContentTypeBadge/ContentTypeBadge.js +2 -0
- package/lib/TreeStructure/TreeStructure.d.ts +7 -6
- package/lib/TreeStructure/TreeStructure.js +293 -180
- package/lib/TreeStructure/helperFunctions.d.ts +0 -2
- package/lib/TreeStructure/helperFunctions.js +2 -6
- package/lib/TreeStructure/index.d.ts +1 -2
- package/lib/TreeStructure/index.js +2 -3
- package/lib/TreeStructure/types.d.ts +4 -22
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -7
- package/lib/locale/messages-en.js +1 -1
- package/lib/locale/messages-nb.js +1 -1
- package/lib/locale/messages-nn.js +1 -1
- package/lib/locale/messages-se.js +1 -1
- package/lib/locale/messages-sma.js +1 -1
- package/lib/styles.css +24 -4
- package/package.json +7 -8
- package/src/Article/Article.tsx +4 -1
- package/src/ContentTypeBadge/ContentTypeBadge.tsx +2 -0
- package/src/TreeStructure/TreeStructure.stories.tsx +38 -68
- package/src/TreeStructure/TreeStructure.tsx +307 -194
- package/src/TreeStructure/helperFunctions.ts +0 -5
- package/src/TreeStructure/index.ts +1 -2
- package/src/TreeStructure/types.ts +4 -25
- package/src/index.ts +0 -3
- package/src/locale/messages-en.ts +1 -1
- package/src/locale/messages-nb.ts +1 -1
- package/src/locale/messages-nn.ts +1 -1
- package/src/locale/messages-se.ts +1 -1
- package/src/locale/messages-sma.ts +1 -1
- package/es/ProgrammeCard/ProgrammeCard.js +0 -51
- package/es/ProgrammeCard/index.js +0 -9
- package/es/TreeStructure/AddFolderButton.js +0 -80
- package/es/TreeStructure/ComboboxButton.js +0 -127
- package/es/TreeStructure/FolderItem.js +0 -225
- package/es/TreeStructure/FolderItems.js +0 -95
- package/es/TreeStructure/arrowNavigation.js +0 -35
- package/lib/ProgrammeCard/ProgrammeCard.d.ts +0 -23
- package/lib/ProgrammeCard/ProgrammeCard.js +0 -58
- package/lib/ProgrammeCard/index.d.ts +0 -9
- package/lib/ProgrammeCard/index.js +0 -13
- package/lib/TreeStructure/AddFolderButton.d.ts +0 -17
- package/lib/TreeStructure/AddFolderButton.js +0 -85
- package/lib/TreeStructure/ComboboxButton.d.ts +0 -27
- package/lib/TreeStructure/ComboboxButton.js +0 -134
- package/lib/TreeStructure/FolderItem.d.ts +0 -17
- package/lib/TreeStructure/FolderItem.js +0 -230
- package/lib/TreeStructure/FolderItems.d.ts +0 -22
- package/lib/TreeStructure/FolderItems.js +0 -100
- package/lib/TreeStructure/arrowNavigation.d.ts +0 -10
- package/lib/TreeStructure/arrowNavigation.js +0 -42
- package/src/ProgrammeCard/ProgrammeCard.stories.tsx +0 -35
- package/src/ProgrammeCard/ProgrammeCard.tsx +0 -78
- package/src/ProgrammeCard/index.tsx +0 -10
- package/src/TreeStructure/AddFolderButton.tsx +0 -79
- package/src/TreeStructure/ComboboxButton.tsx +0 -172
- package/src/TreeStructure/FolderItem.tsx +0 -307
- package/src/TreeStructure/FolderItems.tsx +0 -121
- package/src/TreeStructure/arrowNavigation.ts +0 -54
|
@@ -1,65 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) 2024-present, NDLA.
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the GPLv3 license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
9
|
+
import { KeyboardEvent, useCallback, useMemo, useRef, useState } from "react";
|
|
10
|
+
import { flushSync } from "react-dom";
|
|
11
|
+
import { useTranslation } from "react-i18next";
|
|
12
|
+
import { useTreeView, usePopoverContext, type PopoverOpenChangeDetails } from "@ark-ui/react";
|
|
13
|
+
import { AddLine, HeartFill } from "@ndla/icons/action";
|
|
14
|
+
import { ArrowDownShortLine, ArrowRightShortLine } from "@ndla/icons/common";
|
|
15
|
+
import { FolderUserLine } from "@ndla/icons/contentType";
|
|
16
|
+
import { FolderLine } from "@ndla/icons/editor";
|
|
17
|
+
import {
|
|
18
|
+
Button,
|
|
19
|
+
IconButton,
|
|
20
|
+
PopoverContent,
|
|
21
|
+
PopoverRoot,
|
|
22
|
+
PopoverTrigger,
|
|
23
|
+
Tree,
|
|
24
|
+
TreeBranch,
|
|
25
|
+
TreeBranchContent,
|
|
26
|
+
TreeBranchControl,
|
|
27
|
+
TreeBranchIndicator,
|
|
28
|
+
TreeBranchText,
|
|
29
|
+
TreeBranchTrigger,
|
|
30
|
+
TreeItem,
|
|
31
|
+
TreeItemText,
|
|
32
|
+
TreeLabel,
|
|
33
|
+
TreeRootProvider,
|
|
34
|
+
} from "@ndla/primitives";
|
|
35
|
+
import { HStack, Stack, styled } from "@ndla/styled-system/jsx";
|
|
36
|
+
import { IFolder, IResource } from "@ndla/types-backend/myndla-api";
|
|
37
|
+
import { flattenFolders } from "./helperFunctions";
|
|
38
|
+
import { NewFolderInputFunc } from "./types";
|
|
18
39
|
|
|
19
40
|
export const MAX_LEVEL_FOR_FOLDERS = 5;
|
|
20
41
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
font-weight: ${fonts.weight.semibold};
|
|
25
|
-
`;
|
|
26
|
-
|
|
27
|
-
const StyledTreeStructure = styled.div`
|
|
28
|
-
flex: 1;
|
|
29
|
-
display: flex;
|
|
30
|
-
flex-direction: column;
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
const Row = styled.div`
|
|
34
|
-
display: flex;
|
|
35
|
-
align-items: center;
|
|
36
|
-
justify-content: space-between;
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
const TreeStructureWrapper = styled.div`
|
|
40
|
-
display: flex;
|
|
41
|
-
flex-direction: column;
|
|
42
|
-
transition: ${misc.transition.default};
|
|
43
|
-
&[data-type="picker"] {
|
|
44
|
-
overflow: hidden;
|
|
45
|
-
border: 1px solid ${colors.brand.neutral7};
|
|
46
|
-
border-radius: ${misc.borderRadius};
|
|
47
|
-
scroll-behavior: smooth;
|
|
48
|
-
}
|
|
49
|
-
&:focus-within {
|
|
50
|
-
border-color: ${colors.brand.tertiary};
|
|
51
|
-
}
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
const ScrollableDiv = styled.div`
|
|
55
|
-
&[data-type="picker"] {
|
|
56
|
-
overflow: auto;
|
|
57
|
-
overflow: overlay;
|
|
58
|
-
${utils.scrollbar}
|
|
59
|
-
}
|
|
60
|
-
`;
|
|
61
|
-
|
|
62
|
-
export interface TreeStructureProps extends CommonTreeStructureProps {
|
|
42
|
+
export interface TreeStructureProps {
|
|
43
|
+
loading?: boolean;
|
|
44
|
+
targetResource?: IResource;
|
|
63
45
|
defaultOpenFolders?: string[];
|
|
64
46
|
folders: IFolder[];
|
|
65
47
|
label?: string;
|
|
@@ -69,174 +51,305 @@ export interface TreeStructureProps extends CommonTreeStructureProps {
|
|
|
69
51
|
ariaDescribedby?: string;
|
|
70
52
|
}
|
|
71
53
|
|
|
72
|
-
const
|
|
73
|
-
|
|
54
|
+
const StyledButton = styled(Button, {
|
|
55
|
+
base: {
|
|
56
|
+
width: "100%",
|
|
57
|
+
justifyContent: "space-between",
|
|
58
|
+
"& span": {
|
|
59
|
+
overflow: "hidden",
|
|
60
|
+
textOverflow: "ellipsis",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const StyledHStack = styled(HStack, {
|
|
66
|
+
base: {
|
|
67
|
+
overflow: "hidden",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const StyledHeartFill = styled(HeartFill, {
|
|
72
|
+
base: {
|
|
73
|
+
color: "icon.strong",
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const StyledFolderLine = styled(FolderLine, {
|
|
78
|
+
base: {
|
|
79
|
+
color: "icon.strong",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const StyledFolderUserLine = styled(FolderUserLine, {
|
|
84
|
+
base: {
|
|
85
|
+
color: "icon.strong",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const StyledTreeRootProvider = styled(TreeRootProvider, {
|
|
90
|
+
base: {
|
|
91
|
+
width: "100%",
|
|
92
|
+
maxHeight: "inherit",
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const StyledPopoverContent = styled(PopoverContent, {
|
|
97
|
+
base: {
|
|
98
|
+
display: "flex",
|
|
99
|
+
flexDirection: "column",
|
|
100
|
+
gap: "xsmall",
|
|
101
|
+
overflow: "auto",
|
|
102
|
+
maxHeight: "inherit",
|
|
103
|
+
paddingInline: "xsmall",
|
|
104
|
+
paddingBlock: "xsmall",
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const LabelHStack = styled(HStack, {
|
|
109
|
+
base: {
|
|
110
|
+
width: "100%",
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const TreeStructure = ({
|
|
74
115
|
folders,
|
|
116
|
+
defaultOpenFolders,
|
|
117
|
+
newFolderInput,
|
|
75
118
|
label,
|
|
119
|
+
targetResource,
|
|
76
120
|
loading,
|
|
77
121
|
maxLevel = MAX_LEVEL_FOR_FOLDERS,
|
|
78
122
|
onSelectFolder,
|
|
79
|
-
targetResource,
|
|
80
|
-
type,
|
|
81
|
-
newFolderInput,
|
|
82
123
|
ariaDescribedby,
|
|
83
124
|
}: TreeStructureProps) => {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const
|
|
125
|
+
const [open, setOpen] = useState(false);
|
|
126
|
+
const [selectedValue, setSelectedValue] = useState(defaultOpenFolders?.[defaultOpenFolders?.length - 1] ?? "");
|
|
127
|
+
const [expandedValue, setExpandedValue] = useState<string[]>(defaultOpenFolders ?? []);
|
|
128
|
+
const [focusedValue, setFocusedValue] = useState<string | null>(selectedValue);
|
|
129
|
+
const [newFolderParentId, setNewFolderParentId] = useState<string | null>(null);
|
|
130
|
+
const newFolderButtonRef = useRef<HTMLButtonElement>(null);
|
|
131
|
+
const { t } = useTranslation();
|
|
132
|
+
const rootFolderIds = useMemo(() => folders.map((folder) => folder.id), [folders]);
|
|
133
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
87
134
|
|
|
88
|
-
const
|
|
135
|
+
const selectedFolder = useMemo(() => {
|
|
136
|
+
return flattenFolders(folders).find((folder) => folder.id === selectedValue);
|
|
137
|
+
}, [folders, selectedValue]);
|
|
89
138
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const [showTree, setShowTree] = useState(type === "navigation");
|
|
139
|
+
const disableCreateFolder = useMemo(() => {
|
|
140
|
+
return (selectedFolder?.breadcrumbs.length ?? 0) > maxLevel - 1;
|
|
141
|
+
}, [maxLevel, selectedFolder?.breadcrumbs.length]);
|
|
94
142
|
|
|
95
|
-
const
|
|
143
|
+
const onOpenChange = useCallback((details: PopoverOpenChangeDetails) => {
|
|
144
|
+
setOpen(details.open);
|
|
145
|
+
if (!details.open) {
|
|
146
|
+
setNewFolderParentId(null);
|
|
147
|
+
}
|
|
148
|
+
}, []);
|
|
96
149
|
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return uniq(defaultOpenFolders.concat(prev));
|
|
102
|
-
});
|
|
103
|
-
}
|
|
150
|
+
const onKeyDown = useCallback((e: KeyboardEvent<HTMLElement>) => {
|
|
151
|
+
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
setOpen(true);
|
|
104
154
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
const onShowInput = useCallback(() => {
|
|
158
|
+
if (disableCreateFolder) return;
|
|
159
|
+
const flattenedFolders = flattenFolders(folders);
|
|
160
|
+
const folder = flattenedFolders.find((folder) => folder.id === selectedValue);
|
|
161
|
+
const newExpandedIds = rootFolderIds.concat(folder?.breadcrumbs.map((bc) => bc.id) ?? []);
|
|
162
|
+
setOpen(true);
|
|
163
|
+
setExpandedValue((prev) => Array.from(new Set([...prev, ...newExpandedIds])));
|
|
164
|
+
setNewFolderParentId(selectedValue);
|
|
165
|
+
}, [disableCreateFolder, folders, rootFolderIds, selectedValue]);
|
|
166
|
+
|
|
167
|
+
const treeView = useTreeView({
|
|
168
|
+
focusedValue,
|
|
169
|
+
onFocusChange: (details) => !!details.focusedValue && setFocusedValue(details.focusedValue),
|
|
170
|
+
expandedValue,
|
|
171
|
+
onExpandedChange: (details) => setExpandedValue(details.expandedValue),
|
|
172
|
+
selectedValue: [selectedValue],
|
|
173
|
+
onSelectionChange: (details) => {
|
|
174
|
+
// TODO: This is currently a bug in zag. The TreeView component simply expects the already selected value to remain selected. As such, always choose the "last" selected value.
|
|
175
|
+
const val = details.selectedValue[details.selectedValue.length - 1];
|
|
176
|
+
if (!val) return;
|
|
177
|
+
if (val === selectedValue && details.focusedValue === selectedValue) {
|
|
178
|
+
setOpen(false);
|
|
179
|
+
return;
|
|
116
180
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
181
|
+
setSelectedValue(val);
|
|
182
|
+
onSelectFolder?.(val);
|
|
183
|
+
},
|
|
184
|
+
expandOnClick: false,
|
|
185
|
+
});
|
|
120
186
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
187
|
+
const onCreateFolder = useCallback(
|
|
188
|
+
(folder: IFolder | undefined) => {
|
|
189
|
+
if (!folder) return;
|
|
190
|
+
const focus = treeView.focusItem;
|
|
191
|
+
const expand = treeView.expand;
|
|
192
|
+
const select = treeView.select;
|
|
193
|
+
flushSync(() => {
|
|
194
|
+
setOpen(true);
|
|
195
|
+
});
|
|
196
|
+
flushSync(() => {
|
|
197
|
+
expand(folder.breadcrumbs.map((bc) => bc.id));
|
|
198
|
+
});
|
|
199
|
+
flushSync(() => {
|
|
200
|
+
select([folder.id]);
|
|
201
|
+
});
|
|
202
|
+
flushSync(() => {
|
|
203
|
+
focus(folder.id);
|
|
204
|
+
});
|
|
205
|
+
setNewFolderParentId(null);
|
|
206
|
+
},
|
|
207
|
+
[treeView.expand, treeView.focusItem, treeView.select],
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const onAnimationEnd = useCallback(() => {
|
|
211
|
+
if (open && focusedValue) {
|
|
212
|
+
document.getElementById(focusedValue)?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
125
213
|
}
|
|
126
|
-
};
|
|
214
|
+
}, [focusedValue, open]);
|
|
215
|
+
|
|
216
|
+
const onCancelFolder = useCallback(() => {
|
|
217
|
+
if (!selectedFolder) return;
|
|
218
|
+
const focusFunc = selectedFolder.subfolders.length ? treeView.focusBranch : treeView.focusItem;
|
|
219
|
+
focusFunc(selectedFolder.id);
|
|
220
|
+
setNewFolderParentId(null);
|
|
221
|
+
}, [selectedFolder, treeView.focusBranch, treeView.focusItem]);
|
|
127
222
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
223
|
+
const addTooltip = loading
|
|
224
|
+
? t("loading")
|
|
225
|
+
: disableCreateFolder
|
|
226
|
+
? t("treeStructure.maxFoldersAlreadyAdded")
|
|
227
|
+
: t("myNdla.newFolderUnder", { folderName: selectedFolder?.name });
|
|
132
228
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
229
|
+
return (
|
|
230
|
+
<StyledTreeRootProvider value={treeView} asChild {...treeView.getRootProps()}>
|
|
231
|
+
<Stack align="flex-end">
|
|
232
|
+
<LabelHStack gap="xsmall" justify="space-between">
|
|
233
|
+
<TreeLabel>{label}</TreeLabel>
|
|
234
|
+
<Button
|
|
235
|
+
size="small"
|
|
236
|
+
variant="tertiary"
|
|
237
|
+
ref={newFolderButtonRef}
|
|
238
|
+
aria-disabled={disableCreateFolder}
|
|
239
|
+
title={addTooltip}
|
|
240
|
+
aria-label={addTooltip}
|
|
241
|
+
loading={loading}
|
|
242
|
+
onClick={onShowInput}
|
|
243
|
+
>
|
|
244
|
+
<AddLine />
|
|
245
|
+
{t("myNdla.newFolder")}
|
|
246
|
+
</Button>
|
|
247
|
+
</LabelHStack>
|
|
248
|
+
<PopoverRoot
|
|
249
|
+
open={open}
|
|
250
|
+
positioning={{ sameWidth: true }}
|
|
251
|
+
onOpenChange={onOpenChange}
|
|
252
|
+
persistentElements={[() => newFolderButtonRef.current]}
|
|
253
|
+
initialFocusEl={() => contentRef.current?.querySelector("input") ?? null}
|
|
254
|
+
>
|
|
255
|
+
<PopoverTrigger asChild>
|
|
256
|
+
<StyledButton
|
|
257
|
+
variant="secondary"
|
|
258
|
+
onKeyDown={onKeyDown}
|
|
259
|
+
aria-haspopup="tree"
|
|
260
|
+
role="combobox"
|
|
261
|
+
aria-describedby={ariaDescribedby}
|
|
262
|
+
aria-activedescendant={focusedValue ?? undefined}
|
|
263
|
+
>
|
|
264
|
+
<span>{selectedFolder?.name}</span>
|
|
265
|
+
<ArrowDownShortLine />
|
|
266
|
+
</StyledButton>
|
|
267
|
+
</PopoverTrigger>
|
|
268
|
+
<StyledPopoverContent onAnimationEnd={onAnimationEnd} ref={contentRef}>
|
|
269
|
+
{!!newFolderParentId &&
|
|
270
|
+
newFolderInput?.({ parentId: newFolderParentId, onCreate: onCreateFolder, onCancel: onCancelFolder })}
|
|
271
|
+
<Tree>
|
|
272
|
+
{folders.map((folder) => (
|
|
273
|
+
<TreeStructureItem key={folder.id} folder={folder} targetResource={targetResource} />
|
|
274
|
+
))}
|
|
275
|
+
</Tree>
|
|
276
|
+
</StyledPopoverContent>
|
|
277
|
+
</PopoverRoot>
|
|
278
|
+
</Stack>
|
|
279
|
+
</StyledTreeRootProvider>
|
|
280
|
+
);
|
|
281
|
+
};
|
|
136
282
|
|
|
137
|
-
|
|
138
|
-
|
|
283
|
+
interface TreeStructureItemProps {
|
|
284
|
+
folder: IFolder;
|
|
285
|
+
targetResource?: IResource;
|
|
286
|
+
}
|
|
139
287
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
};
|
|
288
|
+
const TreeStructureItem = ({ folder, targetResource }: TreeStructureItemProps) => {
|
|
289
|
+
const { t } = useTranslation();
|
|
290
|
+
const { setOpen } = usePopoverContext();
|
|
291
|
+
const containsResource =
|
|
292
|
+
targetResource && folder.resources.some((resource) => resource.resourceId === targetResource.resourceId);
|
|
143
293
|
|
|
144
|
-
const
|
|
145
|
-
const closedFolder = flattenedFolders.find((folder) => folder.id === id);
|
|
294
|
+
const FolderIcon = folder.status === "shared" ? StyledFolderUserLine : StyledFolderLine;
|
|
146
295
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
296
|
+
// TODO: Pressing enter selects the item and closes the popover immediately. Do we actually want this? Old behavior.
|
|
297
|
+
const onKeyDown = useCallback(
|
|
298
|
+
(e: KeyboardEvent<HTMLElement>) => {
|
|
299
|
+
if (e.key === "Enter") {
|
|
300
|
+
setOpen(false);
|
|
151
301
|
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const onNewFolderCreated = (newFolder: IFolder | undefined, parentId: string) => {
|
|
157
|
-
if (newFolder) {
|
|
158
|
-
setSelectedFolder(newFolder);
|
|
159
|
-
setFocusedFolder(newFolder);
|
|
160
|
-
setOpenFolders(uniq(openFolders.concat(parentId)));
|
|
161
|
-
setNewFolderParentId?.(undefined);
|
|
162
|
-
ref.current?.focus({ preventScroll: true });
|
|
163
|
-
}
|
|
164
|
-
};
|
|
302
|
+
},
|
|
303
|
+
[setOpen],
|
|
304
|
+
);
|
|
165
305
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
306
|
+
if (!folder.subfolders.length) {
|
|
307
|
+
return (
|
|
308
|
+
<TreeItem key={folder.id} value={folder.id} onKeyDown={onKeyDown} id={folder.id}>
|
|
309
|
+
<StyledHStack gap="xsmall" justify="space-between">
|
|
310
|
+
<StyledHStack gap="xxsmall" justify="center">
|
|
311
|
+
<FolderIcon />
|
|
312
|
+
<TreeItemText>{folder.name}</TreeItemText>
|
|
313
|
+
</StyledHStack>
|
|
314
|
+
{containsResource && <StyledHeartFill title={t("myNdla.alreadyInFolder")} />}
|
|
315
|
+
</StyledHStack>
|
|
316
|
+
</TreeItem>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
170
319
|
|
|
171
|
-
const
|
|
320
|
+
const ariaLabel = folder.status === "shared" ? `${folder.name}. ${t("myNdla.folder.sharing.shared")}` : folder.name;
|
|
172
321
|
|
|
173
322
|
return (
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
<ComboboxButton
|
|
196
|
-
ref={ref}
|
|
197
|
-
showTree={showTree}
|
|
198
|
-
type={type}
|
|
199
|
-
label={label}
|
|
200
|
-
loading={loading}
|
|
201
|
-
focusedFolder={focusedFolder}
|
|
202
|
-
selectedFolder={selectedFolder}
|
|
203
|
-
setSelectedFolder={setSelectedFolder}
|
|
204
|
-
setFocusedFolder={setFocusedFolder}
|
|
205
|
-
onToggleTree={onToggleTree}
|
|
206
|
-
flattenedFolders={flattenedFolders}
|
|
207
|
-
onCloseFolder={onCloseFolder}
|
|
208
|
-
onOpenFolder={onOpenFolder}
|
|
209
|
-
ariaDescribedby={ariaDescribedby}
|
|
210
|
-
/>
|
|
211
|
-
)}
|
|
212
|
-
{showTree && (
|
|
213
|
-
<ScrollableDiv data-type={type}>
|
|
214
|
-
<FolderItems
|
|
215
|
-
focusedFolder={focusedFolder}
|
|
216
|
-
folders={folders}
|
|
217
|
-
level={0}
|
|
218
|
-
loading={loading}
|
|
219
|
-
selectedFolder={selectedFolder}
|
|
220
|
-
maxLevel={maxLevel}
|
|
221
|
-
newFolderParentId={newFolderParentId}
|
|
222
|
-
onCancelNewFolder={onCancelNewFolder}
|
|
223
|
-
onCloseFolder={onCloseFolder}
|
|
224
|
-
onOpenFolder={onOpenFolder}
|
|
225
|
-
openFolders={openFolders}
|
|
226
|
-
setFocusedFolder={setFocusedFolder}
|
|
227
|
-
setSelectedFolder={setSelectedFolder}
|
|
228
|
-
targetResource={targetResource}
|
|
229
|
-
visibleFolders={flattenedFolders}
|
|
230
|
-
type={type}
|
|
231
|
-
closeTree={() => onToggleTree(false)}
|
|
232
|
-
newFolderInput={newFolderInput}
|
|
233
|
-
onCreate={onNewFolderCreated}
|
|
323
|
+
<TreeBranch key={folder.id} value={folder.id} id={folder.id}>
|
|
324
|
+
<TreeBranchControl onKeyDown={onKeyDown} asChild>
|
|
325
|
+
<StyledHStack gap="xsmall" justify="space-between">
|
|
326
|
+
<StyledHStack gap="xxsmall" justify="center">
|
|
327
|
+
<IconButton variant="clear" asChild>
|
|
328
|
+
<TreeBranchTrigger>
|
|
329
|
+
<TreeBranchIndicator asChild>
|
|
330
|
+
<ArrowRightShortLine />
|
|
331
|
+
</TreeBranchIndicator>
|
|
332
|
+
</TreeBranchTrigger>
|
|
333
|
+
</IconButton>
|
|
334
|
+
<FolderIcon />
|
|
335
|
+
<TreeBranchText aria-label={ariaLabel} title={ariaLabel}>
|
|
336
|
+
{folder.name}
|
|
337
|
+
</TreeBranchText>
|
|
338
|
+
</StyledHStack>
|
|
339
|
+
{containsResource && (
|
|
340
|
+
<StyledHeartFill
|
|
341
|
+
title={t("myNdla.alreadyInFolder")}
|
|
342
|
+
aria-label={t("myNdla.alreadyInFolder")}
|
|
343
|
+
aria-hidden={false}
|
|
234
344
|
/>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
</
|
|
238
|
-
|
|
345
|
+
)}
|
|
346
|
+
</StyledHStack>
|
|
347
|
+
</TreeBranchControl>
|
|
348
|
+
<TreeBranchContent>
|
|
349
|
+
{folder.subfolders.map((subfolder) => (
|
|
350
|
+
<TreeStructureItem key={subfolder.id} folder={subfolder} targetResource={targetResource} />
|
|
351
|
+
))}
|
|
352
|
+
</TreeBranchContent>
|
|
353
|
+
</TreeBranch>
|
|
239
354
|
);
|
|
240
355
|
};
|
|
241
|
-
|
|
242
|
-
export default TreeStructure;
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { IFolder } from "@ndla/types-backend/myndla-api";
|
|
10
|
-
import { TreeStructureType } from "./types";
|
|
11
10
|
|
|
12
11
|
export const flattenFolders = (folders: IFolder[], openFolders?: string[]): IFolder[] => {
|
|
13
12
|
return folders.reduce((acc, { subfolders, id, ...rest }) => {
|
|
@@ -17,7 +16,3 @@ export const flattenFolders = (folders: IFolder[], openFolders?: string[]): IFol
|
|
|
17
16
|
return acc.concat({ subfolders, id, ...rest }, flattenFolders(subfolders, openFolders));
|
|
18
17
|
}, [] as IFolder[]);
|
|
19
18
|
};
|
|
20
|
-
|
|
21
|
-
export const treestructureId = (type: TreeStructureType, modifier: string) => {
|
|
22
|
-
return `${type}-treestructure-${modifier}`;
|
|
23
|
-
};
|
|
@@ -7,37 +7,16 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { ReactNode } from "react";
|
|
10
|
-
import { IFolder
|
|
10
|
+
import { IFolder } from "@ndla/types-backend/myndla-api";
|
|
11
11
|
|
|
12
|
-
export type
|
|
13
|
-
|
|
14
|
-
export type OnCreatedFunc = (folder: IFolder | undefined, parentId: string) => void;
|
|
12
|
+
export type OnCreatedFunc = (folder: IFolder | undefined) => void;
|
|
15
13
|
|
|
16
14
|
export type NewFolderInputFunc = ({
|
|
17
|
-
|
|
15
|
+
onCancel,
|
|
18
16
|
parentId,
|
|
19
17
|
onCreate,
|
|
20
18
|
}: {
|
|
21
|
-
|
|
19
|
+
onCancel: () => void;
|
|
22
20
|
parentId: string;
|
|
23
21
|
onCreate: OnCreatedFunc;
|
|
24
22
|
}) => ReactNode;
|
|
25
|
-
|
|
26
|
-
export interface CommonTreeStructureProps {
|
|
27
|
-
loading?: boolean;
|
|
28
|
-
targetResource?: IResource;
|
|
29
|
-
type: TreeStructureType;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface CommonFolderItemsProps extends CommonTreeStructureProps {
|
|
33
|
-
focusedFolder?: IFolder;
|
|
34
|
-
level: number;
|
|
35
|
-
maxLevel: number;
|
|
36
|
-
selectedFolder?: IFolder;
|
|
37
|
-
onCloseFolder: (id: string) => void;
|
|
38
|
-
onOpenFolder: (id: string) => void;
|
|
39
|
-
setFocusedFolder: (folder: IFolder) => void;
|
|
40
|
-
setSelectedFolder: (folder: IFolder) => void;
|
|
41
|
-
visibleFolders: IFolder[];
|
|
42
|
-
closeTree: () => void;
|
|
43
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -124,7 +124,6 @@ export { TreeStructure } from "./TreeStructure";
|
|
|
124
124
|
export type { TreeStructureProps } from "./TreeStructure";
|
|
125
125
|
|
|
126
126
|
export { BlogPostV2 } from "./BlogPost";
|
|
127
|
-
export { ProgrammeCard } from "./ProgrammeCard";
|
|
128
127
|
export { KeyFigure } from "./KeyFigure";
|
|
129
128
|
export { ContactBlock, contactBlockBackgrounds } from "./ContactBlock";
|
|
130
129
|
export type { ContactBlockBackground } from "./ContactBlock";
|
|
@@ -133,8 +132,6 @@ export { CampaignBlock } from "./CampaignBlock";
|
|
|
133
132
|
export { Grid, GridParallaxItem } from "./Grid";
|
|
134
133
|
export type { GridType } from "./Grid";
|
|
135
134
|
|
|
136
|
-
export type { ProgrammeV2 } from "./ProgrammeCard";
|
|
137
|
-
|
|
138
135
|
export { Gloss, GlossExample } from "./Gloss";
|
|
139
136
|
|
|
140
137
|
export { LinkBlock, LinkBlockSection } from "./LinkBlock";
|
|
@@ -1264,7 +1264,7 @@ const messages = {
|
|
|
1264
1264
|
loginTerms: "Log in with Feide to receive access. By logging on your accept your terms of service",
|
|
1265
1265
|
loginResourcePitch: "Do you want to favorite this resource?",
|
|
1266
1266
|
loginWelcome: "Welcome to My NDLA!",
|
|
1267
|
-
deleteAccount: "Delete
|
|
1267
|
+
deleteAccount: "Delete profile",
|
|
1268
1268
|
loginPitch:
|
|
1269
1269
|
"Welcome to My NDLA! Here you can save your favourite resources from NDLA, organize them and share them with others. Log in with your Feide account to get started.",
|
|
1270
1270
|
loginPitchButton: "Log in to My NDLA",
|
|
@@ -1255,7 +1255,7 @@ const messages = {
|
|
|
1255
1255
|
confirmDeleteAccount: "Er du sikker på at du vil slette kontoen?",
|
|
1256
1256
|
confirmDeleteAccountButton: "Slett konto",
|
|
1257
1257
|
myPage: "Min side",
|
|
1258
|
-
deleteAccount: "Slett
|
|
1258
|
+
deleteAccount: "Slett brukerprofil",
|
|
1259
1259
|
loginPitch:
|
|
1260
1260
|
"Velkommen til Min NDLA! Her kan du lagre favorittressursene dine fra NDLA, organisere dem og dele dem med andre. Logg inn med din Feide-konto for å komme i gang.",
|
|
1261
1261
|
loginPitchButton: "Logg inn i Min NDLA",
|
|
@@ -1255,7 +1255,7 @@ const messages = {
|
|
|
1255
1255
|
confirmDeleteAccount: "Er du sikker på at du vil slette kontoen?",
|
|
1256
1256
|
confirmDeleteAccountButton: "Slett konto",
|
|
1257
1257
|
myPage: "Mi side",
|
|
1258
|
-
deleteAccount: "Slett
|
|
1258
|
+
deleteAccount: "Slett brukarprofil",
|
|
1259
1259
|
loginPitch:
|
|
1260
1260
|
"Velkommen til Min NDLA! Her kan du lagre favorittressursane dine frå NDLA, organisere dei og dele dei med andre. Logg inn med din Feide-konto for å komme i gang.",
|
|
1261
1261
|
loginPitchButton: "Logg inn i Min NDLA",
|
|
@@ -1257,7 +1257,7 @@ const messages = {
|
|
|
1257
1257
|
confirmDeleteAccount: "Er du sikker på at du vil slette kontoen?",
|
|
1258
1258
|
confirmDeleteAccountButton: "Slett konto",
|
|
1259
1259
|
myPage: "Min side",
|
|
1260
|
-
deleteAccount: "Slett
|
|
1260
|
+
deleteAccount: "Slett brukerprofil",
|
|
1261
1261
|
loginPitch:
|
|
1262
1262
|
"Velkommen til Min NDLA! Her kan du lagre favorittressursene dine fra NDLA, organisere dem og dele dem med andre. Logg inn med din Feide-konto for å komme i gang.",
|
|
1263
1263
|
loginPitchButton: "Logg inn i Min NDLA",
|