@ndla/ui 27.1.7 → 29.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.
Files changed (111) hide show
  1. package/es/Frontpage/FrontpageAllSubjects.js +9 -8
  2. package/es/Resource/BlockResource.js +22 -13
  3. package/es/Resource/ListResource.js +24 -15
  4. package/es/Resource/resourceComponents.js +28 -28
  5. package/es/TagSelector/Control.js +23 -0
  6. package/es/TagSelector/DropdownIndicator.js +66 -0
  7. package/es/TagSelector/Input.js +19 -0
  8. package/es/TagSelector/Menu.js +26 -0
  9. package/es/TagSelector/MenuList.js +22 -0
  10. package/es/TagSelector/Option.js +55 -0
  11. package/es/TagSelector/SelectContainer.js +18 -0
  12. package/es/TagSelector/TagSelector.js +161 -100
  13. package/es/TagSelector/ValueButton.js +46 -0
  14. package/es/TagSelector/ariaMessages.js +104 -0
  15. package/es/TagSelector/index.js +2 -1
  16. package/es/TagSelector/types.js +0 -0
  17. package/es/TreeStructure/ComboboxButton.js +19 -18
  18. package/es/TreeStructure/TreeStructure.js +8 -8
  19. package/es/locale/messages-en.js +46 -8
  20. package/es/locale/messages-nb.js +57 -19
  21. package/es/locale/messages-nn.js +56 -18
  22. package/es/locale/messages-se.js +48 -10
  23. package/es/locale/messages-sma.js +57 -19
  24. package/es/model/ContentType.js +23 -1
  25. package/es/model/SubjectCategories.js +1 -5
  26. package/es/model/index.js +3 -2
  27. package/lib/Frontpage/FrontpageAllSubjects.js +10 -8
  28. package/lib/Resource/BlockResource.d.ts +6 -3
  29. package/lib/Resource/BlockResource.js +23 -12
  30. package/lib/Resource/ListResource.d.ts +6 -3
  31. package/lib/Resource/ListResource.js +25 -14
  32. package/lib/Resource/resourceComponents.d.ts +6 -3
  33. package/lib/Resource/resourceComponents.js +30 -30
  34. package/lib/TagSelector/Control.d.ts +12 -0
  35. package/lib/TagSelector/Control.js +35 -0
  36. package/lib/TagSelector/DropdownIndicator.d.ts +12 -0
  37. package/lib/TagSelector/DropdownIndicator.js +80 -0
  38. package/lib/TagSelector/Input.d.ts +12 -0
  39. package/lib/TagSelector/Input.js +33 -0
  40. package/lib/TagSelector/Menu.d.ts +12 -0
  41. package/lib/TagSelector/Menu.js +40 -0
  42. package/lib/TagSelector/MenuList.d.ts +13 -0
  43. package/lib/TagSelector/MenuList.js +36 -0
  44. package/lib/TagSelector/Option.d.ts +12 -0
  45. package/lib/TagSelector/Option.js +61 -0
  46. package/lib/TagSelector/SelectContainer.d.ts +12 -0
  47. package/lib/TagSelector/SelectContainer.js +31 -0
  48. package/lib/TagSelector/TagSelector.d.ts +14 -11
  49. package/lib/TagSelector/TagSelector.js +165 -96
  50. package/lib/TagSelector/ValueButton.d.ts +16 -0
  51. package/lib/TagSelector/ValueButton.js +55 -0
  52. package/lib/TagSelector/ariaMessages.d.ts +16 -0
  53. package/lib/TagSelector/ariaMessages.js +113 -0
  54. package/lib/TagSelector/index.d.ts +2 -1
  55. package/lib/TagSelector/index.js +3 -5
  56. package/lib/TagSelector/types.d.ts +11 -0
  57. package/lib/TagSelector/types.js +1 -0
  58. package/lib/TreeStructure/ComboboxButton.js +19 -18
  59. package/lib/TreeStructure/TreeStructure.js +7 -7
  60. package/lib/locale/messages-en.d.ts +56 -25
  61. package/lib/locale/messages-en.js +46 -8
  62. package/lib/locale/messages-nb.d.ts +56 -25
  63. package/lib/locale/messages-nb.js +57 -19
  64. package/lib/locale/messages-nn.d.ts +56 -25
  65. package/lib/locale/messages-nn.js +56 -18
  66. package/lib/locale/messages-se.d.ts +56 -25
  67. package/lib/locale/messages-se.js +48 -10
  68. package/lib/locale/messages-sma.d.ts +56 -25
  69. package/lib/locale/messages-sma.js +57 -19
  70. package/lib/model/ContentType.d.ts +18 -0
  71. package/lib/model/ContentType.js +32 -2
  72. package/lib/model/SubjectCategories.d.ts +0 -3
  73. package/lib/model/SubjectCategories.js +3 -10
  74. package/lib/model/index.d.ts +12 -2
  75. package/lib/model/index.js +4 -3
  76. package/package.json +15 -14
  77. package/src/Frontpage/FrontpageAllSubjects.tsx +5 -2
  78. package/src/Resource/BlockResource.tsx +18 -11
  79. package/src/Resource/ListResource.tsx +14 -11
  80. package/src/Resource/resourceComponents.tsx +13 -14
  81. package/src/TagSelector/Control.tsx +34 -0
  82. package/src/TagSelector/DropdownIndicator.tsx +47 -0
  83. package/src/TagSelector/Input.tsx +31 -0
  84. package/src/TagSelector/Menu.tsx +38 -0
  85. package/src/TagSelector/MenuList.tsx +30 -0
  86. package/src/TagSelector/Option.tsx +53 -0
  87. package/src/TagSelector/SelectContainer.tsx +32 -0
  88. package/src/TagSelector/TagSelector.tsx +105 -84
  89. package/src/TagSelector/ValueButton.tsx +46 -0
  90. package/src/TagSelector/ariaMessages.ts +87 -0
  91. package/src/TagSelector/index.ts +2 -1
  92. package/src/TagSelector/types.ts +12 -0
  93. package/src/TreeStructure/ComboboxButton.tsx +15 -17
  94. package/src/TreeStructure/TreeStructure.tsx +2 -11
  95. package/src/locale/messages-en.ts +46 -9
  96. package/src/locale/messages-nb.ts +57 -20
  97. package/src/locale/messages-nn.ts +56 -19
  98. package/src/locale/messages-se.ts +48 -11
  99. package/src/locale/messages-sma.ts +57 -20
  100. package/src/model/ContentType.ts +29 -0
  101. package/src/model/SubjectCategories.ts +0 -5
  102. package/src/model/index.ts +2 -1
  103. package/es/TagSelector/SuggestionInput.js +0 -285
  104. package/es/TagSelector/Suggestions.js +0 -97
  105. package/lib/TagSelector/SuggestionInput.d.ts +0 -19
  106. package/lib/TagSelector/SuggestionInput.js +0 -299
  107. package/lib/TagSelector/Suggestions.d.ts +0 -12
  108. package/lib/TagSelector/Suggestions.js +0 -99
  109. package/src/.DS_Store +0 -0
  110. package/src/TagSelector/SuggestionInput.tsx +0 -287
  111. package/src/TagSelector/Suggestions.tsx +0 -139
