@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.
Files changed (74) hide show
  1. package/dist/panda.buildinfo.json +7 -2
  2. package/dist/styles.css +24 -4
  3. package/es/Article/Article.js +4 -1
  4. package/es/ContentTypeBadge/ContentTypeBadge.js +2 -0
  5. package/es/TreeStructure/TreeStructure.js +292 -181
  6. package/es/TreeStructure/helperFunctions.js +0 -3
  7. package/es/TreeStructure/index.js +1 -2
  8. package/es/index.js +0 -1
  9. package/es/locale/messages-en.js +1 -1
  10. package/es/locale/messages-nb.js +1 -1
  11. package/es/locale/messages-nn.js +1 -1
  12. package/es/locale/messages-se.js +1 -1
  13. package/es/locale/messages-sma.js +1 -1
  14. package/es/styles.css +24 -4
  15. package/lib/Article/Article.js +4 -1
  16. package/lib/ContentTypeBadge/ContentTypeBadge.js +2 -0
  17. package/lib/TreeStructure/TreeStructure.d.ts +7 -6
  18. package/lib/TreeStructure/TreeStructure.js +293 -180
  19. package/lib/TreeStructure/helperFunctions.d.ts +0 -2
  20. package/lib/TreeStructure/helperFunctions.js +2 -6
  21. package/lib/TreeStructure/index.d.ts +1 -2
  22. package/lib/TreeStructure/index.js +2 -3
  23. package/lib/TreeStructure/types.d.ts +4 -22
  24. package/lib/index.d.ts +0 -2
  25. package/lib/index.js +0 -7
  26. package/lib/locale/messages-en.js +1 -1
  27. package/lib/locale/messages-nb.js +1 -1
  28. package/lib/locale/messages-nn.js +1 -1
  29. package/lib/locale/messages-se.js +1 -1
  30. package/lib/locale/messages-sma.js +1 -1
  31. package/lib/styles.css +24 -4
  32. package/package.json +7 -8
  33. package/src/Article/Article.tsx +4 -1
  34. package/src/ContentTypeBadge/ContentTypeBadge.tsx +2 -0
  35. package/src/TreeStructure/TreeStructure.stories.tsx +38 -68
  36. package/src/TreeStructure/TreeStructure.tsx +307 -194
  37. package/src/TreeStructure/helperFunctions.ts +0 -5
  38. package/src/TreeStructure/index.ts +1 -2
  39. package/src/TreeStructure/types.ts +4 -25
  40. package/src/index.ts +0 -3
  41. package/src/locale/messages-en.ts +1 -1
  42. package/src/locale/messages-nb.ts +1 -1
  43. package/src/locale/messages-nn.ts +1 -1
  44. package/src/locale/messages-se.ts +1 -1
  45. package/src/locale/messages-sma.ts +1 -1
  46. package/es/ProgrammeCard/ProgrammeCard.js +0 -51
  47. package/es/ProgrammeCard/index.js +0 -9
  48. package/es/TreeStructure/AddFolderButton.js +0 -80
  49. package/es/TreeStructure/ComboboxButton.js +0 -127
  50. package/es/TreeStructure/FolderItem.js +0 -225
  51. package/es/TreeStructure/FolderItems.js +0 -95
  52. package/es/TreeStructure/arrowNavigation.js +0 -35
  53. package/lib/ProgrammeCard/ProgrammeCard.d.ts +0 -23
  54. package/lib/ProgrammeCard/ProgrammeCard.js +0 -58
  55. package/lib/ProgrammeCard/index.d.ts +0 -9
  56. package/lib/ProgrammeCard/index.js +0 -13
  57. package/lib/TreeStructure/AddFolderButton.d.ts +0 -17
  58. package/lib/TreeStructure/AddFolderButton.js +0 -85
  59. package/lib/TreeStructure/ComboboxButton.d.ts +0 -27
  60. package/lib/TreeStructure/ComboboxButton.js +0 -134
  61. package/lib/TreeStructure/FolderItem.d.ts +0 -17
  62. package/lib/TreeStructure/FolderItem.js +0 -230
  63. package/lib/TreeStructure/FolderItems.d.ts +0 -22
  64. package/lib/TreeStructure/FolderItems.js +0 -100
  65. package/lib/TreeStructure/arrowNavigation.d.ts +0 -10
  66. package/lib/TreeStructure/arrowNavigation.js +0 -42
  67. package/src/ProgrammeCard/ProgrammeCard.stories.tsx +0 -35
  68. package/src/ProgrammeCard/ProgrammeCard.tsx +0 -78
  69. package/src/ProgrammeCard/index.tsx +0 -10
  70. package/src/TreeStructure/AddFolderButton.tsx +0 -79
  71. package/src/TreeStructure/ComboboxButton.tsx +0 -172
  72. package/src/TreeStructure/FolderItem.tsx +0 -307
  73. package/src/TreeStructure/FolderItems.tsx +0 -121
  74. package/src/TreeStructure/arrowNavigation.ts +0 -54
