@ndla/ui 18.0.0 → 19.0.1
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/Masthead/MastheadAuthModal.js +8 -3
- package/es/MyNdla/Resource/Folder.js +11 -10
- package/es/Resource/BlockResource.js +14 -8
- package/es/Resource/ListResource.js +15 -9
- package/es/Resource/resourceComponents.js +12 -11
- package/es/TreeStructure/FolderItem.js +7 -6
- package/es/TreeStructure/FolderItems.js +8 -7
- package/es/TreeStructure/TreeStructure.js +65 -80
- package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +23 -11
- package/es/locale/messages-en.js +2 -0
- package/es/locale/messages-nb.js +2 -0
- package/es/locale/messages-nn.js +2 -0
- package/es/locale/messages-se.js +2 -0
- package/es/locale/messages-sma.js +2 -0
- 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/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/TreeStructure/FolderItem.js +8 -6
- 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/locale/messages-en.d.ts +2 -0
- package/lib/locale/messages-en.js +2 -0
- package/lib/locale/messages-nb.d.ts +2 -0
- package/lib/locale/messages-nb.js +2 -0
- package/lib/locale/messages-nn.d.ts +2 -0
- package/lib/locale/messages-nn.js +2 -0
- package/lib/locale/messages-se.d.ts +2 -0
- package/lib/locale/messages-se.js +2 -0
- package/lib/locale/messages-sma.d.ts +2 -0
- package/lib/locale/messages-sma.js +2 -0
- package/package.json +5 -5
- package/src/Masthead/MastheadAuthModal.tsx +9 -0
- package/src/MyNdla/Resource/Folder.tsx +6 -6
- package/src/Resource/BlockResource.tsx +7 -6
- package/src/Resource/ListResource.tsx +7 -6
- package/src/Resource/resourceComponents.tsx +8 -1
- package/src/TreeStructure/FolderItem.tsx +3 -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/locale/messages-en.ts +2 -0
- package/src/locale/messages-nb.ts +2 -0
- package/src/locale/messages-nn.ts +2 -0
- package/src/locale/messages-se.ts +2 -0
- package/src/locale/messages-sma.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ndla/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.1",
|
|
4
4
|
"description": "UI component library for NDLA.",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"types"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ndla/button": "^
|
|
34
|
+
"@ndla/button": "^3.0.0",
|
|
35
35
|
"@ndla/carousel": "^1.2.11",
|
|
36
36
|
"@ndla/core": "^2.3.0",
|
|
37
37
|
"@ndla/hooks": "^1.1.4",
|
|
38
38
|
"@ndla/icons": "^1.10.0",
|
|
39
39
|
"@ndla/licenses": "^5.0.2",
|
|
40
40
|
"@ndla/modal": "^1.2.12",
|
|
41
|
-
"@ndla/notion": "^3.1.
|
|
42
|
-
"@ndla/safelink": "^2.0
|
|
41
|
+
"@ndla/notion": "^3.1.19",
|
|
42
|
+
"@ndla/safelink": "^2.1.0",
|
|
43
43
|
"@ndla/switch": "^0.1.7",
|
|
44
44
|
"@ndla/tabs": "^1.1.10",
|
|
45
45
|
"@ndla/tooltip": "^2.1.2",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"publishConfig": {
|
|
82
82
|
"access": "public"
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "8c51904170c455fc66f1c624b02829efe15c1482"
|
|
85
85
|
}
|
|
@@ -10,6 +10,8 @@ import React from 'react';
|
|
|
10
10
|
import styled from '@emotion/styled';
|
|
11
11
|
import Button from '@ndla/button';
|
|
12
12
|
import { Feide } from '@ndla/icons/common';
|
|
13
|
+
import { colors, fonts, spacing } from '@ndla/core';
|
|
14
|
+
import { useTranslation } from 'react-i18next';
|
|
13
15
|
|
|
14
16
|
import AuthModal, { AuthModalProps } from '../User/AuthModal';
|
|
15
17
|
|
|
@@ -18,7 +20,12 @@ type FeideWrapperProps = {
|
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
const StyledButton = styled(Button)<FeideWrapperProps>`
|
|
23
|
+
font-weight: ${fonts.weight.semibold};
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
21
26
|
.feide-icon svg {
|
|
27
|
+
margin-left: ${spacing.xsmall};
|
|
28
|
+
fill: ${colors.brand.primary};
|
|
22
29
|
color: ${(props) => (props.inverted ? `#ffffff` : `#000000`)};
|
|
23
30
|
width: 22px;
|
|
24
31
|
height: 22px;
|
|
@@ -35,11 +42,13 @@ interface Props extends AuthModalProps {
|
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
const MastheadAuthModal = ({ inverted, ...rest }: Props) => {
|
|
45
|
+
const { t } = useTranslation();
|
|
38
46
|
return (
|
|
39
47
|
<AuthModal
|
|
40
48
|
{...rest}
|
|
41
49
|
activateButton={
|
|
42
50
|
<StyledButton inverted={inverted} ghostPill={!inverted} ghostPillInverted={inverted} aria-label="Feide">
|
|
51
|
+
{t('myNdla.myNDLA')}
|
|
43
52
|
<span className="feide-icon">
|
|
44
53
|
<Feide />
|
|
45
54
|
</span>
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import styled from '@emotion/styled';
|
|
10
|
-
import React
|
|
10
|
+
import React from 'react';
|
|
11
11
|
import { FolderOutlined } from '@ndla/icons/contentType';
|
|
12
12
|
import { FileDocumentOutline } from '@ndla/icons/common';
|
|
13
13
|
import { fonts, spacing, colors, mq, breakpoints } from '@ndla/core';
|
|
14
14
|
import { css } from '@emotion/core';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import SafeLink from '@ndla/safelink';
|
|
17
|
-
import { MenuButton } from '@ndla/button';
|
|
17
|
+
import { MenuButton, MenuItemProps } from '@ndla/button';
|
|
18
18
|
|
|
19
19
|
interface FolderIconWrapperProps {
|
|
20
20
|
type?: LayoutType;
|
|
@@ -84,7 +84,7 @@ interface Props {
|
|
|
84
84
|
description?: string;
|
|
85
85
|
link: string;
|
|
86
86
|
type: LayoutType;
|
|
87
|
-
|
|
87
|
+
menuItems?: MenuItemProps[];
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
interface IconCountProps {
|
|
@@ -124,14 +124,14 @@ const IconCount = ({ type, count, layoutType }: IconCountProps) => {
|
|
|
124
124
|
return (
|
|
125
125
|
<IconCountWrapper type={layoutType}>
|
|
126
126
|
<Icon aria-label={t(`myNdla.${type}s`)} />
|
|
127
|
-
<span>{layoutType === 'block' ? count : t(`myNdla.${type}s`, { count
|
|
127
|
+
<span>{layoutType === 'block' ? count : t(`myNdla.${type}s`, { count })}</span>
|
|
128
128
|
</IconCountWrapper>
|
|
129
129
|
);
|
|
130
130
|
};
|
|
131
131
|
|
|
132
132
|
type LayoutType = 'list' | 'block';
|
|
133
133
|
|
|
134
|
-
const Folder = ({ link, title, subFolders, subResources, type = 'list',
|
|
134
|
+
const Folder = ({ link, title, subFolders, subResources, type = 'list', menuItems }: Props) => {
|
|
135
135
|
const { t } = useTranslation();
|
|
136
136
|
return (
|
|
137
137
|
<FolderWrapper to={link}>
|
|
@@ -141,7 +141,7 @@ const Folder = ({ link, title, subFolders, subResources, type = 'list', actionMe
|
|
|
141
141
|
<FolderTitle>{title}</FolderTitle>
|
|
142
142
|
<IconCount layoutType={type} type={'folder'} count={subFolders} />
|
|
143
143
|
<IconCount layoutType={type} type={'resource'} count={subResources} />
|
|
144
|
-
<MenuButton size="small" />
|
|
144
|
+
{menuItems && menuItems.length > 0 && <MenuButton size="small" menuItems={menuItems} />}
|
|
145
145
|
</FolderWrapper>
|
|
146
146
|
);
|
|
147
147
|
};
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import styled from '@emotion/styled';
|
|
10
|
-
import React
|
|
10
|
+
import React from 'react';
|
|
11
11
|
import SafeLink from '@ndla/safelink';
|
|
12
12
|
import { colors, fonts, spacing } from '@ndla/core';
|
|
13
|
+
import { MenuButton, MenuItemProps } from '@ndla/button';
|
|
13
14
|
import Image from '../Image';
|
|
14
|
-
import {
|
|
15
|
+
import { CompressedTagList, ResourceImageProps, ResourceTitle, Row, TopicList } from './resourceComponents';
|
|
15
16
|
|
|
16
17
|
interface BlockResourceProps {
|
|
17
18
|
link: string;
|
|
@@ -20,7 +21,7 @@ interface BlockResourceProps {
|
|
|
20
21
|
topics: string[];
|
|
21
22
|
tags?: string[];
|
|
22
23
|
description?: string;
|
|
23
|
-
|
|
24
|
+
menuItems?: MenuItemProps[];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const BlockElementWrapper = styled(SafeLink)`
|
|
@@ -77,7 +78,7 @@ const ImageWrapper = styled.div`
|
|
|
77
78
|
}
|
|
78
79
|
`;
|
|
79
80
|
|
|
80
|
-
const BlockResource = ({ link, title, tags, resourceImage, topics, description,
|
|
81
|
+
const BlockResource = ({ link, title, tags, resourceImage, topics, description, menuItems }: BlockResourceProps) => {
|
|
81
82
|
return (
|
|
82
83
|
<BlockElementWrapper to={link}>
|
|
83
84
|
<ImageWrapper>
|
|
@@ -90,8 +91,8 @@ const BlockResource = ({ link, title, tags, resourceImage, topics, description,
|
|
|
90
91
|
<TopicList topics={topics} />
|
|
91
92
|
<BlockDescription>{description}</BlockDescription>
|
|
92
93
|
<RightRow>
|
|
93
|
-
{tags &&
|
|
94
|
-
{
|
|
94
|
+
{tags && <CompressedTagList tags={tags} />}
|
|
95
|
+
{menuItems && menuItems.length > 0 && <MenuButton size="small" menuItems={menuItems} />}
|
|
95
96
|
</RightRow>
|
|
96
97
|
</BlockInfoWrapper>
|
|
97
98
|
</BlockElementWrapper>
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import styled from '@emotion/styled';
|
|
10
|
-
import React
|
|
10
|
+
import React from 'react';
|
|
11
11
|
import SafeLink from '@ndla/safelink';
|
|
12
12
|
import { fonts, spacing, colors, breakpoints, mq } from '@ndla/core';
|
|
13
|
+
import { MenuButton, MenuItemProps } from '@ndla/button';
|
|
13
14
|
import Image from '../Image';
|
|
14
|
-
import {
|
|
15
|
+
import { CompressedTagList, ResourceImageProps, ResourceTitle, TopicList } from './resourceComponents';
|
|
15
16
|
|
|
16
17
|
const ResourceDescription = styled.p`
|
|
17
18
|
grid-area: description;
|
|
@@ -109,10 +110,10 @@ export interface ListResourceProps {
|
|
|
109
110
|
topics: string[];
|
|
110
111
|
tags?: string[];
|
|
111
112
|
description?: string;
|
|
112
|
-
|
|
113
|
+
menuItems?: MenuItemProps[];
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
const ListResource = ({ link, title, tags, resourceImage, topics, description,
|
|
116
|
+
const ListResource = ({ link, title, tags, resourceImage, topics, description, menuItems }: ListResourceProps) => {
|
|
116
117
|
const showDescription = description !== undefined;
|
|
117
118
|
|
|
118
119
|
return (
|
|
@@ -126,8 +127,8 @@ const ListResource = ({ link, title, tags, resourceImage, topics, description, a
|
|
|
126
127
|
</TopicAndTitle>
|
|
127
128
|
{showDescription && <ResourceDescription>{description}</ResourceDescription>}
|
|
128
129
|
<TagsandActionMenu>
|
|
129
|
-
{tags &&
|
|
130
|
-
{
|
|
130
|
+
{tags && <CompressedTagList tags={tags} />}
|
|
131
|
+
{menuItems && menuItems.length > 0 && <MenuButton size="small" menuItems={menuItems} />}
|
|
131
132
|
</TagsandActionMenu>
|
|
132
133
|
</ResourceWrapper>
|
|
133
134
|
);
|
|
@@ -48,6 +48,9 @@ const StyledTagListElement = styled.li`
|
|
|
48
48
|
::before {
|
|
49
49
|
content: '#';
|
|
50
50
|
}
|
|
51
|
+
&:hover {
|
|
52
|
+
color: ${colors.brand.primary};
|
|
53
|
+
}
|
|
51
54
|
`;
|
|
52
55
|
|
|
53
56
|
const StyledTopicList = styled.ul`
|
|
@@ -100,7 +103,11 @@ export const TagList = ({ tags }: TagListProps) => {
|
|
|
100
103
|
);
|
|
101
104
|
};
|
|
102
105
|
|
|
103
|
-
|
|
106
|
+
interface CompressedTagListProps {
|
|
107
|
+
tags: string[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const CompressedTagList = ({ tags }: CompressedTagListProps) => {
|
|
104
111
|
const visibleTags = tags.slice(0, 3);
|
|
105
112
|
const remainingTags = tags.slice(3, tags.length).map((tag) => {
|
|
106
113
|
return {
|
|
@@ -11,6 +11,7 @@ import styled from '@emotion/styled';
|
|
|
11
11
|
import { ArrowDropDown } from '@ndla/icons/common';
|
|
12
12
|
import { FolderOutlined } from '@ndla/icons/contentType';
|
|
13
13
|
import { colors, spacing, misc, animations } from '@ndla/core';
|
|
14
|
+
import SafeLink from '@ndla/safelink';
|
|
14
15
|
import { SetFocusedFolderId, FolderChildFuncType } from './TreeStructure.types';
|
|
15
16
|
|
|
16
17
|
const OpenButton = styled.button<{ isOpen: boolean }>`
|
|
@@ -75,7 +76,7 @@ const FolderName = styled.button<{ marked: boolean; noArrow?: boolean }>`
|
|
|
75
76
|
text-align: left;
|
|
76
77
|
`;
|
|
77
78
|
|
|
78
|
-
const FolderNameLink = FolderName.withComponent(
|
|
79
|
+
const FolderNameLink = FolderName.withComponent(SafeLink);
|
|
79
80
|
|
|
80
81
|
interface Props {
|
|
81
82
|
name: string;
|
|
@@ -135,9 +136,9 @@ const FolderItem = ({
|
|
|
135
136
|
<FolderNameLink
|
|
136
137
|
ref={folderNameLinkRef}
|
|
137
138
|
noArrow={hideArrow}
|
|
139
|
+
to={loading ? '' : url}
|
|
138
140
|
tabIndex={marked ? 0 : -1}
|
|
139
141
|
marked={marked}
|
|
140
|
-
href={loading ? undefined : url}
|
|
141
142
|
onFocus={() => {
|
|
142
143
|
setFocusedFolderId(id);
|
|
143
144
|
}}
|
|
@@ -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
|
}
|
|
@@ -20,6 +20,7 @@ const messages = {
|
|
|
20
20
|
delete: 'Delete',
|
|
21
21
|
},
|
|
22
22
|
createFolder: 'Create folder',
|
|
23
|
+
maxFoldersAlreadyAdded: 'Maximum subfolders reached',
|
|
23
24
|
newFolder: {
|
|
24
25
|
placeholder: 'Add foldername',
|
|
25
26
|
defaultName: 'New folder',
|
|
@@ -983,6 +984,7 @@ const messages = {
|
|
|
983
984
|
close: 'Close fact box',
|
|
984
985
|
},
|
|
985
986
|
myNdla: {
|
|
987
|
+
myNDLA: 'My NDLA',
|
|
986
988
|
resources: '{{count}} Resource',
|
|
987
989
|
resources_plural: '{{count}} Resources',
|
|
988
990
|
folders: '{{count}} Folder',
|
|
@@ -20,6 +20,7 @@ const messages = {
|
|
|
20
20
|
delete: 'Slett',
|
|
21
21
|
},
|
|
22
22
|
createFolder: 'Lag mappe',
|
|
23
|
+
maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
|
|
23
24
|
newFolder: {
|
|
24
25
|
placeholder: 'Skriv navn på mappe',
|
|
25
26
|
defaultName: 'Ny mappe',
|
|
@@ -981,6 +982,7 @@ const messages = {
|
|
|
981
982
|
close: 'Lukk faktaboks',
|
|
982
983
|
},
|
|
983
984
|
myNdla: {
|
|
985
|
+
myNDLA: 'Min NDLA',
|
|
984
986
|
resources: '{{count}} ressurs',
|
|
985
987
|
resources_plural: '{{count}} ressurser',
|
|
986
988
|
folders: '{{count}} mappe',
|
|
@@ -20,6 +20,7 @@ const messages = {
|
|
|
20
20
|
delete: 'Slett',
|
|
21
21
|
},
|
|
22
22
|
createFolder: 'Lag mappe',
|
|
23
|
+
maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
|
|
23
24
|
newFolder: {
|
|
24
25
|
placeholder: 'Skriv namn på mappe',
|
|
25
26
|
defaultName: 'Ny mappe',
|
|
@@ -982,6 +983,7 @@ const messages = {
|
|
|
982
983
|
close: 'Lukk faktaboks',
|
|
983
984
|
},
|
|
984
985
|
myNdla: {
|
|
986
|
+
myNDLA: 'Min NDLA',
|
|
985
987
|
resources: '{{count}} ressurs',
|
|
986
988
|
resources_plural: '{{count}} ressursar',
|
|
987
989
|
folders: '{{count}} mappe',
|
|
@@ -20,6 +20,7 @@ const messages = {
|
|
|
20
20
|
delete: 'Slett',
|
|
21
21
|
},
|
|
22
22
|
createFolder: 'Lag mappe',
|
|
23
|
+
maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
|
|
23
24
|
newFolder: {
|
|
24
25
|
placeholder: 'Skriv navn på mappe',
|
|
25
26
|
defaultName: 'Ny mappe',
|
|
@@ -981,6 +982,7 @@ const messages = {
|
|
|
981
982
|
close: 'Lukk faktaboks',
|
|
982
983
|
},
|
|
983
984
|
myNdla: {
|
|
985
|
+
myNDLA: 'Min NDLA',
|
|
984
986
|
resources: '{{count}} ressurs',
|
|
985
987
|
resources_plural: '{{count}} ressurser',
|
|
986
988
|
folders: '{{count}} mappe',
|
|
@@ -20,6 +20,7 @@ const messages = {
|
|
|
20
20
|
delete: 'Slett',
|
|
21
21
|
},
|
|
22
22
|
createFolder: 'Lag mappe',
|
|
23
|
+
maxFoldersAlreadyAdded: 'Maks nivå av undermapper nådd',
|
|
23
24
|
newFolder: {
|
|
24
25
|
placeholder: 'Skriv navn på mappe',
|
|
25
26
|
defaultName: 'Ny mappe',
|
|
@@ -981,6 +982,7 @@ const messages = {
|
|
|
981
982
|
close: 'Lukk faktaboks',
|
|
982
983
|
},
|
|
983
984
|
myNdla: {
|
|
985
|
+
myNDLA: 'Min NDLA',
|
|
984
986
|
resources: '{{count}} ressurs',
|
|
985
987
|
resources_plural: '{{count}} ressurser',
|
|
986
988
|
folders: '{{count}} mappe',
|