@@ -3,7 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.ListOfContentTypes = exports.MULTIDISCIPLINARY_TOPIC = exports.TOPIC = exports.LEARNING_PATH = exports.SOURCE_MATERIAL = exports.EXTERNAL_LEARNING_RESOURCES = exports.SUBJECT = exports.ASSESSMENT_RESOURCES = exports.TASKS_AND_ACTIVITIES = exports.SUBJECT_MATERIAL = void 0;
6
+ exports.contentTypeMapping = exports.ListOfContentTypes = exports.RESOURCE_TYPE_SOURCE_MATERIAL = exports.RESOURCE_TYPE_EXTERNAL_LEARNING_RESOURCES = exports.RESOURCE_TYPE_ASSESSMENT_RESOURCES = exports.RESOURCE_TYPE_TASKS_AND_ACTIVITIES = exports.RESOURCE_TYPE_SUBJECT_MATERIAL = exports.RESOURCE_TYPE_LEARNING_PATH = exports.contentTypes = exports.MULTIDISCIPLINARY_TOPIC = exports.TOPIC = exports.LEARNING_PATH = exports.SOURCE_MATERIAL = exports.EXTERNAL_LEARNING_RESOURCES = exports.SUBJECT = exports.ASSESSMENT_RESOURCES = exports.TASKS_AND_ACTIVITIES = exports.SUBJECT_MATERIAL = void 0;
7
+
8
+ var _contentTypeMapping;
9
+
10
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
7
11
 
