@ndla/ui 30.1.0 → 30.2.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.
@@ -16,39 +16,72 @@ import { useTranslation } from 'react-i18next';
16
16
  import SafeLink from '@ndla/safelink';
17
17
  import { MenuButton, MenuItemProps } from '@ndla/button';
18
18
 
19
- interface FolderIconWrapperProps {
20
- type?: LayoutType;
19
+ type LayoutType = 'list' | 'listLarger' | 'block';
20
+ interface LayoutProps {
21
+ type: LayoutType;
21
22
  }
22
23
 
23
- const FolderIconWrapper = styled.div<FolderIconWrapperProps>`
24
+ const FolderWrapper = styled.div<LayoutProps>`
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ padding: ${spacing.nsmall};
29
+ gap: ${spacing.small};
30
+
31
+ ${mq.range({ until: breakpoints.mobileWide })} {
32
+ ${({ type }) =>
33
+ type !== 'list' &&
34
+ css`
35
+ flex-direction: column;
36
+ align-items: unset;
37
+ `}
38
+ }
39
+
40
+ ${({ type }) =>
41
+ type === 'block' &&
42
+ css`
43
+ flex-direction: column;
44
+ align-items: unset;
45
+ `}
46
+
47
+ border: 1px solid ${colors.brand.neutral7};
48
+ cursor: pointer;
49
+ border-radius: 2px;
50
+ box-shadow: none;
51
+ text-decoration: none;
52
+ &:hover {
53
+ box-shadow: 1px 1px 6px 2px rgba(9, 55, 101, 0.08);
54
+ transition-duration: 0.2s;
55
+ }
56
+ `;
57
+
58
+ const TitleWrapper = styled.div`
59
+ display: flex;
60
+ flex-direction: row;
61
+ align-items: center;
62
+ gap: ${spacing.xsmall};
63
+ justify-content: space-between;
64
+ `;
65
+
66
+ const IconWrapper = styled.div`
24
67
  display: flex;
25
68
  border-radius: 100%;
26
- padding: ${spacing.small};
27
- background-color: ${colors.brand.greyLighter};
69
+ color: ${colors.brand.primary};
28
70
  svg {
29
- width: 18px;
30
- height: 18px;
71
+ width: 20px;
72
+ height: 20px;
31
73
  }
32
- ${(p) =>
33
- p.type === 'block' &&
34
- css`
35
- background-color: transparent;
36
- ${FolderWrapper}:hover & {
37
- background-color: ${colors.brand.light};
38
- transition-duration 0.5s;
39
- }
40
- `};
41
74
  `;
42
75
 
43
- const FolderTitleLink = styled(SafeLink)`
76
+ const StyledLink = styled(SafeLink)`
44
77
  box-shadow: none;
45
- color: ${colors.text.primary};
78
+ color: ${colors.brand.primary};
46
79
  flex: 1;
47
80
  `;
48
81
 
49
82
  const FolderTitle = styled.h2`
50
- ${fonts.sizes(18)};
51
- font-weight: ${fonts.weight.normal};
83
+ ${fonts.sizes('16px', '20px')};
84
+ font-weight: ${fonts.weight.semibold};
52
85
  margin: 0;
53
86
  flex: 1;
54
87
 
@@ -59,41 +92,50 @@ const FolderTitle = styled.h2`
59
92
  -webkit-line-clamp: 1;
60
93
  line-clamp: 1;
61
94
  -webkit-box-orient: vertical;
95
+
96
+ ${FolderWrapper}:hover & {
97
+ color: ${colors.brand.primary};
98
+ text-decoration: underline;
99
+ }
62
100
  `;
63
101
 
64
- const FolderWrapper = styled.div`
102
+ const MenuWrapper = styled.div`
103
+ overflow: hidden;
65
104
  display: flex;
105
+ flex-direction: row;
66
106
  align-items: center;
67
- padding: ${spacing.small};
68
- border: 1px solid ${colors.brand.neutral7};
69
- cursor: pointer;
70
- border-radius: 2px;
71
- box-shadow: none;
72
- text-decoration: none;
73
- color: ${colors.brand.greyDark};
74
- font-family: ${fonts.sans};
75
- transition-duration: 0.2s;
107
+ gap: ${spacing.xsmall};
108
+ justify-content: space-between;
109
+ margin: -${spacing.nsmall};
110
+ margin-left: 0;
111
+ `;
112
+
113
+ const CountContainer = styled.div`
114
+ overflow: hidden;
115
+ display: flex;
116
+ flex-direction: row;
76
117
  gap: ${spacing.small};
77
- &:hover {
78
- box-shadow: 1px 1px 6px 2px rgba(9, 55, 101, 0.08);
79
- transition-duration: 0.2s;
80
- ${FolderTitle} {
81
- color: ${colors.brand.primary};
82
- text-decoration: underline;
83
- }
84
- }
85
118
  `;
86
119
 
87
- interface Props {
88
- id: string;
89
- title: string;
90
- subFolders?: number;
91
- subResources?: number;
92
- description?: string;
93
- link: string;
94
- type: LayoutType;
95
- menuItems?: MenuItemProps[];
96
- }
120
+ const IconCountWrapper = styled.div<LayoutProps>`
121
+ display: flex;
122
+ align-items: center;
123
+ gap: ${spacing.xxsmall};
124
+ color: ${colors.brand.grey};
125
+ white-space: nowrap;
126
+ svg {
127
+ width: 13px;
128
+ height: 13px;
129
+ }
130
+ ${fonts.sizes(16)};
131
+ ${mq.range({ until: breakpoints.mobileWide })} {
132
+ ${({ type }) =>
133
+ type === 'list' &&
134
+ css`
135
+ display: none;
136
+ `}
137
+ }
138
+ `;
97
139
 
98
140
  interface IconCountProps {
99
141
  type: 'resource' | 'folder';
@@ -101,30 +143,7 @@ interface IconCountProps {
101
143
  layoutType: LayoutType;
102
144
  }
103
145
 
104
- interface IconCountWrapperProps {
105
- type: LayoutType;
106
- }
107
-
108
- const IconCountWrapper = styled.div<IconCountWrapperProps>`
109
- display: flex;
110
- align-items: center;
111
- gap: 2px;
112
- ${fonts.sizes(16)};
113
- ${(p) =>
114
- p.type === 'block' &&
115
- css`
116
- opacity: 0;
117
- ${FolderWrapper}:hover & {
118
- opacity: 1;
119
- }
120
- `};
121
-
122
- ${mq.range({ until: breakpoints.tabletWide })} {
123
- display: none;
124
- }
125
- `;
126
-
127
- const IconCount = ({ type, count, layoutType }: IconCountProps) => {
146
+ const Count = ({ type, count, layoutType }: IconCountProps) => {
128
147
  const Icon = type === 'resource' ? FileDocumentOutline : FolderOutlined;
129
148
  const { t } = useTranslation();
130
149
  if (!count) return null;
@@ -132,12 +151,21 @@ const IconCount = ({ type, count, layoutType }: IconCountProps) => {
132
151
  return (
133
152
  <IconCountWrapper type={layoutType}>
134
153
  <Icon aria-label={t(`myNdla.${type}s`)} />
135
- <span>{layoutType === 'block' ? count : t(`myNdla.${type}s`, { count })}</span>
154
+ <span>{t(`myNdla.${type}s`, { count })}</span>
136
155
  </IconCountWrapper>
137
156
  );
138
157
  };
139
158
 
140
- type LayoutType = 'list' | 'block';
159
+ interface Props {
160
+ id: string;
161
+ title: string;
162
+ subFolders?: number;
163
+ subResources?: number;
164
+ description?: string;
165
+ link: string;
166
+ type: LayoutType;
167
+ menuItems?: MenuItemProps[];
168
+ }
141
169
 
142
170
  const Folder = ({ id, link, title, subFolders, subResources, type = 'list', menuItems }: Props) => {
143
171
  const { t } = useTranslation();
@@ -148,16 +176,22 @@ const Folder = ({ id, link, title, subFolders, subResources, type = 'list', menu
148
176
  };
149
177
 
150
178
  return (
151
- <FolderWrapper onClick={onClick} id={id}>
152
- <FolderIconWrapper type={type}>
153
- <FolderOutlined aria-label={t('myNdla.folder.folder')} />
154
- </FolderIconWrapper>
155
- <FolderTitleLink to={link} ref={linkRef}>
156
- <FolderTitle>{title}</FolderTitle>
157
- </FolderTitleLink>
158
- <IconCount layoutType={type} type={'folder'} count={subFolders} />
159
- <IconCount layoutType={type} type={'resource'} count={subResources} />
160
- {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
179
+ <FolderWrapper type={type} onClick={onClick} id={id}>
180
+ <TitleWrapper>
181
+ <IconWrapper>
182
+ <FolderOutlined aria-label={t('myNdla.folder.folder')} />
183
+ </IconWrapper>
184
+ <StyledLink to={link} ref={linkRef}>
185
+ <FolderTitle>{title}</FolderTitle>
186
+ </StyledLink>
187
+ </TitleWrapper>
188
+ <MenuWrapper>
189
+ <CountContainer>
190
+ <Count layoutType={type} type={'folder'} count={subFolders} />
191
+ <Count layoutType={type} type={'resource'} count={subResources} />
192
+ </CountContainer>
193
+ {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
194
+ </MenuWrapper>
161
195
  </FolderWrapper>
162
196
  );
163
197
  };
@@ -25,27 +25,12 @@ import {
25
25
  import ContentLoader from '../ContentLoader';
26
26
  import { contentTypeMapping } from '../model/ContentType';
27
27
 
28
- interface BlockResourceProps {
29
- id: string;
30
- link: string;
31
- tagLinkPrefix?: string;
32
- title: string;
33
- resourceImage: ResourceImageProps;
34
- tags?: string[];
35
- description?: string;
36
- headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
37
- menuItems?: MenuItemProps[];
38
- isLoading?: boolean;
39
- targetBlank?: boolean;
40
- resourceTypes?: { id: string; name: string }[];
41
- }
42
-
43
28
  const BlockElementWrapper = styled.div`
44
29
  display: flex;
45
30
  text-decoration: none;
46
31
  box-shadow: none;
47
32
  flex-direction: column;
48
- max-width: 300px;
33
+ max-width: 450px;
49
34
  max-height: 240px;
50
35
  border: 1px solid ${colors.brand.light};
51
36
  border-radius: 2px;
@@ -83,14 +68,18 @@ const BlockDescription = styled.p`
83
68
 
84
69
  const RightRow = styled(Row)`
85
70
  justify-content: flex-end;
86
- margin-bottom: -${spacing.xxsmall};
71
+ margin: 0 -${spacing.small} -${spacing.small} 0;
87
72
  `;
88
73
 
89
74
  const BlockInfoWrapper = styled.div`
90
75
  display: flex;
91
76
  flex-direction: column;
92
77
  padding: ${spacing.small};
93
- gap: ${spacing.xxsmall};
78
+ `;
79
+
80
+ const ContentWrapper = styled.div`
81
+ display: flex;
82
+ flex-direction: column;
94
83
  `;
95
84
 
96
85
  const ImageWrapper = styled.div`
@@ -99,11 +88,11 @@ const ImageWrapper = styled.div`
99
88
  justify-content: center;
100
89
  overflow: hidden;
101
90
  align-items: center;
102
- aspect-ratio: 3/4;
103
91
  img {
104
92
  min-width: 100%;
105
93
  }
106
94
  `;
95
+
107
96
  interface BlockImageProps {
108
97
  image: ResourceImageProps;
109
98
  loading?: boolean;
@@ -142,6 +131,21 @@ const ResourceTypeAndTitleLoader = ({ children, loading }: LoaderProps) => {
142
131
  return <>{children}</>;
143
132
  };
144
133
 
134
+ interface Props {
135
+ id: string;
136
+ link: string;
137
+ tagLinkPrefix?: string;
138
+ title: string;
139
+ resourceImage: ResourceImageProps;
140
+ tags?: string[];
141
+ description?: string;
142
+ headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
143
+ menuItems?: MenuItemProps[];
144
+ isLoading?: boolean;
145
+ targetBlank?: boolean;
146
+ resourceTypes?: { id: string; name: string }[];
147
+ }
148
+
145
149
  const BlockResource = ({
146
150
  id,
147
151
  link,
@@ -155,7 +159,7 @@ const BlockResource = ({
155
159
  headingLevel = 'h2',
156
160
  targetBlank,
157
161
  resourceTypes,
158
- }: BlockResourceProps) => {
162
+ }: Props) => {
159
163
  const linkRef = useRef<HTMLAnchorElement>(null);
160
164
  const firstResourceType = resourceTypes?.[0].id ?? '';
161
165
  const Title = ResourceTitle.withComponent(headingLevel);
@@ -176,15 +180,17 @@ const BlockResource = ({
176
180
  />
177
181
  </ImageWrapper>
178
182
  <BlockInfoWrapper>
179
- <ResourceTypeAndTitleLoader loading={isLoading}>
180
- <ResourceTitleLink title={title} target={targetBlank ? '_blank' : undefined} to={link} ref={linkRef}>
181
- <Title>{title}</Title>
182
- </ResourceTitleLink>
183
- </ResourceTypeAndTitleLoader>
184
- <ResourceTypeList resourceTypes={resourceTypes} />
185
- <BlockDescription>{description}</BlockDescription>
183
+ <ContentWrapper>
184
+ <ResourceTypeAndTitleLoader loading={isLoading}>
185
+ <ResourceTitleLink title={title} target={targetBlank ? '_blank' : undefined} to={link} ref={linkRef}>
186
+ <Title>{title}</Title>
187
+ </ResourceTitleLink>
188
+ </ResourceTypeAndTitleLoader>
189
+ <ResourceTypeList resourceTypes={resourceTypes} />
190
+ <BlockDescription>{description}</BlockDescription>
191
+ </ContentWrapper>
186
192
  <RightRow>
187
- {tags && <CompressedTagList tagLinkPrefix={tagLinkPrefix} tags={tags} />}
193
+ {tags && tags.length > 0 && <CompressedTagList tagLinkPrefix={tagLinkPrefix} tags={tags} />}
188
194
  {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
189
195
  </RightRow>
190
196
  </BlockInfoWrapper>
@@ -15,7 +15,7 @@ import {
15
15
  CompressedTagList,
16
16
  ResourceImageProps,
17
17
  ResourceTitle,
18
- ResourceTitleLink,
18
+ ResourceTitleLink as StyledLink,
19
19
  ResourceTypeList,
20
20
  StyledContentIconWrapper,
21
21
  LoaderProps,
@@ -24,30 +24,13 @@ import ContentLoader from '../ContentLoader';
24
24
  import ContentTypeBadge from '../ContentTypeBadge';
25
25
  import { contentTypeMapping } from '../model/ContentType';
26
26
 
27
- const StyledResourceDescription = styled.p`
28
- grid-area: description;
29
- line-clamp: 2;
30
- line-height: 1em;
31
- height: 3.1em;
32
- margin: 0;
33
- overflow: hidden;
34
- ${fonts.sizes(16)};
35
- text-overflow: ellipsis;
36
- // Unfortunate css needed for multi-line text overflow ellipsis.
37
- display: -webkit-box;
38
- -webkit-line-clamp: 2;
39
- line-clamp: 2;
40
- -webkit-box-orient: vertical;
41
- `;
42
-
43
- const ResourceWrapper = styled.div`
27
+ const ListResourceWrapper = styled.div`
44
28
  flex: 1;
45
29
  display: grid;
46
30
  grid-template-columns: auto 1fr auto;
47
31
  grid-template-areas:
48
32
  'image topicAndTitle tags'
49
33
  'image description description';
50
-
51
34
  ${mq.range({ until: breakpoints.mobileWide })} {
52
35
  grid-template-columns: auto 1fr;
53
36
  grid-template-areas:
@@ -56,46 +39,31 @@ const ResourceWrapper = styled.div`
56
39
  'tags tags';
57
40
  }
58
41
 
59
- cursor: pointer;
60
42
  padding: ${spacing.small};
43
+ column-gap: ${spacing.small};
44
+ cursor: pointer;
61
45
  border: 1px solid ${colors.brand.neutral7};
62
46
  border-radius: 2px;
63
- gap: 0 ${spacing.small};
64
47
 
65
48
  &:hover {
66
49
  box-shadow: 1px 1px 6px 2px rgba(9, 55, 101, 0.08);
67
50
  transition-duration: 0.2s;
68
- ${() => ResourceTitleLink} {
51
+ ${() => StyledLink} {
69
52
  color: ${colors.brand.primary};
70
53
  text-decoration: underline;
71
54
  }
72
55
  }
73
56
  `;
74
57
 
75
- const TagsandActionMenu = styled.div`
76
- box-sizing: content-box;
77
- padding: 2px;
78
- grid-area: tags;
79
- display: flex;
80
- align-items: center;
81
- width: 100%;
82
- overflow: hidden;
83
- align-self: flex-start;
84
- justify-self: flex-end;
85
- justify-content: flex-end;
86
-
87
- ${mq.range({ from: breakpoints.mobileWide })} {
88
- margin-top: -${spacing.xsmall};
89
- margin-right: -${spacing.xxsmall};
90
- }
91
- `;
58
+ interface StyledImageProps {
59
+ imageSize: 'normal' | 'compact';
60
+ }
92
61
 
93
- const StyledImageWrapper = styled.div<StyledImageProps>`
62
+ const ImageWrapper = styled.div<StyledImageProps>`
94
63
  grid-area: image;
95
64
  width: ${(p) => (p.imageSize === 'normal' ? '136px' : '56px')};
96
65
  ${mq.range({ until: breakpoints.mobileWide })} {
97
- width: 54px;
98
- height: 40px;
66
+ width: 56px;
99
67
  }
100
68
  overflow: hidden;
101
69
  border-radius: 2px;
@@ -110,29 +78,48 @@ const StyledImage = styled(Image)`
110
78
  aspect-ratio: 4/3;
111
79
  `;
112
80
 
81
+ const StyledResourceDescription = styled.p`
82
+ grid-area: description;
83
+ line-clamp: 2;
84
+ line-height: 1em;
85
+ height: 3.1em;
86
+ margin: 0;
87
+ margin-top: ${spacing.xxsmall};
88
+ overflow: hidden;
89
+ ${fonts.sizes(16)};
90
+ text-overflow: ellipsis;
91
+ // Unfortunate css needed for multi-line text overflow ellipsis.
92
+ display: -webkit-box;
93
+ -webkit-line-clamp: 2;
94
+ line-clamp: 2;
95
+ -webkit-box-orient: vertical;
96
+ `;
97
+
98
+ const TagsandActionMenu = styled.div`
99
+ box-sizing: content-box;
100
+ grid-area: tags;
101
+ display: flex;
102
+ align-items: center;
103
+ width: 100%;
104
+ overflow: hidden;
105
+ align-self: flex-start;
106
+ justify-self: flex-end;
107
+ justify-content: flex-end;
108
+ margin: -${spacing.small} -${spacing.small} 0 0;
109
+ ${mq.range({ until: breakpoints.mobileWide })} {
110
+ margin: 0 -${spacing.small} -${spacing.small} 0;
111
+ }
112
+ `;
113
+
113
114
  const TopicAndTitleWrapper = styled.div`
114
115
  grid-area: topicAndTitle;
115
- margin-top: 2px;
116
116
  `;
117
117
 
118
- interface StyledImageProps {
119
- imageSize: 'normal' | 'compact';
120
- }
121
-
122
- export interface ListResourceProps {
123
- id: string;
124
- link: string;
125
- tagLinkPrefix?: string;
126
- title: string;
127
- resourceImage: ResourceImageProps;
128
- headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
129
- resourceTypes: { id: string; name: string }[];
130
- tags?: string[];
131
- description?: string;
132
- menuItems?: MenuItemProps[];
133
- isLoading?: boolean;
134
- targetBlank?: boolean;
135
- }
118
+ const StyledMenuButton = styled(MenuButton)`
119
+ :only-child {
120
+ margin-left: ${spacing.small};
121
+ }
122
+ `;
136
123
 
137
124
  interface ListResourceImageProps {
138
125
  resourceImage: ResourceImageProps;
@@ -170,7 +157,7 @@ const ListResourceImage = ({ resourceImage, loading, type, contentType }: ListRe
170
157
  );
171
158
  };
172
159
 
173
- const ResourceTypeAndTitleLoader = ({ loading, children }: LoaderProps) => {
160
+ const TypeAndTitleLoader = ({ loading, children }: LoaderProps) => {
174
161
  if (loading) {
175
162
  return (
176
163
  <ContentLoader height={'40px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
@@ -188,7 +175,7 @@ interface ResourceDescriptionProps {
188
175
  loading?: boolean;
189
176
  }
190
177
 
191
- const ResourceDescription = ({ description, loading }: ResourceDescriptionProps) => {
178
+ const Description = ({ description, loading }: ResourceDescriptionProps) => {
192
179
  if (loading) {
193
180
  return (
194
181
  <ContentLoader height={'20px'} width={'100%'} viewBox={null} preserveAspectRatio="none">
@@ -199,6 +186,21 @@ const ResourceDescription = ({ description, loading }: ResourceDescriptionProps)
199
186
  return <StyledResourceDescription>{description}</StyledResourceDescription>;
200
187
  };
201
188
 
189
+ export interface ListResourceProps {
190
+ id: string;
191
+ link: string;
192
+ tagLinkPrefix?: string;
193
+ title: string;
194
+ resourceImage: ResourceImageProps;
195
+ headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
196
+ resourceTypes: { id: string; name: string }[];
197
+ tags?: string[];
198
+ description?: string;
199
+ menuItems?: MenuItemProps[];
200
+ isLoading?: boolean;
201
+ targetBlank?: boolean;
202
+ }
203
+
202
204
  const ListResource = ({
203
205
  id,
204
206
  link,
@@ -225,29 +227,29 @@ const ListResource = ({
225
227
  };
226
228
 
227
229
  return (
228
- <ResourceWrapper onClick={handleClick} id={id}>
229
- <StyledImageWrapper imageSize={imageType}>
230
+ <ListResourceWrapper onClick={handleClick} id={id}>
231
+ <ImageWrapper imageSize={imageType}>
230
232
  <ListResourceImage
231
233
  resourceImage={resourceImage}
232
234
  loading={isLoading}
233
235
  type={imageType}
234
236
  contentType={contentTypeMapping[firstContentType] ?? contentTypeMapping['default']}
235
237
  />
236
- </StyledImageWrapper>
238
+ </ImageWrapper>
237
239
  <TopicAndTitleWrapper>
238
- <ResourceTypeAndTitleLoader loading={isLoading}>
239
- <ResourceTitleLink to={link} target={targetBlank ? '_blank' : undefined} ref={linkRef}>
240
+ <TypeAndTitleLoader loading={isLoading}>
241
+ <StyledLink to={link} target={targetBlank ? '_blank' : undefined} ref={linkRef}>
240
242
  <Title>{title}</Title>
241
- </ResourceTitleLink>
243
+ </StyledLink>
242
244
  <ResourceTypeList resourceTypes={resourceTypes} />
243
- </ResourceTypeAndTitleLoader>
245
+ </TypeAndTitleLoader>
244
246
  </TopicAndTitleWrapper>
245
- {showDescription && <ResourceDescription description={description} loading={isLoading} />}
247
+ {showDescription && <Description description={description} loading={isLoading} />}
246
248
  <TagsandActionMenu>
247
- {tags && <CompressedTagList tagLinkPrefix={tagLinkPrefix} tags={tags} />}
248
- {menuItems && menuItems.length > 0 && <MenuButton alignRight size="small" menuItems={menuItems} />}
249
+ {tags && tags.length > 0 && <CompressedTagList tagLinkPrefix={tagLinkPrefix} tags={tags} />}
250
+ {menuItems && menuItems.length > 0 && <StyledMenuButton alignRight size="small" menuItems={menuItems} />}
249
251
  </TagsandActionMenu>
250
- </ResourceWrapper>
252
+ </ListResourceWrapper>
251
253
  );
252
254
  };
253
255