@ndla/ui 42.1.1 → 43.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/AudioPlayer/AudioPlayer.js +27 -33
- package/es/AudioPlayer/Controls.js +41 -39
- package/es/Breadcrumb/index.js +0 -1
- package/es/CampaignBlock/CampaignBlock.js +32 -18
- package/es/LanguageSelector/LanguageSelector.js +31 -36
- package/es/LicenseByline/EmbedByline.js +5 -5
- package/es/MyNdla/Resource/Folder.js +27 -72
- package/es/MyNdla/index.js +1 -3
- package/es/Resource/BlockResource.js +15 -27
- package/es/Resource/ListResource.js +14 -19
- package/es/Resource/resourceComponents.js +82 -41
- package/es/TreeStructure/ComboboxButton.js +17 -20
- package/es/TreeStructure/FolderItem.js +42 -69
- package/es/TreeStructure/FolderItems.js +25 -19
- package/es/TreeStructure/TreeStructure.js +19 -26
- package/es/index.js +2 -2
- package/lib/AudioPlayer/AudioPlayer.js +27 -33
- package/lib/AudioPlayer/Controls.js +40 -38
- package/lib/Breadcrumb/index.d.ts +0 -1
- package/lib/Breadcrumb/index.js +0 -7
- package/lib/CampaignBlock/CampaignBlock.js +32 -18
- package/lib/LanguageSelector/LanguageSelector.js +31 -36
- package/lib/LicenseByline/EmbedByline.js +5 -5
- package/lib/MyNdla/Resource/Folder.d.ts +3 -4
- package/lib/MyNdla/Resource/Folder.js +27 -72
- package/lib/MyNdla/index.d.ts +1 -3
- package/lib/MyNdla/index.js +0 -14
- package/lib/Resource/BlockResource.d.ts +3 -3
- package/lib/Resource/BlockResource.js +14 -26
- package/lib/Resource/ListResource.d.ts +3 -3
- package/lib/Resource/ListResource.js +13 -18
- package/lib/Resource/resourceComponents.d.ts +5 -10
- package/lib/Resource/resourceComponents.js +85 -42
- package/lib/TreeStructure/ComboboxButton.js +17 -20
- package/lib/TreeStructure/FolderItem.js +40 -67
- package/lib/TreeStructure/FolderItems.js +31 -26
- package/lib/TreeStructure/TreeStructure.js +19 -26
- package/lib/index.d.ts +2 -2
- package/lib/index.js +0 -12
- package/package.json +16 -17
- package/src/AudioPlayer/AudioPlayer.tsx +24 -34
- package/src/AudioPlayer/Controls.tsx +22 -26
- package/src/Breadcrumb/index.ts +0 -2
- package/src/CampaignBlock/CampaignBlock.tsx +10 -10
- package/src/LanguageSelector/LanguageSelector.tsx +26 -32
- package/src/LicenseByline/EmbedByline.tsx +1 -1
- package/src/MyNdla/Resource/Folder.stories.tsx +27 -5
- package/src/MyNdla/Resource/Folder.tsx +32 -54
- package/src/MyNdla/index.ts +1 -3
- package/src/Resource/BlockResource.stories.tsx +1 -1
- package/src/Resource/BlockResource.tsx +25 -24
- package/src/Resource/ListResource.tsx +21 -18
- package/src/Resource/Resource.stories.tsx +32 -2
- package/src/Resource/resourceComponents.tsx +55 -26
- package/src/TreeStructure/ComboboxButton.tsx +5 -7
- package/src/TreeStructure/FolderItem.tsx +50 -35
- package/src/TreeStructure/FolderItems.tsx +6 -8
- package/src/TreeStructure/TreeStructure.tsx +16 -25
- package/src/index.ts +2 -2
- package/es/Breadcrumb/ActionBreadcrumb.js +0 -74
- package/es/MyNdla/Resource/FolderMenu.js +0 -74
- package/es/MyNdla/SettingsMenu.js +0 -98
- package/lib/Breadcrumb/ActionBreadcrumb.d.ts +0 -15
- package/lib/Breadcrumb/ActionBreadcrumb.js +0 -82
- package/lib/MyNdla/Resource/FolderMenu.d.ts +0 -16
- package/lib/MyNdla/Resource/FolderMenu.js +0 -81
- package/lib/MyNdla/SettingsMenu.d.ts +0 -15
- package/lib/MyNdla/SettingsMenu.js +0 -102
- package/src/Breadcrumb/ActionBreadcrumb.tsx +0 -87
- package/src/MyNdla/Resource/FolderMenu.tsx +0 -102
- package/src/MyNdla/SettingsMenu.tsx +0 -96
|
@@ -7,23 +7,21 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import styled from '@emotion/styled';
|
|
10
|
-
import React from 'react';
|
|
10
|
+
import React, { ReactNode } from 'react';
|
|
11
11
|
import { colors, fonts, spacing } from '@ndla/core';
|
|
12
|
-
import { MenuItemProps } from '@ndla/button';
|
|
13
12
|
import ContentTypeBadge from '../ContentTypeBadge';
|
|
14
13
|
import Image from '../Image';
|
|
15
14
|
import {
|
|
16
15
|
CompressedTagList,
|
|
17
16
|
ResourceImageProps,
|
|
18
|
-
ResourceTitle,
|
|
19
17
|
ResourceTypeList,
|
|
20
18
|
ResourceTitleLink,
|
|
21
19
|
LoaderProps,
|
|
22
|
-
|
|
20
|
+
ContentIconWrapper,
|
|
21
|
+
resourceHeadingStyle,
|
|
23
22
|
} from './resourceComponents';
|
|
24
23
|
import ContentLoader from '../ContentLoader';
|
|
25
24
|
import { contentTypeMapping, resourceEmbedTypeMapping } from '../model/ContentType';
|
|
26
|
-
import { SettingsMenu } from '../MyNdla';
|
|
27
25
|
|
|
28
26
|
const BlockElementWrapper = styled.div`
|
|
29
27
|
display: flex;
|
|
@@ -41,11 +39,23 @@ const BlockElementWrapper = styled.div`
|
|
|
41
39
|
&:hover {
|
|
42
40
|
box-shadow: 1px 1px 6px 2px rgba(9, 55, 101, 0.08);
|
|
43
41
|
transition-duration: 0.2s;
|
|
44
|
-
|
|
42
|
+
[data-link] {
|
|
45
43
|
color: ${colors.brand.primary};
|
|
46
44
|
text-decoration: underline;
|
|
47
45
|
}
|
|
48
46
|
}
|
|
47
|
+
|
|
48
|
+
&:hover,
|
|
49
|
+
&:focus,
|
|
50
|
+
&:focus-within {
|
|
51
|
+
[data-description] {
|
|
52
|
+
/* Unfortunate css needed for multi-line text overflow ellipsis. */
|
|
53
|
+
height: 3.1em;
|
|
54
|
+
-webkit-line-clamp: 2;
|
|
55
|
+
line-clamp: 2;
|
|
56
|
+
-webkit-box-orient: vertical;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
49
59
|
`;
|
|
50
60
|
|
|
51
61
|
const BlockDescription = styled.p`
|
|
@@ -57,14 +67,6 @@ const BlockDescription = styled.p`
|
|
|
57
67
|
overflow: hidden;
|
|
58
68
|
text-overflow: ellipsis;
|
|
59
69
|
transition: height 0.2s ease-out;
|
|
60
|
-
${() => BlockElementWrapper}:hover &, ${() => BlockElementWrapper}:focus & , ${() =>
|
|
61
|
-
BlockElementWrapper}:focus-within & {
|
|
62
|
-
// Unfortunate css needed for multi-line text overflow ellipsis.
|
|
63
|
-
height: 3.1em;
|
|
64
|
-
-webkit-line-clamp: 2;
|
|
65
|
-
line-clamp: 2;
|
|
66
|
-
-webkit-box-orient: vertical;
|
|
67
|
-
}
|
|
68
70
|
`;
|
|
69
71
|
|
|
70
72
|
const TagsAndActionMenu = styled.div`
|
|
@@ -114,9 +116,9 @@ const BlockImage = ({ image, loading, contentType }: BlockImageProps) => {
|
|
|
114
116
|
}
|
|
115
117
|
if (image.src === '') {
|
|
116
118
|
return (
|
|
117
|
-
<
|
|
119
|
+
<ContentIconWrapper contentType={contentType}>
|
|
118
120
|
<ContentTypeBadge type={contentType} size="large" />
|
|
119
|
-
</
|
|
121
|
+
</ContentIconWrapper>
|
|
120
122
|
);
|
|
121
123
|
} else {
|
|
122
124
|
return <Image alt={image.alt} src={image.src} fallbackWidth={300} />;
|
|
@@ -145,7 +147,7 @@ interface Props {
|
|
|
145
147
|
tags?: string[];
|
|
146
148
|
description?: string;
|
|
147
149
|
headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
148
|
-
|
|
150
|
+
menu?: ReactNode;
|
|
149
151
|
isLoading?: boolean;
|
|
150
152
|
targetBlank?: boolean;
|
|
151
153
|
resourceTypes?: { id: string; name: string }[];
|
|
@@ -159,14 +161,13 @@ const BlockResource = ({
|
|
|
159
161
|
tags,
|
|
160
162
|
resourceImage,
|
|
161
163
|
description,
|
|
162
|
-
|
|
164
|
+
menu,
|
|
163
165
|
isLoading,
|
|
164
|
-
headingLevel = 'h2',
|
|
166
|
+
headingLevel: Heading = 'h2',
|
|
165
167
|
targetBlank,
|
|
166
168
|
resourceTypes,
|
|
167
169
|
}: Props) => {
|
|
168
170
|
const firstResourceType = resourceTypes?.[0]?.id ?? '';
|
|
169
|
-
const Title = ResourceTitle.withComponent(headingLevel);
|
|
170
171
|
|
|
171
172
|
return (
|
|
172
173
|
<BlockElementWrapper id={id}>
|
|
@@ -184,16 +185,16 @@ const BlockResource = ({
|
|
|
184
185
|
<BlockInfoWrapper>
|
|
185
186
|
<ContentWrapper>
|
|
186
187
|
<ResourceTypeAndTitleLoader loading={isLoading}>
|
|
187
|
-
<ResourceTitleLink title={title} target={targetBlank ? '_blank' : undefined} to={link}>
|
|
188
|
-
<
|
|
188
|
+
<ResourceTitleLink data-link="" title={title} target={targetBlank ? '_blank' : undefined} to={link}>
|
|
189
|
+
<Heading css={resourceHeadingStyle}>{title}</Heading>
|
|
189
190
|
</ResourceTitleLink>
|
|
190
191
|
</ResourceTypeAndTitleLoader>
|
|
191
192
|
<ResourceTypeList resourceTypes={resourceTypes} />
|
|
192
|
-
<BlockDescription>{description}</BlockDescription>
|
|
193
|
+
<BlockDescription data-description="">{description}</BlockDescription>
|
|
193
194
|
</ContentWrapper>
|
|
194
195
|
<TagsAndActionMenu>
|
|
195
196
|
{tags && tags.length > 0 && <CompressedTagList tagLinkPrefix={tagLinkPrefix} tags={tags} />}
|
|
196
|
-
{
|
|
197
|
+
{menu}
|
|
197
198
|
</TagsAndActionMenu>
|
|
198
199
|
</BlockInfoWrapper>
|
|
199
200
|
</BlockElementWrapper>
|
|
@@ -7,23 +7,21 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import styled from '@emotion/styled';
|
|
10
|
-
import React from 'react';
|
|
10
|
+
import React, { ReactNode } from 'react';
|
|
11
11
|
import { fonts, spacing, colors, breakpoints, mq } from '@ndla/core';
|
|
12
|
-
import { MenuItemProps } from '@ndla/button';
|
|
13
12
|
import Image from '../Image';
|
|
14
13
|
import {
|
|
15
14
|
CompressedTagList,
|
|
16
15
|
ResourceImageProps,
|
|
17
|
-
|
|
16
|
+
resourceHeadingStyle,
|
|
18
17
|
ResourceTitleLink as StyledLink,
|
|
19
18
|
ResourceTypeList,
|
|
20
|
-
StyledContentIconWrapper,
|
|
21
19
|
LoaderProps,
|
|
20
|
+
ContentIconWrapper,
|
|
22
21
|
} from './resourceComponents';
|
|
23
22
|
import ContentLoader from '../ContentLoader';
|
|
24
23
|
import ContentTypeBadge from '../ContentTypeBadge';
|
|
25
24
|
import { contentTypeMapping, resourceEmbedTypeMapping } from '../model/ContentType';
|
|
26
|
-
import { SettingsMenu } from '../MyNdla';
|
|
27
25
|
|
|
28
26
|
const ListResourceWrapper = styled.div`
|
|
29
27
|
flex: 1;
|
|
@@ -48,7 +46,7 @@ const ListResourceWrapper = styled.div`
|
|
|
48
46
|
&:hover {
|
|
49
47
|
box-shadow: 1px 1px 6px 2px rgba(9, 55, 101, 0.08);
|
|
50
48
|
transition-duration: 0.2s;
|
|
51
|
-
|
|
49
|
+
[data-link] {
|
|
52
50
|
color: ${colors.brand.primary};
|
|
53
51
|
text-decoration: underline;
|
|
54
52
|
}
|
|
@@ -61,11 +59,7 @@ interface StyledImageProps {
|
|
|
61
59
|
|
|
62
60
|
const ImageWrapper = styled.div<StyledImageProps>`
|
|
63
61
|
grid-area: image;
|
|
64
|
-
width:
|
|
65
|
-
${mq.range({ until: breakpoints.mobileWide })} {
|
|
66
|
-
width: 56px;
|
|
67
|
-
margin-bottom: 0;
|
|
68
|
-
}
|
|
62
|
+
width: 56px;
|
|
69
63
|
overflow: hidden;
|
|
70
64
|
border-radius: 2px;
|
|
71
65
|
display: flex;
|
|
@@ -73,6 +67,13 @@ const ImageWrapper = styled.div<StyledImageProps>`
|
|
|
73
67
|
align-items: center;
|
|
74
68
|
justify-content: center;
|
|
75
69
|
aspect-ratio: 4/3;
|
|
70
|
+
&[data-image-size='normal'] {
|
|
71
|
+
width: 136px;
|
|
72
|
+
}
|
|
73
|
+
${mq.range({ until: breakpoints.mobileWide })} {
|
|
74
|
+
width: 56px;
|
|
75
|
+
margin-bottom: 0;
|
|
76
|
+
}
|
|
76
77
|
`;
|
|
77
78
|
|
|
78
79
|
const StyledImage = styled(Image)`
|
|
@@ -138,9 +139,9 @@ const ListResourceImage = ({ resourceImage, loading, type, contentType, backgrou
|
|
|
138
139
|
if (!loading) {
|
|
139
140
|
if (resourceImage.src === '') {
|
|
140
141
|
return (
|
|
141
|
-
<
|
|
142
|
+
<ContentIconWrapper contentType={contentType}>
|
|
142
143
|
<ContentTypeBadge type={contentType} background={background} size="x-small" />
|
|
143
|
-
</
|
|
144
|
+
</ContentIconWrapper>
|
|
144
145
|
);
|
|
145
146
|
} else {
|
|
146
147
|
return (
|
|
@@ -201,7 +202,7 @@ export interface ListResourceProps {
|
|
|
201
202
|
resourceTypes: { id: string; name: string }[];
|
|
202
203
|
tags?: string[];
|
|
203
204
|
description?: string;
|
|
204
|
-
|
|
205
|
+
menu?: ReactNode;
|
|
205
206
|
isLoading?: boolean;
|
|
206
207
|
targetBlank?: boolean;
|
|
207
208
|
}
|
|
@@ -215,7 +216,7 @@ const ListResource = ({
|
|
|
215
216
|
resourceImage,
|
|
216
217
|
resourceTypes,
|
|
217
218
|
description,
|
|
218
|
-
|
|
219
|
+
menu,
|
|
219
220
|
isLoading = false,
|
|
220
221
|
targetBlank,
|
|
221
222
|
}: ListResourceProps) => {
|
|
@@ -237,8 +238,10 @@ const ListResource = ({
|
|
|
237
238
|
</ImageWrapper>
|
|
238
239
|
<TopicAndTitleWrapper>
|
|
239
240
|
<TypeAndTitleLoader loading={isLoading}>
|
|
240
|
-
<StyledLink to={link} target={targetBlank ? '_blank' : undefined}>
|
|
241
|
-
<
|
|
241
|
+
<StyledLink to={link} data-link="" target={targetBlank ? '_blank' : undefined}>
|
|
242
|
+
<h1 css={resourceHeadingStyle} title={title}>
|
|
243
|
+
{title}
|
|
244
|
+
</h1>
|
|
242
245
|
</StyledLink>
|
|
243
246
|
<ResourceTypeList resourceTypes={resourceTypes} />
|
|
244
247
|
</TypeAndTitleLoader>
|
|
@@ -246,7 +249,7 @@ const ListResource = ({
|
|
|
246
249
|
{showDescription && <Description description={description} loading={isLoading} />}
|
|
247
250
|
<TagsandActionMenu>
|
|
248
251
|
{tags && tags.length > 0 && <CompressedTagList tagLinkPrefix={tagLinkPrefix} tags={tags} />}
|
|
249
|
-
{
|
|
252
|
+
{menu}
|
|
250
253
|
</TagsandActionMenu>
|
|
251
254
|
</ListResourceWrapper>
|
|
252
255
|
);
|
|
@@ -8,8 +8,37 @@
|
|
|
8
8
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { Meta, StoryFn } from '@storybook/react';
|
|
11
|
-
import {
|
|
11
|
+
import { DropdownMenu, DropdownContent, DropdownItem, DropdownTrigger } from '@ndla/dropdown-menu';
|
|
12
|
+
import { ButtonV2, IconButtonV2 } from '@ndla/button';
|
|
13
|
+
import { HorizontalMenu } from '@ndla/icons/contentType';
|
|
14
|
+
import { Pencil } from '@ndla/icons/action';
|
|
15
|
+
import { DeleteForever } from '@ndla/icons/editor';
|
|
12
16
|
import ListResource from './ListResource';
|
|
17
|
+
import { defaultParameters } from '../../../../stories/defaults';
|
|
18
|
+
|
|
19
|
+
const StoryResourceMenu = () => (
|
|
20
|
+
<DropdownMenu>
|
|
21
|
+
<DropdownTrigger>
|
|
22
|
+
<IconButtonV2 aria-label="Show more" title="Show more" variant="ghost" colorTheme="light">
|
|
23
|
+
<HorizontalMenu />
|
|
24
|
+
</IconButtonV2>
|
|
25
|
+
</DropdownTrigger>
|
|
26
|
+
<DropdownContent>
|
|
27
|
+
<DropdownItem>
|
|
28
|
+
<ButtonV2 variant="ghost" colorTheme="light" shape="sharp" size="small" fontWeight="normal">
|
|
29
|
+
<Pencil />
|
|
30
|
+
Rediger
|
|
31
|
+
</ButtonV2>
|
|
32
|
+
</DropdownItem>
|
|
33
|
+
<DropdownItem>
|
|
34
|
+
<ButtonV2 variant="ghost" colorTheme="danger" shape="sharp" size="small" fontWeight="normal">
|
|
35
|
+
<DeleteForever />
|
|
36
|
+
Slett
|
|
37
|
+
</ButtonV2>
|
|
38
|
+
</DropdownItem>
|
|
39
|
+
</DropdownContent>
|
|
40
|
+
</DropdownMenu>
|
|
41
|
+
);
|
|
13
42
|
|
|
14
43
|
export default {
|
|
15
44
|
title: 'Components/Resources/ListResource',
|
|
@@ -28,7 +57,7 @@ export default {
|
|
|
28
57
|
headingLevel: {
|
|
29
58
|
control: false,
|
|
30
59
|
},
|
|
31
|
-
|
|
60
|
+
menu: {
|
|
32
61
|
control: false,
|
|
33
62
|
},
|
|
34
63
|
},
|
|
@@ -41,6 +70,7 @@ export default {
|
|
|
41
70
|
alt: '',
|
|
42
71
|
},
|
|
43
72
|
resourceTypes: [{ id: 'urn:resourcetype:learningPath', name: 'Læringssti' }],
|
|
73
|
+
menu: <StoryResourceMenu />,
|
|
44
74
|
tags: ['tag', 'tag', 'tag', 'tag'],
|
|
45
75
|
},
|
|
46
76
|
} as Meta<typeof ListResource>;
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
import styled from '@emotion/styled';
|
|
10
10
|
import { colors, fonts, spacing } from '@ndla/core';
|
|
11
|
-
import React, { ReactNode } from 'react';
|
|
11
|
+
import React, { CSSProperties, HTMLAttributes, ReactNode, useMemo } from 'react';
|
|
12
12
|
import { useTranslation } from 'react-i18next';
|
|
13
|
-
import {
|
|
14
|
-
import SafeLink from '@ndla/safelink';
|
|
15
|
-
import { useNavigate } from 'react-router-dom';
|
|
13
|
+
import { IconButtonV2 } from '@ndla/button';
|
|
14
|
+
import SafeLink, { SafeLinkButton } from '@ndla/safelink';
|
|
16
15
|
import { HashTag } from '@ndla/icons/common';
|
|
16
|
+
import { css } from '@emotion/react';
|
|
17
|
+
import { DropdownMenu, DropdownContent, DropdownTrigger, DropdownItem } from '@ndla/dropdown-menu';
|
|
17
18
|
import resourceTypeColor from '../utils/resourceTypeColor';
|
|
18
19
|
import { resourceEmbedTypeMapping } from '../model/ContentType';
|
|
19
20
|
|
|
@@ -37,7 +38,11 @@ export const ResourceTitleLink = styled(SafeLink)`
|
|
|
37
38
|
}
|
|
38
39
|
`;
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
const StyledTrigger = styled(IconButtonV2)`
|
|
42
|
+
margin: 0px ${spacing.xsmall};
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
export const resourceHeadingStyle = css`
|
|
41
46
|
margin: 0;
|
|
42
47
|
overflow: hidden;
|
|
43
48
|
text-overflow: ellipsis;
|
|
@@ -113,19 +118,32 @@ const TagCounterWrapper = styled.span`
|
|
|
113
118
|
${fonts.sizes('14px', '14px')};
|
|
114
119
|
`;
|
|
115
120
|
|
|
116
|
-
|
|
121
|
+
interface ContentIconProps extends HTMLAttributes<HTMLSpanElement> {
|
|
117
122
|
contentType: string;
|
|
123
|
+
children?: ReactNode;
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
|
|
126
|
+
const StyledContentIconWrapper = styled.span`
|
|
121
127
|
width: 100%;
|
|
122
128
|
aspect-ratio: 4/3;
|
|
123
129
|
display: flex;
|
|
124
130
|
align-items: center;
|
|
125
131
|
justify-content: center;
|
|
126
|
-
background-color:
|
|
132
|
+
background-color: var(--content-background-color);
|
|
127
133
|
`;
|
|
128
134
|
|
|
135
|
+
export const ContentIconWrapper = ({ contentType, children, ...props }: ContentIconProps) => {
|
|
136
|
+
const contentIconWrapperVars = useMemo(
|
|
137
|
+
() => ({ '--content-background-color': resourceTypeColor(contentType) } as unknown as CSSProperties),
|
|
138
|
+
[contentType],
|
|
139
|
+
);
|
|
140
|
+
return (
|
|
141
|
+
<StyledContentIconWrapper {...props} style={contentIconWrapperVars}>
|
|
142
|
+
{children}
|
|
143
|
+
</StyledContentIconWrapper>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
129
147
|
interface TagListProps {
|
|
130
148
|
tags?: string[];
|
|
131
149
|
tagLinkPrefix?: string;
|
|
@@ -159,29 +177,40 @@ interface CompressedTagListProps {
|
|
|
159
177
|
}
|
|
160
178
|
|
|
161
179
|
export const CompressedTagList = ({ tags, tagLinkPrefix }: CompressedTagListProps) => {
|
|
162
|
-
const navigate = useNavigate();
|
|
163
180
|
const { t } = useTranslation();
|
|
164
|
-
const visibleTags = tags.slice(0, 3);
|
|
165
|
-
const remainingTags = tags.slice(3, tags.length)
|
|
166
|
-
|
|
167
|
-
icon: <HashTag />,
|
|
168
|
-
text: tag,
|
|
169
|
-
onClick: () => {
|
|
170
|
-
navigate(`${tagLinkPrefix ? tagLinkPrefix : ''}/${encodeURIComponent(tag)}`);
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
});
|
|
181
|
+
const visibleTags = useMemo(() => tags.slice(0, 3), [tags]);
|
|
182
|
+
const remainingTags = useMemo(() => tags.slice(3, tags.length), [tags]);
|
|
183
|
+
|
|
174
184
|
return (
|
|
175
185
|
<>
|
|
176
186
|
<TagList tagLinkPrefix={tagLinkPrefix} tags={visibleTags} />
|
|
177
187
|
{remainingTags.length > 0 && (
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
188
|
+
<DropdownMenu>
|
|
189
|
+
<DropdownTrigger>
|
|
190
|
+
<StyledTrigger
|
|
191
|
+
size="xsmall"
|
|
192
|
+
variant="ghost"
|
|
193
|
+
colorTheme="light"
|
|
194
|
+
aria-label={t('myNdla.moreTags', { count: remainingTags.length })}
|
|
195
|
+
>
|
|
196
|
+
{<TagCounterWrapper>{`+${remainingTags.length}`}</TagCounterWrapper>}
|
|
197
|
+
</StyledTrigger>
|
|
198
|
+
</DropdownTrigger>
|
|
199
|
+
<DropdownContent showArrow>
|
|
200
|
+
{remainingTags.map((tag, i) => (
|
|
201
|
+
<DropdownItem key={`tag-${i}`}>
|
|
202
|
+
<SafeLinkButton
|
|
203
|
+
to={`${tagLinkPrefix ?? ''}/${encodeURIComponent(tag)}`}
|
|
204
|
+
variant="ghost"
|
|
205
|
+
colorTheme="light"
|
|
206
|
+
>
|
|
207
|
+
<HashTag />
|
|
208
|
+
{tag}
|
|
209
|
+
</SafeLinkButton>
|
|
210
|
+
</DropdownItem>
|
|
211
|
+
))}
|
|
212
|
+
</DropdownContent>
|
|
213
|
+
</DropdownMenu>
|
|
185
214
|
)}
|
|
186
215
|
</>
|
|
187
216
|
);
|
|
@@ -18,15 +18,13 @@ import { TreeStructureType } from './types';
|
|
|
18
18
|
import { arrowNavigation } from './arrowNavigation';
|
|
19
19
|
import ContentLoader from '../ContentLoader';
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
isOpen: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const StyledRow = styled.div<StyledRowProps>`
|
|
21
|
+
const StyledRow = styled.div`
|
|
26
22
|
display: flex;
|
|
27
23
|
padding: ${spacing.xxsmall};
|
|
28
24
|
align-items: center;
|
|
29
|
-
|
|
25
|
+
&[data-open='true'] {
|
|
26
|
+
border-bottom: 1px solid ${colors.brand.tertiary};
|
|
27
|
+
}
|
|
30
28
|
`;
|
|
31
29
|
const StyledSelectedFolder = styled(Button)`
|
|
32
30
|
flex: 1;
|
|
@@ -110,7 +108,7 @@ const ComboboxButton = forwardRef<HTMLButtonElement, Props>(
|
|
|
110
108
|
|
|
111
109
|
return (
|
|
112
110
|
<StyledRow
|
|
113
|
-
|
|
111
|
+
data-open={showTree}
|
|
114
112
|
onMouseDown={(e) => {
|
|
115
113
|
if (!e.defaultPrevented) {
|
|
116
114
|
e.preventDefault();
|
|
@@ -6,21 +6,21 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import React, { KeyboardEvent, useEffect, useRef } from 'react';
|
|
9
|
+
import React, { CSSProperties, KeyboardEvent, useEffect, useMemo, useRef } from 'react';
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import styled from '@emotion/styled';
|
|
12
12
|
import { ArrowDropDownRounded } from '@ndla/icons/common';
|
|
13
13
|
import { FolderOutlined, FolderShared } from '@ndla/icons/contentType';
|
|
14
14
|
import { Done } from '@ndla/icons/editor';
|
|
15
15
|
import { ButtonV2 as Button } from '@ndla/button';
|
|
16
|
-
import { colors, spacing, animations,
|
|
16
|
+
import { colors, spacing, animations, misc, fonts } from '@ndla/core';
|
|
17
17
|
import SafeLink from '@ndla/safelink';
|
|
18
18
|
import { IFolder } from '@ndla/types-backend/learningpath-api';
|
|
19
19
|
import { CommonFolderItemsProps } from './types';
|
|
20
20
|
import { arrowNavigation } from './arrowNavigation';
|
|
21
21
|
import { treestructureId } from './helperFunctions';
|
|
22
22
|
|
|
23
|
-
const OpenButton = styled.span
|
|
23
|
+
const OpenButton = styled.span`
|
|
24
24
|
display: flex;
|
|
25
25
|
align-items: center;
|
|
26
26
|
justify-content: center;
|
|
@@ -34,7 +34,12 @@ const OpenButton = styled.span<{ isOpen: boolean }>`
|
|
|
34
34
|
svg {
|
|
35
35
|
width: 24px;
|
|
36
36
|
height: 24px;
|
|
37
|
-
transform: rotate(
|
|
37
|
+
transform: rotate(-90deg);
|
|
38
|
+
}
|
|
39
|
+
&[data-open='true'] {
|
|
40
|
+
svg {
|
|
41
|
+
transform: rotate(0deg);
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
`;
|
|
40
45
|
|
|
@@ -57,57 +62,67 @@ const FolderIconWrapper = styled.div`
|
|
|
57
62
|
}
|
|
58
63
|
`;
|
|
59
64
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
interface FolderNameProps {
|
|
63
|
-
selected?: boolean;
|
|
64
|
-
level: number;
|
|
65
|
-
isCreatingFolder?: boolean;
|
|
66
|
-
focused?: boolean;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const FolderName = styled(Button, { shouldForwardProp })<FolderNameProps>`
|
|
65
|
+
const FolderName = styled(Button)`
|
|
70
66
|
display: grid;
|
|
71
67
|
grid-template-columns: auto 1fr auto;
|
|
72
|
-
padding-left:
|
|
68
|
+
padding-left: calc(0.75 * ${spacing.normal} * var(--level));
|
|
73
69
|
gap: ${spacing.xxsmall};
|
|
74
70
|
border: none;
|
|
75
71
|
outline: none;
|
|
76
|
-
|
|
77
|
-
isCreatingFolder ? 'none' : selected ? colors.brand.lighter : focused && colors.brand.lightest};
|
|
78
|
-
color: ${({ isCreatingFolder, focused }) =>
|
|
79
|
-
isCreatingFolder && focused ? colors.brand.primary : colors.text.primary};
|
|
72
|
+
color: ${colors.text.primary};
|
|
80
73
|
transition: ${animations.durations.superFast};
|
|
81
74
|
word-break: break-word;
|
|
82
75
|
|
|
83
76
|
&:hover {
|
|
84
77
|
box-shadow: none;
|
|
85
78
|
outline: none;
|
|
86
|
-
background: ${
|
|
79
|
+
background: ${colors.brand.lightest};
|
|
87
80
|
color: ${colors.text.primary};
|
|
88
81
|
}
|
|
82
|
+
|
|
83
|
+
&[data-focused='true'] {
|
|
84
|
+
background: ${colors.brand.lightest};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&[data-selected='true'] {
|
|
88
|
+
background: ${colors.brand.lighter};
|
|
89
|
+
&:hover {
|
|
90
|
+
background: ${colors.brand.light};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&[data-creating='true'][data-focused='true'] {
|
|
95
|
+
color: ${colors.brand.primary};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&[data-creating='true'] {
|
|
99
|
+
background: none;
|
|
100
|
+
}
|
|
89
101
|
`;
|
|
90
102
|
|
|
91
103
|
const StyledDone = styled(Done)`
|
|
92
104
|
color: ${colors.support.green};
|
|
93
105
|
`;
|
|
94
106
|
|
|
95
|
-
const FolderNameLink = styled(SafeLink
|
|
107
|
+
const FolderNameLink = styled(SafeLink)`
|
|
96
108
|
display: grid;
|
|
97
109
|
align-items: center;
|
|
98
110
|
grid-template-columns: ${spacing.medium} 1fr auto;
|
|
99
111
|
padding: ${spacing.small} ${spacing.xxsmall};
|
|
100
|
-
|
|
112
|
+
padding-left: calc(0.75 * ${spacing.normal} * var(--level));
|
|
101
113
|
gap: ${spacing.xxsmall};
|
|
102
114
|
cursor: pointer;
|
|
103
115
|
|
|
104
116
|
border: none;
|
|
105
117
|
box-shadow: none;
|
|
106
|
-
color: ${
|
|
107
|
-
font-weight: ${({ selected }) => selected && fonts.weight.semibold};
|
|
118
|
+
color: ${colors.text.primary};
|
|
108
119
|
font-size: ${fonts.sizes('16px')};
|
|
109
120
|
transition: ${animations.durations.superFast};
|
|
110
121
|
word-break: break-word;
|
|
122
|
+
&[data-selected='true'] {
|
|
123
|
+
color: ${colors.brand.primary};
|
|
124
|
+
font-weight: ${fonts.weight.semibold};
|
|
125
|
+
}
|
|
111
126
|
&:hover,
|
|
112
127
|
&:focus {
|
|
113
128
|
color: ${colors.brand.primary};
|
|
@@ -145,6 +160,8 @@ const FolderItem = ({
|
|
|
145
160
|
const ref = useRef<HTMLButtonElement & HTMLAnchorElement>(null);
|
|
146
161
|
const selected = selectedFolder ? selectedFolder.id === id : false;
|
|
147
162
|
|
|
163
|
+
const levelVariable = useMemo(() => ({ '--level': level } as unknown as CSSProperties), [level]);
|
|
164
|
+
|
|
148
165
|
const focused = focusedFolder?.id === id;
|
|
149
166
|
|
|
150
167
|
const handleClickFolder = () => {
|
|
@@ -195,7 +212,7 @@ const FolderItem = ({
|
|
|
195
212
|
aria-current={selected ? 'page' : undefined}
|
|
196
213
|
aria-describedby={containsResource ? `alreadyAdded-${folder.id}` : undefined}
|
|
197
214
|
ref={ref}
|
|
198
|
-
|
|
215
|
+
style={levelVariable}
|
|
199
216
|
onKeyDown={(e: KeyboardEvent<HTMLElement>) => {
|
|
200
217
|
if (e.key === 'Enter') {
|
|
201
218
|
setSelectedFolder(folder);
|
|
@@ -205,7 +222,7 @@ const FolderItem = ({
|
|
|
205
222
|
}}
|
|
206
223
|
to={loading ? '' : linkPath}
|
|
207
224
|
tabIndex={tabable ? 0 : -1}
|
|
208
|
-
selected={selected}
|
|
225
|
+
data-selected={selected}
|
|
209
226
|
onFocus={() => setFocusedFolder(folder)}
|
|
210
227
|
onClick={handleClickFolder}
|
|
211
228
|
>
|
|
@@ -213,7 +230,7 @@ const FolderItem = ({
|
|
|
213
230
|
<OpenButton
|
|
214
231
|
aria-hidden
|
|
215
232
|
tabIndex={-1}
|
|
216
|
-
|
|
233
|
+
data-open={isOpen}
|
|
217
234
|
onClick={(e) => {
|
|
218
235
|
e.stopPropagation();
|
|
219
236
|
e.preventDefault();
|
|
@@ -237,7 +254,7 @@ const FolderItem = ({
|
|
|
237
254
|
id={treestructureId(type, folder.id)}
|
|
238
255
|
aria-expanded={isMaxDepth || emptyFolder ? undefined : isOpen}
|
|
239
256
|
aria-selected={selected}
|
|
240
|
-
focused={focusedFolder?.id === folder.id}
|
|
257
|
+
data-focused={focusedFolder?.id === folder.id}
|
|
241
258
|
aria-describedby={containsResource ? `alreadyAdded-${folder.id}` : undefined}
|
|
242
259
|
aria-label={`${name}${folder.status === 'shared' ? `, ${t('myNdla.folder.sharing.shared')}` : ''}`}
|
|
243
260
|
variant="ghost"
|
|
@@ -245,21 +262,19 @@ const FolderItem = ({
|
|
|
245
262
|
fontWeight="normal"
|
|
246
263
|
colorTheme="light"
|
|
247
264
|
ref={ref}
|
|
248
|
-
|
|
249
|
-
selected={selected}
|
|
265
|
+
style={levelVariable}
|
|
266
|
+
data-selected={selected}
|
|
250
267
|
disabled={loading}
|
|
251
|
-
onFocus={(
|
|
252
|
-
setFocusedFolder(focusedFolder || folder);
|
|
253
|
-
}}
|
|
268
|
+
onFocus={() => setFocusedFolder(focusedFolder || folder)}
|
|
254
269
|
onClick={handleClickFolder}
|
|
255
|
-
|
|
270
|
+
data-creating={isCreatingFolder}
|
|
256
271
|
>
|
|
257
272
|
<IconWrapper>
|
|
258
273
|
{!hideArrow && (
|
|
259
274
|
<OpenButton
|
|
260
275
|
aria-hidden
|
|
261
276
|
tabIndex={-1}
|
|
262
|
-
|
|
277
|
+
data-open={isOpen}
|
|
263
278
|
onClick={(e) => {
|
|
264
279
|
e.stopPropagation();
|
|
265
280
|
setFocusedFolder(folder);
|