8
12
  /**
9
13
  * Copyright (c) 2017-present, NDLA.
@@ -30,5 +34,31 @@ var TOPIC = 'topic';
30
34
  exports.TOPIC = TOPIC;
31
35
  var MULTIDISCIPLINARY_TOPIC = 'multidisciplinary-topic';
32
36
  exports.MULTIDISCIPLINARY_TOPIC = MULTIDISCIPLINARY_TOPIC;
37
+ var contentTypes = {
38
+ SUBJECT_MATERIAL: SUBJECT_MATERIAL,
39
+ TASKS_AND_ACTIVITIES: TASKS_AND_ACTIVITIES,
40
+ ASSESSMENT_RESOURCES: ASSESSMENT_RESOURCES,
41
+ SUBJECT: SUBJECT,
42
+ EXTERNAL_LEARNING_RESOURCES: EXTERNAL_LEARNING_RESOURCES,
43
+ SOURCE_MATERIAL: SOURCE_MATERIAL,
44
+ LEARNING_PATH: LEARNING_PATH,
45
+ TOPIC: TOPIC,
46
+ MULTIDISCIPLINARY_TOPIC: MULTIDISCIPLINARY_TOPIC
47
+ };
48
+ exports.contentTypes = contentTypes;
49
+ var RESOURCE_TYPE_LEARNING_PATH = 'urn:resourcetype:learningPath';
50
+ exports.RESOURCE_TYPE_LEARNING_PATH = RESOURCE_TYPE_LEARNING_PATH;
51
+ var RESOURCE_TYPE_SUBJECT_MATERIAL = 'urn:resourcetype:subjectMaterial';
52
+ exports.RESOURCE_TYPE_SUBJECT_MATERIAL = RESOURCE_TYPE_SUBJECT_MATERIAL;
53
+ var RESOURCE_TYPE_TASKS_AND_ACTIVITIES = 'urn:resourcetype:tasksAndActivities';
54
+ exports.RESOURCE_TYPE_TASKS_AND_ACTIVITIES = RESOURCE_TYPE_TASKS_AND_ACTIVITIES;
55
+ var RESOURCE_TYPE_ASSESSMENT_RESOURCES = 'urn:resourcetype:reviewResource';
56
+ exports.RESOURCE_TYPE_ASSESSMENT_RESOURCES = RESOURCE_TYPE_ASSESSMENT_RESOURCES;
57
+ var RESOURCE_TYPE_EXTERNAL_LEARNING_RESOURCES = 'urn:resourcetype:externalResource';
58
+ exports.RESOURCE_TYPE_EXTERNAL_LEARNING_RESOURCES = RESOURCE_TYPE_EXTERNAL_LEARNING_RESOURCES;
59
+ var RESOURCE_TYPE_SOURCE_MATERIAL = 'urn:resourcetype:SourceMaterial';
60
+ exports.RESOURCE_TYPE_SOURCE_MATERIAL = RESOURCE_TYPE_SOURCE_MATERIAL;
33
61
  var ListOfContentTypes = ['SUBJECT_MATERIAL', 'TASKS_AND_ACTIVITIES', 'ASSESSMENT_RESOURCES', 'SUBJECT', 'EXTERNAL_LEARNING_RESOURCES', 'SOURCE_MATERIAL', 'LEARNING_PATH', 'TOPIC', 'MULTIDISIPLINARY_TOPIC'];
34
- exports.ListOfContentTypes = ListOfContentTypes;
62
+ exports.ListOfContentTypes = ListOfContentTypes;
63
+ var contentTypeMapping = (_contentTypeMapping = {}, _defineProperty(_contentTypeMapping, RESOURCE_TYPE_LEARNING_PATH, LEARNING_PATH), _defineProperty(_contentTypeMapping, RESOURCE_TYPE_SUBJECT_MATERIAL, SUBJECT_MATERIAL), _defineProperty(_contentTypeMapping, RESOURCE_TYPE_TASKS_AND_ACTIVITIES, TASKS_AND_ACTIVITIES), _defineProperty(_contentTypeMapping, RESOURCE_TYPE_ASSESSMENT_RESOURCES, ASSESSMENT_RESOURCES), _defineProperty(_contentTypeMapping, RESOURCE_TYPE_EXTERNAL_LEARNING_RESOURCES, EXTERNAL_LEARNING_RESOURCES), _defineProperty(_contentTypeMapping, RESOURCE_TYPE_SOURCE_MATERIAL, SOURCE_MATERIAL), _defineProperty(_contentTypeMapping, "default", SUBJECT_MATERIAL), _contentTypeMapping);
64
+ exports.contentTypeMapping = contentTypeMapping;
@@ -8,6 +8,3 @@
8
8
  export declare const ACTIVE_SUBJECTS = "active";
9
9
  export declare const BETA_SUBJECTS = "beta";
10
10
  export declare const ARCHIVE_SUBJECTS = "archive";
11
- export declare const COMMON_SUBJECTS = "common";
12
- export declare const PROGRAMME_SUBJECTS = "programme";
13
- export declare const SPECIALIZED_SUBJECTS = "specialized";
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.SPECIALIZED_SUBJECTS = exports.PROGRAMME_SUBJECTS = exports.COMMON_SUBJECTS = exports.ARCHIVE_SUBJECTS = exports.BETA_SUBJECTS = exports.ACTIVE_SUBJECTS = void 0;
6
+ exports.ARCHIVE_SUBJECTS = exports.BETA_SUBJECTS = exports.ACTIVE_SUBJECTS = void 0;
7
7
 
8
8
  /**
9
9
  * Copyright (c) 2021-present, NDLA.
@@ -16,12 +16,5 @@ var ACTIVE_SUBJECTS = 'active';
16
16
  exports.ACTIVE_SUBJECTS = ACTIVE_SUBJECTS;
17
17
  var BETA_SUBJECTS = 'beta';
18
18
  exports.BETA_SUBJECTS = BETA_SUBJECTS;
19
- var ARCHIVE_SUBJECTS = 'archive'; // deprecated
20
-
21
- exports.ARCHIVE_SUBJECTS = ARCHIVE_SUBJECTS;
22
- var COMMON_SUBJECTS = 'common';
23
- exports.COMMON_SUBJECTS = COMMON_SUBJECTS;
24
- var PROGRAMME_SUBJECTS = 'programme';
25
- exports.PROGRAMME_SUBJECTS = PROGRAMME_SUBJECTS;
26
- var SPECIALIZED_SUBJECTS = 'specialized';
27
- exports.SPECIALIZED_SUBJECTS = SPECIALIZED_SUBJECTS;
19
+ var ARCHIVE_SUBJECTS = 'archive';
20
+ exports.ARCHIVE_SUBJECTS = ARCHIVE_SUBJECTS;
@@ -5,12 +5,22 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
7
  */
