@strapi/admin 4.5.0 → 4.5.2

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 (157) hide show
  1. package/admin/src/components/LeftMenu/index.js +22 -4
  2. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +4 -3
  3. package/admin/src/content-manager/components/DynamicZone/components/{AddComponentButton/index.js → AddComponentButton.js} +12 -6
  4. package/admin/src/content-manager/components/DynamicZone/components/{ComponentPicker/Category/ComponentCard/index.js → ComponentCard.js} +8 -19
  5. package/admin/src/content-manager/components/DynamicZone/components/{ComponentPicker/Category/index.js → ComponentCategory.js} +19 -18
  6. package/admin/src/content-manager/components/DynamicZone/components/{ComponentPicker/index.js → ComponentPicker.js} +36 -38
  7. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +195 -0
  8. package/admin/src/content-manager/components/DynamicZone/components/{DzLabel/index.js → DynamicZoneLabel.js} +13 -5
  9. package/admin/src/content-manager/components/DynamicZone/index.js +33 -118
  10. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +79 -76
  11. package/admin/src/pages/HomePage/index.js +1 -1
  12. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/FilterSelect.js +2 -1
  13. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/index.js +1 -0
  14. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +2 -2
  15. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +1 -1
  16. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +1 -1
  17. package/admin/src/translations/nl.json +210 -23
  18. package/admin/src/translations/pt-BR.json +14 -14
  19. package/admin/src/translations/pt.json +33 -32
  20. package/admin/src/translations/sv.json +117 -85
  21. package/admin/src/translations/zh.json +280 -95
  22. package/build/{7692.a36fb2c2.chunk.js → 1233.bddaaa76.chunk.js} +144 -445
  23. package/build/1920.208c77f8.chunk.js +245 -0
  24. package/build/2438.afe24949.chunk.js +2525 -0
  25. package/build/2517.5cc235ba.chunk.js +117 -0
  26. package/build/4306.e62b841c.chunk.js +98 -0
  27. package/build/4318.7931eee7.chunk.js +30 -0
  28. package/build/4986.3820d11d.chunk.js +145 -0
  29. package/build/{8469.41c8d25f.chunk.js → 5015.f080b64e.chunk.js} +6 -1
  30. package/build/504.9aeff724.chunk.js +758 -0
  31. package/build/805.a1894307.chunk.js +138 -0
  32. package/build/8633.8da5488a.chunk.js +1 -0
  33. package/build/9707.932a3c12.chunk.js +70 -0
  34. package/build/Admin-authenticatedApp.7484bdf1.chunk.js +80 -0
  35. package/build/Admin_InternalErrorPage.e0317a5e.chunk.js +1 -0
  36. package/build/Admin_homePage.54e33c2d.chunk.js +77 -0
  37. package/build/Admin_marketplace.ff012eb2.chunk.js +26 -0
  38. package/build/Admin_pluginsPage.3c872de7.chunk.js +6 -0
  39. package/build/Admin_profilePage.e9fcce92.chunk.js +15 -0
  40. package/build/Admin_settingsPage.a1a5218b.chunk.js +178 -0
  41. package/build/admin-app.9cb0abc7.chunk.js +112 -0
  42. package/build/admin-edit-roles-page.23f15909.chunk.js +1 -0
  43. package/build/admin-edit-users.283b49ed.chunk.js +10 -0
  44. package/build/admin-users.a0748674.chunk.js +11 -0
  45. package/build/api-tokens-list-page.700e575f.chunk.js +16 -0
  46. package/build/content-manager.577fddcb.chunk.js +1200 -0
  47. package/build/content-type-builder-list-view.95012cf0.chunk.js +201 -0
  48. package/build/content-type-builder-translation-pt-BR-json.6fe3b8d1.chunk.js +1 -0
  49. package/build/content-type-builder-translation-pt-json.96a31576.chunk.js +1 -0
  50. package/build/content-type-builder-translation-zh-json.3b0afd31.chunk.js +1 -0
  51. package/build/content-type-builder.95b9d6a2.chunk.js +145 -0
  52. package/build/email-settings-page.4bb3606f.chunk.js +15 -0
  53. package/build/email-translation-en-json.ebad8943.chunk.js +1 -0
  54. package/build/email-translation-pt-json.159505ab.chunk.js +1 -0
  55. package/build/email-translation-zh-json.62b1c6fe.chunk.js +1 -0
  56. package/build/i18n-settings-page.195d42fe.chunk.js +1 -0
  57. package/build/i18n-translation-zh-json.eeebb849.chunk.js +1 -0
  58. package/build/index.html +1 -1
  59. package/build/main.23670c4c.js +2026 -0
  60. package/build/nl-json.26f39180.chunk.js +1 -0
  61. package/build/pt-BR-json.2b72b1d6.chunk.js +1 -0
  62. package/build/pt-json.cd67ba86.chunk.js +1 -0
  63. package/build/runtime~main.4dc8c7c6.js +2 -0
  64. package/build/sso-settings-page.9f091262.chunk.js +1 -0
  65. package/build/sv-json.fb1081ff.chunk.js +1 -0
  66. package/build/upload-settings.3010911f.chunk.js +18 -0
  67. package/build/upload-translation-pt-json.5c452b48.chunk.js +1 -0
  68. package/build/upload-translation-zh-json.ac5711de.chunk.js +1 -0
  69. package/build/upload.9f19f2e8.chunk.js +64 -0
  70. package/build/users-advanced-settings-page.9df41d67.chunk.js +13 -0
  71. package/build/users-email-settings-page.56d82eaf.chunk.js +28 -0
  72. package/build/users-permissions-translation-sv-json.d5d11648.chunk.js +1 -0
  73. package/build/users-permissions-translation-zh-json.92f406f9.chunk.js +1 -0
  74. package/build/users-providers-settings-page.96bb7da0.chunk.js +33 -0
  75. package/build/users-roles-settings-page.445e5e16.chunk.js +30 -0
  76. package/build/webhook-edit-page.3285abc4.chunk.js +75 -0
  77. package/build/webhook-list-page.b87821f2.chunk.js +42 -0
  78. package/build/zh-json.2ecc6b99.chunk.js +1 -0
  79. package/jest.config.front.js +0 -1
  80. package/package.json +17 -16
  81. package/server/content-types/api-token.js +1 -1
  82. package/webpack.alias.js +3 -2
  83. package/webpack.config.js +13 -1
  84. package/admin/src/content-manager/components/DynamicZone/components/Component/Rectangle.js +0 -19
  85. package/admin/src/content-manager/components/DynamicZone/components/Component/index.js +0 -191
  86. package/admin/src/content-manager/icons/Bold/index.js +0 -22
  87. package/admin/src/content-manager/icons/Code/index.js +0 -13
  88. package/admin/src/content-manager/icons/Cross/index.js +0 -28
  89. package/admin/src/content-manager/icons/Italic/index.js +0 -23
  90. package/admin/src/content-manager/icons/Link/index.js +0 -21
  91. package/admin/src/content-manager/icons/Media/index.js +0 -14
  92. package/admin/src/content-manager/icons/Na/index.js +0 -39
  93. package/admin/src/content-manager/icons/Ol/index.js +0 -13
  94. package/admin/src/content-manager/icons/Quote/index.js +0 -13
  95. package/admin/src/content-manager/icons/Striked/index.js +0 -24
  96. package/admin/src/content-manager/icons/Ul/index.js +0 -15
  97. package/admin/src/content-manager/icons/Underline/index.js +0 -22
  98. package/build/1856.172d5fa0.chunk.js +0 -174
  99. package/build/2077.058590f4.chunk.js +0 -206
  100. package/build/2912.2c42c07b.chunk.js +0 -259
  101. package/build/4318.5e670740.chunk.js +0 -30
  102. package/build/4715.22747b59.chunk.js +0 -387
  103. package/build/4800.a6935af6.chunk.js +0 -1
  104. package/build/4982.1b75ddb1.chunk.js +0 -325
  105. package/build/6925.f5c8b6fc.chunk.js +0 -761
  106. package/build/7379.d246dd38.chunk.js +0 -1
  107. package/build/7841.c50e9509.chunk.js +0 -259
  108. package/build/7866.ba215f99.chunk.js +0 -505
  109. package/build/8380.e53e7207.chunk.js +0 -299
  110. package/build/8549.832ed79d.chunk.js +0 -159
  111. package/build/8738.0fe8a61e.chunk.js +0 -463
  112. package/build/9066.eaf76ff3.chunk.js +0 -101
  113. package/build/9166.90876521.chunk.js +0 -342
  114. package/build/9420.5292d1d2.chunk.js +0 -505
  115. package/build/9649.468667d9.chunk.js +0 -199
  116. package/build/Admin-authenticatedApp.c4f68103.chunk.js +0 -80
  117. package/build/Admin_InternalErrorPage.12e24216.chunk.js +0 -1
  118. package/build/Admin_homePage.26d32e30.chunk.js +0 -72
  119. package/build/Admin_marketplace.32375885.chunk.js +0 -22
  120. package/build/Admin_pluginsPage.4d59785a.chunk.js +0 -1
  121. package/build/Admin_profilePage.da32abbc.chunk.js +0 -15
  122. package/build/Admin_settingsPage.bf2234e1.chunk.js +0 -178
  123. package/build/admin-app.9049056c.chunk.js +0 -112
  124. package/build/admin-edit-roles-page.69d9fcb2.chunk.js +0 -1
  125. package/build/admin-edit-users.c585212f.chunk.js +0 -10
  126. package/build/admin-users.d71f198a.chunk.js +0 -11
  127. package/build/api-tokens-list-page.bb36535f.chunk.js +0 -16
  128. package/build/content-manager.ff998bed.chunk.js +0 -1204
  129. package/build/content-type-builder-list-view.5b3cd768.chunk.js +0 -194
  130. package/build/content-type-builder-translation-pt-BR-json.d6c7fcc1.chunk.js +0 -1
  131. package/build/content-type-builder-translation-pt-json.766bd747.chunk.js +0 -1
  132. package/build/content-type-builder-translation-zh-json.2cc55621.chunk.js +0 -1
  133. package/build/content-type-builder.16af63a6.chunk.js +0 -145
  134. package/build/email-settings-page.c3469093.chunk.js +0 -103
  135. package/build/email-translation-en-json.3d74ff95.chunk.js +0 -1
  136. package/build/email-translation-pt-json.959ea070.chunk.js +0 -1
  137. package/build/email-translation-zh-json.3455468b.chunk.js +0 -1
  138. package/build/i18n-settings-page.46d894ff.chunk.js +0 -101
  139. package/build/main.91328e7a.js +0 -9381
  140. package/build/nl-json.2b8cc3a0.chunk.js +0 -1
  141. package/build/pt-BR-json.7852f808.chunk.js +0 -1
  142. package/build/pt-json.3161ca22.chunk.js +0 -1
  143. package/build/runtime~main.c9c319c0.js +0 -2
  144. package/build/sso-settings-page.9ceb0140.chunk.js +0 -1
  145. package/build/sv-json.8e5a7911.chunk.js +0 -1
  146. package/build/upload-settings.53b690f3.chunk.js +0 -101
  147. package/build/upload-translation-zh-json.ee8fba96.chunk.js +0 -1
  148. package/build/upload.7084cea6.chunk.js +0 -7
  149. package/build/users-advanced-settings-page.3f4ee86e.chunk.js +0 -101
  150. package/build/users-email-settings-page.73c41236.chunk.js +0 -1
  151. package/build/users-permissions-translation-sv-json.83c60841.chunk.js +0 -1
  152. package/build/users-permissions-translation-zh-json.e03ae2a4.chunk.js +0 -1
  153. package/build/users-providers-settings-page.f8e78537.chunk.js +0 -1
  154. package/build/users-roles-settings-page.b33ec5e5.chunk.js +0 -30
  155. package/build/webhook-edit-page.dc9442ce.chunk.js +0 -23
  156. package/build/webhook-list-page.02191138.chunk.js +0 -134
  157. package/build/zh-json.608aaf24.chunk.js +0 -1