@@ -1,65 +1,47 @@
1
1
  /**
2
- * Copyright (c) 2022-present, NDLA.
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 { useEffect, useState, useMemo, useRef } from "react";
10
- import styled from "@emotion/styled";
11
- import { colors, fonts, misc, utils } from "@ndla/core";
12
- import { IFolder } from "@ndla/types-backend/myndla-api";
13
- import AddFolderButton from "./AddFolderButton";
14
- import ComboboxButton from "./ComboboxButton";
15
- import FolderItems from "./FolderItems";
16
- import { flattenFolders, treestructureId } from "./helperFunctions";
17
- import { CommonTreeStructureProps, NewFolderInputFunc } from "./types";
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
- const uniq = <T,>(array: T[]): T[] => Array.from(new Set(array));
22
-
23
- const StyledLabel = styled.label`
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 TreeStructure = ({
73
- defaultOpenFolders,
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 ref = useRef<HTMLButtonElement>(null);
85
-
86
- const defaultSelectedFolderId = defaultOpenFolders?.[defaultOpenFolders.length - 1];
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 [openFolders, setOpenFolders] = useState<string[]>(defaultOpenFolders || []);
135
+ const selectedFolder = useMemo(() => {
136
+ return flattenFolders(folders).find((folder) => folder.id === selectedValue);
137
+ }, [folders, selectedValue]);
89
138
 
90
- const [newFolderParentId, setNewFolderParentId] = useState<string | undefined>();
91
- const [focusedFolder, _setFocusedFolder] = useState<IFolder | undefined>();
92
- const [selectedFolder, _setSelectedFolder] = useState<IFolder | undefined>();
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 flattenedFolders = useMemo(() => flattenFolders(folders, openFolders), [folders, openFolders]);
143
+ const onOpenChange = useCallback((details: PopoverOpenChangeDetails) => {
144
+ setOpen(details.open);
145
+ if (!details.open) {
146
+ setNewFolderParentId(null);
147
+ }
148
+ }, []);
96
149
 
97
- useEffect(() => {
98
- if (defaultOpenFolders) {
99
- if (!defaultOpenFolders.every((element) => openFolders.includes(element))) {
100
- setOpenFolders((prev) => {
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
- // eslint-disable-next-line react-hooks/exhaustive-deps
106
- }, [defaultOpenFolders]);
107
-
108
- useEffect(() => {
109
- if (defaultSelectedFolderId !== undefined) {
110
- const selected = flattenFolders(folders).find((folder) => folder.id === defaultSelectedFolderId);
111
- if (selected) {
112
- _setSelectedFolder(selected);
113
- if (type === "picker") {
114
- _setFocusedFolder(selected);
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
- // eslint-disable-next-line react-hooks/exhaustive-deps
119
- }, [defaultSelectedFolderId]);
181
+ setSelectedValue(val);
182
+ onSelectFolder?.(val);
183
+ },
184
+ expandOnClick: false,
185
+ });
120
186
 
121
- const onToggleTree = (open: boolean) => {
122
- setShowTree(open);
123
- if (!open) {
124
- setNewFolderParentId(undefined);
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 setSelectedFolder = (folder: IFolder) => {
129
- _setSelectedFolder(folder);
130
- onSelectFolder?.(folder.id);
131
- };
223
+ const addTooltip = loading
224
+ ? t("loading")
225
+ : disableCreateFolder
226
+ ? t("treeStructure.maxFoldersAlreadyAdded")
227
+ : t("myNdla.newFolderUnder", { folderName: selectedFolder?.name });
132
228
 
133
- const setFocusedFolder = (folder: IFolder) => {
134
- _setFocusedFolder(folder);
135
- setNewFolderParentId(undefined);
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
- ref.current?.focus({ preventScroll: true });
138
- };
283
+ interface TreeStructureItemProps {
284
+ folder: IFolder;
285
+ targetResource?: IResource;
286
+ }
139
287
 
140
- const onOpenFolder = (id: string) => {
141
- setOpenFolders(uniq(openFolders.concat(id)));
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 onCloseFolder = (id: string) => {
145
- const closedFolder = flattenedFolders.find((folder) => folder.id === id);
294
+ const FolderIcon = folder.status === "shared" ? StyledFolderUserLine : StyledFolderLine;
146
295
 
147
- if (closedFolder) {
148
- const subFolders = closedFolder.subfolders && flattenFolders(closedFolder.subfolders);
149
- if (subFolders.some((folder) => folder.id === selectedFolder?.id)) {
150
- setFocusedFolder(closedFolder);
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
- setOpenFolders(openFolders.filter((folderId) => folderId !== id));
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
- const onCancelNewFolder = () => {
167
- setNewFolderParentId?.(undefined);
168
- ref.current?.focus({ preventScroll: true });
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 canAddFolder = selectedFolder && selectedFolder?.breadcrumbs.length < (maxLevel || 1);
320
+ const ariaLabel = folder.status === "shared" ? `${folder.name}. ${t("myNdla.folder.sharing.shared")}` : folder.name;
172
321
 
173
322
  return (
174
- <StyledTreeStructure
175
- onBlur={(e) => {
176
- if (type === "picker" && !e.currentTarget.contains(e.relatedTarget)) {
177
- onToggleTree(false);
178
- }
179
- }}
180
- >
181
- <Row>
182
- {label && <StyledLabel id={treestructureId(type, "label")}>{label}</StyledLabel>}
183
- {type === "picker" && (
184
- <AddFolderButton
185
- loading={loading}
186
- canAddFolder={!!canAddFolder}
187
- focusedFolder={focusedFolder}
188
- setNewFolderParentId={setNewFolderParentId}
189
- setShowTree={setShowTree}
190
- />
191
- )}
192
- </Row>
193
- <TreeStructureWrapper aria-label={label} data-type={type}>
194
- {type === "picker" && (
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
- </ScrollableDiv>
236
- )}
237
- </TreeStructureWrapper>
238
- </StyledTreeStructure>
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
- };
@@ -6,6 +6,5 @@
6
6
  *
7
7
  */
8
8
 
9
- import TreeStructure from "./TreeStructure";
10
9
  export type { TreeStructureProps } from "./TreeStructure";
11
- export { TreeStructure };
10
+ export { TreeStructure } from "./TreeStructure";
@@ -7,37 +7,16 @@
7
7
  */
8
8
 
9
9
  import { ReactNode } from "react";
10
- import { IFolder, IResource } from "@ndla/types-backend/myndla-api";
10
+ import { IFolder } from "@ndla/types-backend/myndla-api";
11
11
 
12
- export type TreeStructureType = "navigation" | "picker";
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
- onClose,
15
+ onCancel,
18
16
  parentId,
19
17
  onCreate,
20
18
  }: {
21
- onClose: () => void;
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 My NDLA",
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 Min NDLA",
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 Min NDLA",
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 Min NDLA",
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",