8
- import * as contentTypes from './ContentType';
9
8
  import * as subjectCategories from './SubjectCategories';
10
9
  import * as subjectTypes from './SubjectTypes';
11
10
  declare const model: {
12
- contentTypes: typeof contentTypes;
11
+ contentTypes: {
12
+ SUBJECT_MATERIAL: string;
13
+ TASKS_AND_ACTIVITIES: string;
14
+ ASSESSMENT_RESOURCES: string;
15
+ SUBJECT: string;
16
+ EXTERNAL_LEARNING_RESOURCES: string;
17
+ SOURCE_MATERIAL: string;
18
+ LEARNING_PATH: string;
19
+ TOPIC: string;
20
+ MULTIDISCIPLINARY_TOPIC: string;
21
+ };
13
22
  subjectCategories: typeof subjectCategories;
14
23
  subjectTypes: typeof subjectTypes;
24
+ contentTypeMapping: Record<string, string>;
15
25
  };
16
26
  export default model;
@@ -7,7 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  });
8
8
  exports["default"] = void 0;
9
9
 
10
- var contentTypes = _interopRequireWildcard(require("./ContentType"));
10
+ var _ContentType = require("./ContentType");
11
11
 
12
12
  var subjectCategories = _interopRequireWildcard(require("./SubjectCategories"));
13
13
 
@@ -25,9 +25,10 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj;
25
25
  *
26
26
  */
27
27
  var model = {
28
- contentTypes: contentTypes,
28
+ contentTypes: _ContentType.contentTypes,
29
29
  subjectCategories: subjectCategories,
30
- subjectTypes: subjectTypes
30
+ subjectTypes: subjectTypes,
31
+ contentTypeMapping: _ContentType.contentTypeMapping
31
32
  };
32
33
  var _default = model;
33
34
  exports["default"] = _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
- "version": "27.1.7",
3
+ "version": "29.0.0",
4
4
  "description": "UI component library for NDLA.",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -32,19 +32,19 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@ndla/article-scripts": "^3.0.2",
35
- "@ndla/button": "^3.6.1",
36
- "@ndla/carousel": "^1.2.23",
37
- "@ndla/core": "^2.3.5",
38
- "@ndla/forms": "^3.3.5",
35
+ "@ndla/button": "^3.7.0",
36
+ "@ndla/carousel": "^1.2.24",
37
+ "@ndla/core": "^2.3.6",
38
+ "@ndla/forms": "^3.3.6",
39
39
  "@ndla/hooks": "^1.1.6",
40
- "@ndla/icons": "^1.13.0",
41
- "@ndla/licenses": "^5.0.17",
42
- "@ndla/modal": "^1.5.0",
43
- "@ndla/notion": "^3.1.46",
44
- "@ndla/safelink": "^2.2.19",
45
- "@ndla/switch": "^0.1.13",
46
- "@ndla/tabs": "^1.1.22",
47
- "@ndla/tooltip": "^2.2.1",
40
+ "@ndla/icons": "^1.14.0",
41
+ "@ndla/licenses": "^5.0.18",
42
+ "@ndla/modal": "^1.6.1",
43
+ "@ndla/notion": "^3.1.48",
44
+ "@ndla/safelink": "^2.2.20",
45
+ "@ndla/switch": "^0.1.14",
46
+ "@ndla/tabs": "^1.1.23",
47
+ "@ndla/tooltip": "^2.2.2",
48
48
  "@ndla/types-learningpath-api": "^0.0.13",