@@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import PropTypes from 'prop-types';
4
4
  import { useIntl } from 'react-intl';
5
- import { NavLink as RouterNavLink } from 'react-router-dom';
5
+ import { NavLink as RouterNavLink, useLocation } from 'react-router-dom';
6
6
  import { Divider } from '@strapi/design-system/Divider';
7
7
  import {
8
8
  MainNav,
@@ -19,7 +19,7 @@ import { Typography } from '@strapi/design-system/Typography';
19
19
  import { Stack } from '@strapi/design-system/Stack';
20
20
  import Write from '@strapi/icons/Write';
21
21
  import Exit from '@strapi/icons/Exit';
22
- import { auth, usePersistentState, useAppInfos } from '@strapi/helper-plugin';
22
+ import { auth, usePersistentState, useAppInfos, useTracking } from '@strapi/helper-plugin';
23
23
  import useConfigurations from '../../hooks/useConfigurations';
24
24
 
25
25
  const LinkUserWrapper = styled(Box)`
@@ -59,6 +59,8 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
59
59
  const [condensed, setCondensed] = usePersistentState('navbar-condensed', false);
60
60
  const { userDisplayName } = useAppInfos();
61
61
  const { formatMessage } = useIntl();
62
+ const { trackUsage } = useTracking();
63
+ const { pathname } = useLocation();
62
64
 
63
65
  const initials = userDisplayName
64
66
  .split(' ')
@@ -82,6 +84,10 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
82
84
  }
83
85
  };
84
86
 
87
+ const handleClickOnLink = (destination = null) => {
88
+ trackUsage('willNavigate', { from: pathname, to: destination });
89
+ };
90
+
85
91
  const menuTitle = formatMessage({
86
92
  id: 'app.components.LeftMenu.navbrand.title',
87
93
  defaultMessage: 'Strapi Dashboard',
@@ -110,7 +116,12 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
110
116
  <Divider />
111
117
 
112
118
  <NavSections>
113
- <NavLink as={RouterNavLink} to="/content-manager" icon={<Write />}>
119
+ <NavLink
120
+ as={RouterNavLink}
121
+ to="/content-manager"
122
+ icon={<Write />}
123
+ onClick={() => handleClickOnLink('/content-manager')}
124
+ >
114
125
  {formatMessage({ id: 'global.content-manager', defaultMessage: 'Content manager' })}
115
126
  </NavLink>
116
127
 
@@ -125,7 +136,13 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
125
136
  const Icon = link.icon;
126
137
 
127
138
  return (
128
- <NavLink as={RouterNavLink} to={link.to} key={link.to} icon={<Icon />}>
139
+ <NavLink
140
+ as={RouterNavLink}
141
+ to={link.to}
142
+ key={link.to}
143
+ icon={<Icon />}
144
+ onClick={() => handleClickOnLink(link.to)}
145
+ >
129
146
  {formatMessage(link.intlLabel)}
130
147
  </NavLink>
131
148
  );
@@ -152,6 +169,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
152
169
  to={link.to}
153
170
  key={link.to}
154
171
  icon={<LinkIcon />}
172
+ onClick={() => handleClickOnLink(link.to)}
155
173
  >
156
174
  {formatMessage(link.intlLabel)}
157
175
  </NavLink>
@@ -5,6 +5,7 @@ import { useIntl } from 'react-intl';
5
5
  import { Typography } from '@strapi/design-system/Typography';
6
6
  import { Box } from '@strapi/design-system/Box';
7
7
  import { Badge } from '@strapi/design-system/Badge';
8
+ import { Flex } from '@strapi/design-system/Flex';
8
9
  import { SimpleMenu, MenuItem } from '@strapi/design-system/SimpleMenu';
9
10
  import { Loader } from '@strapi/design-system/Loader';
10
11
  import styled from 'styled-components';
@@ -38,8 +39,8 @@ const RelationMultiple = ({ fieldSchema, metadatas, name, entityId, value, conte
38
39
  const [isOpen, setIsOpen] = useState(false);
39
40
 
40
41
  const Label = (
41
- <>
42
- <Badge>{value.count}</Badge>{' '}
42
+ <Flex gap={1} wrap="nowrap">
43
+ <Badge>{value.count}</Badge>
43
44
  {formatMessage(
44
45
  {
45
46
  id: 'content-manager.containers.ListPage.items',
@@ -47,7 +48,7 @@ const RelationMultiple = ({ fieldSchema, metadatas, name, entityId, value, conte
47
48
  },
48
49
  { number: value.count }
49
50
  )}
50
- </>
51
+ </Flex>
51
52
  );
52
53
 
53
54
  const notify = () => {
@@ -13,7 +13,8 @@ import { BaseButton } from '@strapi/design-system/BaseButton';
13
13
  import { Box } from '@strapi/design-system/Box';
14
14
  import { Flex } from '@strapi/design-system/Flex';
15
15
  import { Typography } from '@strapi/design-system/Typography';
16
- import { getTrad } from '../../../../utils';
16
+
17
+ import { getTrad } from '../../../utils';
17
18
 
18
19
  const StyledAddIcon = styled(PlusCircle)`
19
20
  transform: ${({ $isOpen }) => ($isOpen ? 'rotate(45deg)' : 'rotate(0deg)')};
@@ -142,17 +143,22 @@ const AddComponentButton = ({
142
143
  };
143
144
 
144
145
  AddComponentButton.defaultProps = {
146
+ hasError: false,
147
+ hasMaxError: false,
148
+ hasMinError: false,
149
+ isDisabled: false,
150
+ isOpen: false,
145
151
  label: '',
146
152
  missingComponentNumber: 0,
147
153
  };
148
154
 
149
155
  AddComponentButton.propTypes = {
150
156
  label: PropTypes.string,
151
- hasError: PropTypes.bool.isRequired,
152
- hasMaxError: PropTypes.bool.isRequired,
153
- hasMinError: PropTypes.bool.isRequired,
154
- isDisabled: PropTypes.bool.isRequired,
155
- isOpen: PropTypes.bool.isRequired,
157
+ hasError: PropTypes.bool,
158
+ hasMaxError: PropTypes.bool,
159
+ hasMinError: PropTypes.bool,
160
+ isDisabled: PropTypes.bool,
161
+ isOpen: PropTypes.bool,
156
162
  missingComponentNumber: PropTypes.number,
157
163
  name: PropTypes.string.isRequired,
158
164
  onClick: PropTypes.func.isRequired,
@@ -6,13 +6,13 @@
6
6
 
7
7
  import React from 'react';
8
8
  import PropTypes from 'prop-types';
9
+ import styled from 'styled-components';
10
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11
+
9
12
  import { Box } from '@strapi/design-system/Box';
10
13
  import { Typography } from '@strapi/design-system/Typography';
11
14
  import { Stack } from '@strapi/design-system/Stack';
12
15
  import { pxToRem } from '@strapi/helper-plugin';
13
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
14
- import styled from 'styled-components';
15
- import { useIntl } from 'react-intl';
16
16
 
17
17
  const StyledFontAwesomeIcon = styled(FontAwesomeIcon)`
18
18
  width: ${pxToRem(32)} !important;
@@ -53,19 +53,14 @@ const ComponentBox = styled(Box)`
53
53
  }
54
54
  `;
55
55
 
56
- function ComponentCard({ componentUid, intlLabel, icon, onClick }) {
57
- const { formatMessage } = useIntl();
58
- const handleClick = () => {
59
- onClick(componentUid);
60
- };
61
-
56
+ export default function ComponentCard({ children, icon, onClick }) {
62
57
  return (
63
- <button type="button" onClick={handleClick}>
58
+ <button type="button" onClick={onClick}>
64
59
  <ComponentBox borderRadius="borderRadius">
65
60
  <Stack spacing={1} style={{ justifyContent: 'center', alignItems: 'center' }}>
66
- <StyledFontAwesomeIcon icon={icon} />
61
+ <StyledFontAwesomeIcon data-testid="component-card-icon" icon={icon} />
67
62
  <Typography variant="pi" fontWeight="bold" textColor="neutral600">
68
- {formatMessage(intlLabel)}
63
+ {children}
69
64
  </Typography>
70
65
  </Stack>
71
66
  </ComponentBox>
@@ -79,13 +74,7 @@ ComponentCard.defaultProps = {
79
74
  };
80
75
 
81
76
  ComponentCard.propTypes = {
82
- componentUid: PropTypes.string.isRequired,
83
- intlLabel: PropTypes.shape({
84
- id: PropTypes.string.isRequired,
85
- defaultMessage: PropTypes.string.isRequired,
86
- }).isRequired,
77
+ children: PropTypes.node.isRequired,
87
78
  icon: PropTypes.string,
88
79
  onClick: PropTypes.func,
89
80
  };
90
-
91
- export default ComponentCard;
@@ -4,6 +4,7 @@ import { Accordion, AccordionToggle, AccordionContent } from '@strapi/design-sys
4
4
  import { Box } from '@strapi/design-system/Box';
5
5
  import styled from 'styled-components';
6
6
  import { useIntl } from 'react-intl';
7
+
7
8
  import ComponentCard from './ComponentCard';
8
9
 
9
10
  const Grid = styled.div`
@@ -12,7 +13,7 @@ const Grid = styled.div`
12
13
  grid-gap: ${({ theme }) => theme.spaces[1]};
13
14
  `;
14
15
 
15
- const Category = ({ category, components, isOdd, isOpen, onAddComponent, onToggle }) => {
16
+ const ComponentCategory = ({ category, components, variant, isOpen, onAddComponent, onToggle }) => {
16
17
  const { formatMessage } = useIntl();
17
18
 
18
19
  const handleToggle = () => {
@@ -22,24 +23,18 @@ const Category = ({ category, components, isOdd, isOpen, onAddComponent, onToggl
22
23
  return (
23
24
  <Accordion expanded={isOpen} onToggle={handleToggle} size="S">
24
25
  <AccordionToggle
25
- variant={isOdd ? 'primary' : 'secondary'}
26
+ variant={variant}
26
27
  title={formatMessage({ id: category, defaultMessage: category })}
27
28
  togglePosition="left"
28
29
  />
29
30
  <AccordionContent>
30
31
  <Box paddingTop={4} paddingBottom={4} paddingLeft={3} paddingRight={3}>
31
32
  <Grid>
32
- {components.map(({ componentUid, info: { displayName, icon } }) => {
33
- return (
34
- <ComponentCard
35
- key={componentUid}
36
- componentUid={componentUid}
37
- intlLabel={{ id: displayName, defaultMessage: displayName }}
38
- icon={icon}
39
- onClick={onAddComponent}
40
- />
41
- );
42
- })}
33
+ {components.map(({ componentUid, info: { displayName, icon } }) => (
34
+ <ComponentCard key={componentUid} icon={icon} onClick={onAddComponent(componentUid)}>
35
+ {formatMessage({ id: displayName, defaultMessage: displayName })}
36
+ </ComponentCard>
37
+ ))}
43
38
  </Grid>
44
39
  </Box>
45
40
  </AccordionContent>
@@ -47,13 +42,19 @@ const Category = ({ category, components, isOdd, isOpen, onAddComponent, onToggl
47
42
  );
48
43
  };
49
44
 
50
- Category.propTypes = {
45
+ ComponentCategory.defaultProps = {
46
+ components: [],
47
+ isOpen: false,
48
+ variant: 'primary',
49
+ };
50
+
51
+ ComponentCategory.propTypes = {
51
52
  category: PropTypes.string.isRequired,
52
- components: PropTypes.array.isRequired,
53
- isOdd: PropTypes.bool.isRequired,
54
- isOpen: PropTypes.bool.isRequired,
53
+ components: PropTypes.array,
54
+ isOpen: PropTypes.bool,
55
55
  onAddComponent: PropTypes.func.isRequired,
56
56
  onToggle: PropTypes.func.isRequired,
57
+ variant: PropTypes.oneOf(['primary', 'secondary']),
57
58
  };
58
59
 
59
- export default Category;
60
+ export default ComponentCategory;
@@ -1,4 +1,4 @@
1
- import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
2
  import groupBy from 'lodash/groupBy';
3
3
  import PropTypes from 'prop-types';
4
4
  import { useIntl } from 'react-intl';
@@ -6,9 +6,11 @@ import { KeyboardNavigable } from '@strapi/design-system/KeyboardNavigable';
6
6
  import { Box } from '@strapi/design-system/Box';
7
7
  import { Flex } from '@strapi/design-system/Flex';
8
8
  import { Typography } from '@strapi/design-system/Typography';
9
- import { getTrad } from '../../../../utils';
10
- import { useContentTypeLayout } from '../../../../hooks';
11
- import Category from './Category';
9
+
10
+ import { getTrad } from '../../../utils';
11
+ import { useContentTypeLayout } from '../../../hooks';
12
+
13
+ import ComponentCategory from './ComponentCategory';
12
14
 
13
15
  const ComponentPicker = ({ components, isOpen, onClickAddComponent }) => {
14
16
  const { formatMessage } = useIntl();
@@ -32,27 +34,22 @@ const ComponentPicker = ({ components, isOpen, onClickAddComponent }) => {
32
34
  }, [components, getComponentLayout]);
33
35
 
34
36
  useEffect(() => {
35
- if (isOpen && dynamicComponentCategories.length) {
37
+ if (isOpen && dynamicComponentCategories.length > 0) {
36
38
  setCategoryToOpen(dynamicComponentCategories[0].category);
37
39
  }
38
40
  }, [isOpen, dynamicComponentCategories]);
39
41
 
40
- const handleAddComponentToDz = useCallback(
41
- (componentUid) => {
42
- onClickAddComponent(componentUid);
43
- setCategoryToOpen('');
44
- },
45
- [onClickAddComponent]
46
- );
47
-
48
- const handleClickToggle = useCallback(
49
- (categoryName) => {
50
- const nextCategoryToOpen = categoryToOpen === categoryName ? '' : categoryName;
42
+ const handleAddComponentToDz = (componentUid) => () => {
43
+ onClickAddComponent(componentUid);
44
+ setCategoryToOpen('');
45
+ };
51
46
 
52
- setCategoryToOpen(nextCategoryToOpen);
53
- },
54
- [categoryToOpen]
55
- );
47
+ /**
48
+ * @type {(categoryName: string) => void}
49
+ */
50
+ const handleClickToggle = (categoryName) => {
51
+ setCategoryToOpen((currentCat) => (currentCat === categoryName ? '' : categoryName));
52
+ };
56
53
 
57
54
  if (!isOpen) {
58
55
  return null;
@@ -80,21 +77,17 @@ const ComponentPicker = ({ components, isOpen, onClickAddComponent }) => {
80
77
  </Flex>
81
78
  <Box paddingTop={2}>
82
79
  <KeyboardNavigable attributeName="data-strapi-accordion-toggle">
83
- {dynamicComponentCategories.map(({ category, components }, index) => {
84
- return (
85
- <Category
86
- key={category}
87
- category={category}
88
- components={components}
89
- isOdd={index % 2 === 1}
90
- isOpen={category === categoryToOpen}
91
- // TODO?
92
- // isFirst={index === 0}
93
- onAddComponent={handleAddComponentToDz}
94
- onToggle={handleClickToggle}
95
- />
96
- );
97
- })}
80
+ {dynamicComponentCategories.map(({ category, components }, index) => (
81
+ <ComponentCategory
82
+ key={category}
83
+ category={category}
84
+ components={components}
85
+ onAddComponent={handleAddComponentToDz}
86
+ isOpen={category === categoryToOpen}
87
+ onToggle={handleClickToggle}
88
+ variant={index % 2 === 1 ? 'primary' : 'secondary'}
89
+ />
90
+ ))}
98
91
  </KeyboardNavigable>
99
92
  </Box>
100
93
  </Box>
@@ -102,10 +95,15 @@ const ComponentPicker = ({ components, isOpen, onClickAddComponent }) => {
102
95
  );
103
96
  };
104
97
 
98
+ ComponentPicker.defaultProps = {
99
+ components: [],
100
+ isOpen: false,
101
+ };
102
+
105
103
  ComponentPicker.propTypes = {
106
- components: PropTypes.array.isRequired,
107
- isOpen: PropTypes.bool.isRequired,
104
+ components: PropTypes.array,
105
+ isOpen: PropTypes.bool,
108
106
  onClickAddComponent: PropTypes.func.isRequired,
109
107
  };
110
108
 
111
- export default memo(ComponentPicker);
109
+ export default ComponentPicker;
@@ -0,0 +1,195 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import { useIntl } from 'react-intl';
5
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6
+ import get from 'lodash/get';
7
+
8
+ import { Accordion, AccordionToggle, AccordionContent } from '@strapi/design-system/Accordion';
9
+ import { IconButton } from '@strapi/design-system/IconButton';
10
+ import { Box } from '@strapi/design-system/Box';
11
+ import { Flex } from '@strapi/design-system/Flex';
12
+ import { Stack } from '@strapi/design-system/Stack';
13
+
14
+ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
15
+
16
+ import Trash from '@strapi/icons/Trash';
17
+ import ArrowDown from '@strapi/icons/ArrowDown';
18
+ import ArrowUp from '@strapi/icons/ArrowUp';
19
+
20
+ import { useContentTypeLayout } from '../../../hooks';
21
+ import { getTrad } from '../../../utils';
22
+
23
+ import FieldComponent from '../../FieldComponent';
24
+
25
+ const IconButtonCustom = styled(IconButton)`
26
+ background-color: transparent;
27
+
28
+ svg path {
29
+ fill: ${({ theme, expanded }) =>
30
+ expanded ? theme.colors.primary600 : theme.colors.neutral600};
31
+ }
32
+ `;
33
+
34
+ const StyledBox = styled(Box)`
35
+ > div:first-child {
36
+ box-shadow: ${({ theme }) => theme.shadows.tableShadow};
37
+ }
38
+ `;
39
+
40
+ const AccordionContentRadius = styled(Box)`
41
+ border-radius: 0 0 ${({ theme }) => theme.spaces[1]} ${({ theme }) => theme.spaces[1]};
42
+ `;
43
+
44
+ const Rectangle = styled(Box)`
45
+ width: ${({ theme }) => theme.spaces[2]};
46
+ height: ${({ theme }) => theme.spaces[4]};
47
+ `;
48
+
49
+ const DynamicZoneComponent = ({
50
+ componentUid,
51
+ formErrors,
52
+ index,
53
+ isFieldAllowed,
54
+ onMoveComponentDownClick,
55
+ onMoveComponentUpClick,
56
+ name,
57
+ onRemoveComponentClick,
58
+ showDownIcon,
59
+ showUpIcon,
60
+ }) => {
61
+ const [isOpen, setIsOpen] = useState(true);
62
+ const { formatMessage } = useIntl();
63
+ const { getComponentLayout } = useContentTypeLayout();
64
+ const { modifiedData } = useCMEditViewDataManager();
65
+ const { icon, friendlyName, mainValue } = useMemo(() => {
66
+ const componentLayoutData = getComponentLayout(componentUid);
67
+
68
+ const {
69
+ info: { icon, displayName },
70
+ } = componentLayoutData;
71
+
72
+ const mainFieldKey = get(componentLayoutData, ['settings', 'mainField'], 'id');
73
+
74
+ const mainField = get(modifiedData, [name, index, mainFieldKey]) ?? '';
75
+
76
+ const displayedValue = mainFieldKey === 'id' ? '' : mainField.trim();
77
+
78
+ const mainValue = displayedValue.length > 0 ? ` - ${displayedValue}` : displayedValue;
79
+
80
+ return { friendlyName: displayName, icon, mainValue };
81
+ }, [componentUid, getComponentLayout, modifiedData, name, index]);
82
+
83
+ const fieldsErrors = Object.keys(formErrors).filter((errorKey) => {
84
+ const errorKeysArray = errorKey.split('.');
85
+
86
+ if (`${errorKeysArray[0]}.${errorKeysArray[1]}` === `${name}.${index}`) {
87
+ return true;
88
+ }
89
+
90
+ return false;
91
+ });
92
+
93
+ let errorMessage;
94
+
95
+ if (fieldsErrors.length > 0) {
96
+ errorMessage = formatMessage({
97
+ id: getTrad('components.DynamicZone.error-message'),
98
+ defaultMessage: 'The component contains error(s)',
99
+ });
100
+ }
101
+
102
+ const handleToggle = () => {
103
+ setIsOpen((s) => !s);
104
+ };
105
+
106
+ return (
107
+ <Box>
108
+ <Flex justifyContent="center">
109
+ <Rectangle background="neutral200" />
110
+ </Flex>
111
+ <StyledBox hasRadius>
112
+ <Accordion expanded={isOpen} onToggle={handleToggle} size="S" error={errorMessage}>
113
+ <AccordionToggle
114
+ startIcon={<FontAwesomeIcon icon={icon} />}
115
+ action={
116
+ <Stack horizontal spacing={0} expanded={isOpen}>
117
+ {showDownIcon && (
118
+ <IconButtonCustom
119
+ noBorder
120
+ label={formatMessage({
121
+ id: getTrad('components.DynamicZone.move-down-label'),
122
+ defaultMessage: 'Move component down',
123
+ })}
124
+ onClick={onMoveComponentDownClick}
125
+ icon={<ArrowDown />}
126
+ />
127
+ )}
128
+ {showUpIcon && (
129
+ <IconButtonCustom
130
+ noBorder
131
+ label={formatMessage({
132
+ id: getTrad('components.DynamicZone.move-up-label'),
133
+ defaultMessage: 'Move component up',
134
+ })}
135
+ onClick={onMoveComponentUpClick}
136
+ icon={<ArrowUp />}
137
+ />
138
+ )}
139
+ {isFieldAllowed && (
140
+ <IconButtonCustom
141
+ noBorder
142
+ label={formatMessage(
143
+ {
144
+ id: getTrad('components.DynamicZone.delete-label'),
145
+ defaultMessage: 'Delete {name}',
146
+ },
147
+ { name: friendlyName }
148
+ )}
149
+ onClick={onRemoveComponentClick}
150
+ icon={<Trash />}
151
+ />
152
+ )}
153
+ </Stack>
154
+ }
155
+ title={`${friendlyName}${mainValue}`}
156
+ togglePosition="left"
157
+ />
158
+ <AccordionContent>
159
+ <AccordionContentRadius background="neutral0">
160
+ <FieldComponent
161
+ componentUid={componentUid}
162
+ icon={icon}
163
+ name={`${name}.${index}`}
164
+ isFromDynamicZone
165
+ />
166
+ </AccordionContentRadius>
167
+ </AccordionContent>
168
+ </Accordion>
169
+ </StyledBox>
170
+ </Box>
171
+ );
172
+ };
173
+
174
+ DynamicZoneComponent.defaultProps = {
175
+ formErrors: {},
176
+ index: 0,
177
+ isFieldAllowed: true,
178
+ showDownIcon: true,
179
+ showUpIcon: true,
180
+ };
181
+
182
+ DynamicZoneComponent.propTypes = {
183
+ componentUid: PropTypes.string.isRequired,
184
+ formErrors: PropTypes.object,
185
+ index: PropTypes.number,
186
+ isFieldAllowed: PropTypes.bool,
187
+ name: PropTypes.string.isRequired,
188
+ onMoveComponentDownClick: PropTypes.func.isRequired,
189
+ onMoveComponentUpClick: PropTypes.func.isRequired,
190
+ onRemoveComponentClick: PropTypes.func.isRequired,
191
+ showDownIcon: PropTypes.bool,
192
+ showUpIcon: PropTypes.bool,
193
+ };
194
+
195
+ export default DynamicZoneComponent;
@@ -17,7 +17,14 @@ const StyledBox = styled(Box)`
17
17
  border-radius: ${pxToRem(26)};
18
18
  `;
19
19
 
20
- const DzLabel = ({ label, labelAction, name, numberOfComponents, required, intlDescription }) => {
20
+ const DynamicZoneLabel = ({
21
+ label,
22
+ labelAction,
23
+ name,
24
+ numberOfComponents,
25
+ required,
26
+ intlDescription,
27
+ }) => {
21
28
  const { formatMessage } = useIntl();
22
29
  const intlLabel = formatMessage({ id: label || name, defaultMessage: label || name });
23
30
 
@@ -58,14 +65,15 @@ const DzLabel = ({ label, labelAction, name, numberOfComponents, required, intlD
58
65
  );
59
66
  };
60
67
 
61
- DzLabel.defaultProps = {
68
+ DynamicZoneLabel.defaultProps = {
62
69
  intlDescription: undefined,
63
70
  label: '',
64
71
  labelAction: undefined,
72
+ numberOfComponents: 0,
65
73
  required: false,
66
74
  };
67
75
 
68
- DzLabel.propTypes = {
76
+ DynamicZoneLabel.propTypes = {
69
77
  intlDescription: PropTypes.shape({
70
78
  id: PropTypes.string.isRequired,
71
79
  defaultMessage: PropTypes.string.isRequired,
@@ -73,8 +81,8 @@ DzLabel.propTypes = {
73
81
  label: PropTypes.string,
74
82
  labelAction: PropTypes.element,
75
83
  name: PropTypes.string.isRequired,
76
- numberOfComponents: PropTypes.number.isRequired,
84
+ numberOfComponents: PropTypes.number,
77
85
  required: PropTypes.bool,
78
86
  };
79
87
 
80
- export default DzLabel;
88
+ export default DynamicZoneLabel;