49
49
  "@ndla/util": "^3.1.0",
50
50
  "@reach/menu-button": "^0.16.2",
@@ -58,6 +58,7 @@
58
58
  "lodash": "^4.17.20",
59
59
  "react-bem-helper": "1.4.1",
60
60
  "react-device-detect": "^2.1.2",
61
+ "react-select": "^5.4.0",
61
62
  "react-swipeable": "^6.2.0",
62
63
  "react-transition-group": "^2.5.3",
63
64
  "remarkable": "^2.0.1",
@@ -85,5 +86,5 @@
85
86
  "publishConfig": {
86
87
  "access": "public"
87
88
  },
88
- "gitHead": "a2bbbd2a97f3e3468c06cc1aea9849a24d9e5461"
89
+ "gitHead": "2c5d6b7038c442a871b4cd69247d243cadd129da"
89
90
  }
@@ -6,6 +6,7 @@ import SafeLink from '@ndla/safelink';
6
6
  import { colors, fonts, mq, breakpoints } from '@ndla/core';
7
7
  import { MessageBox } from '../Messages';
8
8
  import { ToggleItem } from '../Filter';
9
+ import constants from '../model';
9
10
 
10
11
  const StyledWrapper = styled.nav`
11
12
  margin: 32px 0 0;
@@ -198,12 +199,14 @@ const FrontpageAllSubjects = ({
198
199
  content: (
199
200
  <>
200
201
  {/* Should be persistent til fall 2022 */}
201
- {category.name === t('subjectCategories.beta') && (
202
+ {(category.name === t('subjectCategories.beta') ||
203
+ category.type === constants.subjectCategories.BETA_SUBJECTS) && (
202
204
  <MessageBoxWrapper>
203
205
  <MessageBox>{t('messageBoxInfo.frontPageBeta')}</MessageBox>
204
206
  </MessageBoxWrapper>
205
207
  )}
206
- {category.name === t('subjectCategories.archive') && (
208
+ {(category.name === t('subjectCategories.archive') ||
209
+ category.type === constants.subjectCategories.ARCHIVE_SUBJECTS) && (
207
210
  <MessageBoxWrapper>
208
211
  <MessageBox>{t('messageBoxInfo.frontPageExpired')}</MessageBox>
209
212
  </MessageBoxWrapper>
@@ -17,12 +17,13 @@ import {
17
17
  ResourceImageProps,
18
18
  ResourceTitle,
19
19
  Row,
20
- TopicList,
20
+ ResourceTypeList,
21
21
  ResourceTitleLink,
22
22
  LoaderProps,
23
23
  StyledContentIconWrapper,
24
24
  } from './resourceComponents';
25
25
  import ContentLoader from '../ContentLoader';
26
+ import { contentTypeMapping } from '../model/ContentType';
26
27
 
27
28
  interface BlockResourceProps {
28
29
  id: string;
@@ -30,13 +31,13 @@ interface BlockResourceProps {
30
31
  tagLinkPrefix?: string;
31
32
  title: string;
32
33
  resourceImage: ResourceImageProps;
33
- topics: string[];
34
34
  tags?: string[];
35
35
  description?: string;
36
+ headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
36
37
  menuItems?: MenuItemProps[];
37
38
  isLoading?: boolean;
38
39
  targetBlank?: boolean;
39
- contentType: string;
40
+ resourceTypes?: { id: string; name: string }[];
40
41
  }
41
42
 
42
43
  const BlockElementWrapper = styled.div`
@@ -128,7 +129,7 @@ const BlockImage = ({ image, loading, contentType }: BlockImageProps) => {
128
129
  }
129
130
  };
130
131
 
131
- const TopicAndTitleLoader = ({ children, loading }: LoaderProps) => {
132
+ const ResourceTypeAndTitleLoader = ({ children, loading }: LoaderProps) => {
132
133
  if (loading) {
133
134
  return (
134
135
  <ContentLoader height={'18px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
@@ -148,14 +149,16 @@ const BlockResource = ({
148
149
  title,
149
150
  tags,
150
151
  resourceImage,
151
- topics,
152
152
  description,
153
153
  menuItems,
154
154
  isLoading,
155
+ headingLevel = 'h2',
155
156
  targetBlank,
156
- contentType,
157
+ resourceTypes,
157
158
  }: BlockResourceProps) => {
158
159
  const linkRef = useRef<HTMLAnchorElement>(null);
160
+ const firstResourceType = resourceTypes?.[0].id ?? '';
161
+ const Title = ResourceTitle.withComponent(headingLevel);
159
162
 
160
163
  const handleClick = () => {
161
164
  if (linkRef.current) {
@@ -166,15 +169,19 @@ const BlockResource = ({
166
169
  return (
167
170
  <BlockElementWrapper onClick={handleClick} id={id}>
168
171
  <ImageWrapper>
169
- <BlockImage image={resourceImage} loading={isLoading} contentType={contentType} />
172
+ <BlockImage
173
+ image={resourceImage}
174
+ loading={isLoading}
175
+ contentType={contentTypeMapping[firstResourceType] ?? contentTypeMapping['default']}
176
+ />
170
177
  </ImageWrapper>
171
178
  <BlockInfoWrapper>
172
- <TopicAndTitleLoader loading={isLoading}>
179
+ <ResourceTypeAndTitleLoader loading={isLoading}>
173
180
  <ResourceTitleLink title={title} target={targetBlank ? '_blank' : undefined} to={link} ref={linkRef}>
174
- <ResourceTitle>{title}</ResourceTitle>
181
+ <Title>{title}</Title>
175
182
  </ResourceTitleLink>
176
- </TopicAndTitleLoader>
177
- <TopicList topics={topics} />
183
+ </ResourceTypeAndTitleLoader>
184
+ <ResourceTypeList resourceTypes={resourceTypes} />
178
185
  <BlockDescription>{description}</BlockDescription>
179
186
  <RightRow>
180
187
  {tags && <CompressedTagList tagLinkPrefix={tagLinkPrefix} tags={tags} />}
@@ -16,12 +16,13 @@ import {
16
16
  ResourceImageProps,
17
17
  ResourceTitle,
18
18
  ResourceTitleLink,
19
- TopicList,
19
+ ResourceTypeList,
20
20
  StyledContentIconWrapper,
21
21
  LoaderProps,
22
22
  } from './resourceComponents';
23
23
  import ContentLoader from '../ContentLoader';
24
24
  import ContentTypeBadge from '../ContentTypeBadge';
25
+ import { contentTypeMapping } from '../model/ContentType';
25
26
 
26
27
  const StyledResourceDescription = styled.p`
27
28
  grid-area: description;
@@ -124,13 +125,13 @@ export interface ListResourceProps {
124
125
  tagLinkPrefix?: string;
125
126
  title: string;
126
127
  resourceImage: ResourceImageProps;
127
- topics: string[];
128
+ headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
129
+ resourceTypes: { id: string; name: string }[];
128
130
  tags?: string[];
129
131
  description?: string;
130
132
  menuItems?: MenuItemProps[];
131
133
  isLoading?: boolean;
132
134
  targetBlank?: boolean;
133
- contentType: string;
134
135
  }
135
136
 
136
137
  interface ListResourceImageProps {
@@ -169,7 +170,7 @@ const ListResourceImage = ({ resourceImage, loading, type, contentType }: ListRe
169
170
  );
170
171
  };
171
172
 
172
- const TopicAndTitleLoader = ({ loading, children }: LoaderProps) => {
173
+ const ResourceTypeAndTitleLoader = ({ loading, children }: LoaderProps) => {
173
174
  if (loading) {
174
175
  return (
175
176
  <ContentLoader height={'40px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
@@ -205,16 +206,18 @@ const ListResource = ({
205
206
  title,
206
207
  tags,
207
208
  resourceImage,
208
- topics,
209
+ resourceTypes,
210
+ headingLevel = 'h2',
209
211
  description,
210
212
  menuItems,
211
213
  isLoading = false,
212
214
  targetBlank,
213
- contentType,
214
215
  }: ListResourceProps) => {
215
216
  const showDescription = description !== undefined;
216
217
  const imageType = showDescription ? 'normal' : 'compact';
217
218
  const linkRef = useRef<HTMLAnchorElement>(null);
219
+ const firstContentType = resourceTypes?.[0]?.id ?? '';
220
+ const Title = ResourceTitle.withComponent(headingLevel);
218
221
  const handleClick = () => {
219
222
  if (linkRef.current) {
220
223
  linkRef.current.click();
@@ -228,16 +231,16 @@ const ListResource = ({
228
231
  resourceImage={resourceImage}
229
232
  loading={isLoading}
230
233
  type={imageType}
231
- contentType={contentType}
234
+ contentType={contentTypeMapping[firstContentType] ?? contentTypeMapping['default']}
232
235
  />
233
236
  </StyledImageWrapper>
234
237
  <TopicAndTitleWrapper>
235
- <TopicAndTitleLoader loading={isLoading}>
238
+ <ResourceTypeAndTitleLoader loading={isLoading}>
236
239
  <ResourceTitleLink to={link} target={targetBlank ? '_blank' : undefined} ref={linkRef}>
237
- <ResourceTitle>{title}</ResourceTitle>
240
+ <Title>{title}</Title>
238
241
  </ResourceTitleLink>
239
- <TopicList topics={topics} />
240
- </TopicAndTitleLoader>
242
+ <ResourceTypeList resourceTypes={resourceTypes} />
243
+ </ResourceTypeAndTitleLoader>
241
244
  </TopicAndTitleWrapper>
242
245
  {showDescription && <ResourceDescription description={description} loading={isLoading} />}
243
246
  <TagsandActionMenu>
@@ -65,13 +65,12 @@ const StyledSafeLink = styled(SafeLink)`
65
65
  }
66
66
  `;
67
67
 
68
- const StyledTopicList = styled.ul`
68
+ const StyledResourceTypeList = styled.ul`
69
69
  list-style: none;
70
70
  display: flex;
71
71
  margin: 0;
72
72
  padding: 0;
73
73
  overflow: hidden;
74
- grid-area: topicList;
75
74
  `;
76
75
 
77
76
  const StyledTopicDivider = styled.span`
@@ -79,7 +78,7 @@ const StyledTopicDivider = styled.span`
79
78
  padding: 0 ${spacing.xxsmall};
80
79
  `;
81
80
 
82
- const StyledTopicListElement = styled.li`
81
+ const StyledResourceListElement = styled.li`
83
82
  ${fonts.sizes(12)};
84
83
  margin: 0;
85
84
  line-height: 1.5;
@@ -187,21 +186,21 @@ export const CompressedTagList = ({ tags, tagLinkPrefix }: CompressedTagListProp
187
186
  );
188
187
  };
189
188
 
190
- interface TopicListProps {
191
- topics?: string[];
189
+ interface ResourceTypeListProps {
190
+ resourceTypes?: { id: string; name: string }[];
192
191
  }
193
192
 
194
- export const TopicList = ({ topics }: TopicListProps) => {
193
+ export const ResourceTypeList = ({ resourceTypes }: ResourceTypeListProps) => {
195
194
  const { t } = useTranslation();
196
- if (!topics) return null;
195
+ if (!resourceTypes) return null;
197
196
  return (
198
- <StyledTopicList aria-label={t('navigation.topics')}>
199
- {topics.map((topic, i) => (
200
- <StyledTopicListElement key={topic}>
201
- {topic}
202
- {i !== topics.length - 1 && <StyledTopicDivider aria-hidden="true">•</StyledTopicDivider>}
203
- </StyledTopicListElement>
197
+ <StyledResourceTypeList aria-label={t('navigation.topics')}>
198
+ {resourceTypes.map((resource, i) => (
199
+ <StyledResourceListElement key={resource.id}>
200
+ {resource.name}
201
+ {i !== resourceTypes.length - 1 && <StyledTopicDivider aria-hidden="true">•</StyledTopicDivider>}
202
+ </StyledResourceListElement>
204
203
  ))}
205
- </StyledTopicList>
204
+ </StyledResourceTypeList>
206
205
  );
207
206
  };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import styled from '@emotion/styled';
10
+ import { spacing, utils } from '@ndla/core';
11
+ import React from 'react';
12
+ import { ControlProps } from 'react-select';
13
+ import { TagType } from './types';
14
+
15
+ const StyledTagSelectorControl = styled.div`
16
+ display: flex;
17
+ flex-wrap: wrap;
18
+ justify-content: space-between;
19
+ align-items: center;
20
+ margin: ${spacing.xxsmall};
21
+
22
+ overflow: overlay;
23
+ ${utils.scrollbar}
24
+ `;
25
+
26
+ const Control = ({ innerProps, children, innerRef }: ControlProps<TagType, true>) => {
27
+ return (
28
+ <StyledTagSelectorControl ref={innerRef} {...innerProps}>
29
+ {children}
30
+ </StyledTagSelectorControl>
31
+ );
32
+ };
33
+
34
+ export default Control;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import { ChevronDown, ChevronUp } from '@ndla/icons/common';
10
+ import React, { useMemo } from 'react';
11
+ import styled from '@emotion/styled';
12
+ import { useTranslation } from 'react-i18next';
13
+ import { DropdownIndicatorProps, components } from 'react-select';
14
+ import { iconButtonStyle } from '@ndla/button';
15
+ import { TagType } from './types';
16
+
17
+ const StyledIconWrapper = styled.span`
18
+ svg {
19
+ pointer-events: none;
20
+ }
21
+ `;
22
+
23
+ const DropdownIndicator = ({ selectProps, children, ...props }: DropdownIndicatorProps<TagType, true>) => {
24
+ const { t } = useTranslation();
25
+
26
+ const { menuIsOpen } = selectProps;
27
+ const Icon = menuIsOpen ? ChevronUp : ChevronDown;
28
+
29
+ const css = useMemo(
30
+ () => iconButtonStyle({ colorTheme: 'greyLighter', variant: 'ghost', shape: 'pill', size: 'small' }),
31
+ [],
32
+ );
33
+
34
+ return (
35
+ <components.DropdownIndicator
36
+ css={css}
37
+ {...props}
38
+ selectProps={selectProps}
39
+ aria-label={menuIsOpen ? t('tagSelector.hideTags') : t('tagSelector.showTags')}>
40
+ <StyledIconWrapper aria-hidden>
41
+ <Icon />
42
+ </StyledIconWrapper>
43
+ </components.DropdownIndicator>
44
+ );
45
+ };
46
+
47
+ export default DropdownIndicator;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import css from '@emotion/css';
10
+ import { colors, spacing } from '@ndla/core';
11
+ import React from 'react';
12
+ import { components, InputProps } from 'react-select';
13
+ import { TagType } from './types';
14
+ import { StyledValueButton } from './ValueButton';
15
+
16
+ const inputStyle = css`
17
+ && {
18
+ padding: 0 ${spacing.small};
19
+ color: ${colors.brand.primary};
20
+ margin: 0;
21
+ ${StyledValueButton} + & {
22
+ padding-left: ${spacing.xxsmall};
23
+ }
24
+ }
25
+ `;
26
+
27
+ const Input = (props: InputProps<TagType, true>) => {
28
+ return <components.Input css={inputStyle} {...props} />;
29
+ };
30
+
31
+ export default Input;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import css from '@emotion/css';
10
+ import { colors } from '@ndla/core';
11
+ import React from 'react';
12
+ import { MenuProps, components } from 'react-select';
13
+ import { StyledMenuList } from './MenuList';
14
+ import { TagType } from './types';
15
+
16
+ const menuStyle = css`
17
+ display: flex;
18
+ position: relative;
19
+ flex-direction: column;
20
+ margin: 0;
21
+ overflow: hidden;
22
+ border-top: 1px solid ${colors.brand.tertiary};
23
+ min-height: 70px;
24
+
25
+ :has(${StyledMenuList}>*:only-child) {
26
+ min-height: 40px;
27
+ }
28
+ `;
29
+
30
+ const Menu = ({ children, ...props }: MenuProps<TagType, true>) => {
31
+ return (
32
+ <components.Menu css={menuStyle} {...props}>
33
+ {children}
34
+ </components.Menu>
35
+ );
36
+ };
37
+
38
+ export default Menu;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import styled from '@emotion/styled';
10
+ import { utils } from '@ndla/core';
11
+ import React from 'react';
12
+ import { MenuListProps } from 'react-select';
13
+ import { TagType } from './types';
14
+
15
+ export const StyledMenuList = styled.div`
16
+ overflow: overlay;
17
+ ${utils.scrollbar}
18
+ display: flex;
19
+ flex-direction: column;
20
+ `;
21
+
22
+ const MenuList = ({ innerProps, innerRef, children }: MenuListProps<TagType, true>) => {
23
+ return (
24
+ <StyledMenuList ref={innerRef} {...innerProps}>
25
+ {children}
26
+ </StyledMenuList>
27
+ );
28
+ };
29
+
30
+ export default MenuList;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import React from 'react';
10
+ import { OptionProps } from 'react-select';
11
+ import { buttonStyleV2 as buttonStyle } from '@ndla/button';
12
+ import styled from '@emotion/styled';
13
+ import { colors, fonts } from '@ndla/core';
14
+ import { Done } from '@ndla/icons/editor';
15
+ import { TagType } from './types';
16
+
17
+ interface StyledProps {
18
+ selected: boolean;
19
+ focused: boolean;
20
+ }
21
+
22
+ const StyledMenuOption = styled.div<StyledProps>`
23
+ && {
24
+ background: ${({ focused }) => focused && colors.brand.lighter};
25
+ color: ${({ focused }) => focused && colors.brand.primary};
26
+ justify-content: space-between;
27
+ ${fonts.sizes(16)};
28
+ font-weight: ${fonts.weight.normal};
29
+ color: ${({ selected }) => selected && colors.brand.grey};
30
+ }
31
+ `;
32
+
33
+ const StyledCheck = styled(Done)`
34
+ width: 24px;
35
+ height: 24px;
36
+ fill: ${colors.brand.tertiary};
37
+ `;
38
+
39
+ const Option = ({ innerProps, innerRef, children, isSelected, isFocused }: OptionProps<TagType, true>) => {
40
+ return (
41
+ <StyledMenuOption
42
+ focused={isFocused}
43
+ selected={isSelected}
44
+ css={buttonStyle({ colorTheme: 'lighter', variant: 'ghost', shape: 'sharp' })}
45
+ ref={innerRef}
46
+ {...innerProps}>
47
+ {children}
48
+ {isSelected && <StyledCheck />}
49
+ </StyledMenuOption>
50
+ );
51
+ };
52
+
53
+ export default Option;