@strapi/admin 4.6.0-alpha.1 → 4.6.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +13 -2
  2. package/admin/src/components/GlobalStyle/index.js +0 -5
  3. package/admin/src/content-manager/components/ComponentIcon/ComponentIcon.js +49 -0
  4. package/admin/src/content-manager/components/ComponentIcon/index.js +1 -0
  5. package/admin/src/content-manager/components/ComponentInitializer/index.js +1 -7
  6. package/admin/src/content-manager/components/{RepeatableComponent/DragPreview.js → DragLayer/ComponentDragPreview.js} +10 -11
  7. package/admin/src/content-manager/components/DragLayer/RelationDragPreview.js +75 -0
  8. package/admin/src/content-manager/components/DragLayer/index.js +23 -7
  9. package/admin/src/content-manager/components/DynamicZone/components/ComponentCard.js +17 -31
  10. package/admin/src/content-manager/components/DynamicZone/components/ComponentCategory.js +2 -2
  11. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +129 -85
  12. package/admin/src/content-manager/components/DynamicZone/index.js +99 -24
  13. package/admin/src/content-manager/components/DynamicZone/utils/select.js +9 -5
  14. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +76 -14
  15. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +42 -25
  16. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +24 -5
  17. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/recursivelyFindPathsBasedOnCondition.js +8 -1
  18. package/admin/src/content-manager/components/InputJSON/FieldWrapper.js +10 -2
  19. package/admin/src/content-manager/components/InputJSON/Label.js +2 -18
  20. package/admin/src/content-manager/components/InputJSON/index.js +7 -3
  21. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +4 -0
  22. package/admin/src/content-manager/components/RelationInput/RelationInput.js +205 -74
  23. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +136 -21
  24. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +1 -2
  25. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  26. package/admin/src/content-manager/components/RelationInput/index.js +1 -0
  27. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +132 -10
  28. package/admin/src/content-manager/components/RepeatableComponent/components/Accordion.js +77 -0
  29. package/admin/src/content-manager/components/RepeatableComponent/components/Component.js +262 -0
  30. package/admin/src/content-manager/components/RepeatableComponent/{DraggedItem → components}/Preview.js +0 -0
  31. package/admin/src/content-manager/components/RepeatableComponent/index.js +148 -90
  32. package/admin/src/content-manager/components/RepeatableComponent/utils/getComponentErrorKeys.js +1 -1
  33. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +1 -1
  34. package/admin/src/content-manager/components/Wysiwyg/Editor.js +1 -1
  35. package/admin/src/content-manager/hooks/index.js +2 -0
  36. package/admin/src/content-manager/hooks/useDragAndDrop.js +134 -0
  37. package/admin/src/content-manager/hooks/useKeyboardDragAndDrop.js +98 -0
  38. package/admin/src/content-manager/hooks/useLazyComponents/index.js +40 -15
  39. package/admin/src/content-manager/pages/EditSettingsView/components/DynamicZoneList.js +18 -38
  40. package/admin/src/content-manager/sharedReducers/crudReducer/actions.js +5 -0
  41. package/admin/src/content-manager/sharedReducers/crudReducer/constants.js +2 -0
  42. package/admin/src/content-manager/sharedReducers/crudReducer/reducer.js +7 -0
  43. package/admin/src/content-manager/utils/ItemTypes.js +1 -1
  44. package/admin/src/content-manager/utils/composeRefs.js +28 -0
  45. package/admin/src/content-manager/utils/getMaxTempKey.js +1 -1
  46. package/admin/src/content-manager/utils/index.js +7 -0
  47. package/admin/src/core/utils/axiosInstance.js +4 -2
  48. package/admin/src/hooks/index.js +1 -0
  49. package/admin/src/hooks/useFetchClient/index.js +23 -0
  50. package/admin/src/hooks/useSettingsMenu/init.js +0 -7
  51. package/admin/src/pages/Admin/Onboarding/index.js +42 -44
  52. package/admin/src/pages/App/index.js +20 -13
  53. package/admin/src/pages/AuthPage/components/Register/index.js +1 -1
  54. package/admin/src/pages/AuthPage/components/ResetPassword/index.js +1 -1
  55. package/admin/src/pages/HomePage/SocialLinks.js +4 -4
  56. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +2 -3
  57. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +23 -18
  58. package/admin/src/pages/SettingsPage/utils/defaultRoutes.js +0 -11
  59. package/admin/src/permissions/defaultPermissions.js +0 -4
  60. package/admin/src/translations/en.json +8 -8
  61. package/admin/src/translations/sk.json +274 -52
  62. package/admin/src/translations/tr.json +485 -5
  63. package/admin/src/utils/fetchClient.js +45 -0
  64. package/admin/src/utils/getFetchClient.js +10 -0
  65. package/admin/src/utils/index.js +1 -0
  66. package/admin/src/utils/uniqueAdminHash.js +22 -0
  67. package/build/2235.06c13219.chunk.js +106 -0
  68. package/build/2598.962797b2.chunk.js +159 -0
  69. package/build/4318.0bbd3f4b.chunk.js +30 -0
  70. package/build/4958.7c118f5e.chunk.js +276 -0
  71. package/build/5052.712419ea.chunk.js +65 -0
  72. package/build/7295.04ac49dc.chunk.js +114 -0
  73. package/build/805.ddcead70.chunk.js +138 -0
  74. package/build/{8633.8da5488a.chunk.js → 8633.59223842.chunk.js} +1 -1
  75. package/build/874.bde3ea04.chunk.js +104 -0
  76. package/build/{1233.80b05d66.chunk.js → 9159.ac968e72.chunk.js} +67 -67
  77. package/build/9707.77e475ee.chunk.js +101 -0
  78. package/build/Admin-authenticatedApp.9dd415b8.chunk.js +72 -0
  79. package/build/{Admin_homePage.54e33c2d.chunk.js → Admin_homePage.8945f71a.chunk.js} +5 -5
  80. package/build/{Admin_marketplace.8219fda6.chunk.js → Admin_marketplace.ed754a4a.chunk.js} +1 -1
  81. package/build/Admin_pluginsPage.67728975.chunk.js +6 -0
  82. package/build/{Admin_profilePage.e9fcce92.chunk.js → Admin_profilePage.60ab80bb.chunk.js} +1 -1
  83. package/build/{Admin_settingsPage.d3f48e9e.chunk.js → Admin_settingsPage.9ce40fed.chunk.js} +15 -15
  84. package/build/Upload_ConfigureTheView.7cb2a3fd.chunk.js +1 -0
  85. package/build/admin-app.d8fc7c4d.chunk.js +112 -0
  86. package/build/admin-edit-roles-page.f407538c.chunk.js +1 -0
  87. package/build/admin-edit-users.5547b126.chunk.js +10 -0
  88. package/build/{admin-users.a0748674.chunk.js → admin-users.4b6b47f8.chunk.js} +2 -2
  89. package/build/api-tokens-create-page.dd4ddfcb.chunk.js +1 -0
  90. package/build/api-tokens-edit-page.821c5a6c.chunk.js +1 -0
  91. package/build/{api-tokens-list-page.700e575f.chunk.js → api-tokens-list-page.50519ed7.chunk.js} +1 -1
  92. package/build/content-manager.f2214e32.chunk.js +1166 -0
  93. package/build/content-type-builder-list-view.4aea46fa.chunk.js +198 -0
  94. package/build/content-type-builder-translation-de-json.a52482c7.chunk.js +1 -0
  95. package/build/content-type-builder-translation-dk-json.a8616510.chunk.js +1 -0
  96. package/build/content-type-builder-translation-en-json.1d9a3c14.chunk.js +1 -0
  97. package/build/content-type-builder-translation-es-json.c3ea46fb.chunk.js +1 -0
  98. package/build/content-type-builder-translation-ko-json.3fb7ddc8.chunk.js +1 -0
  99. package/build/content-type-builder-translation-pl-json.9b2993b2.chunk.js +1 -0
  100. package/build/content-type-builder-translation-pt-BR-json.6d255441.chunk.js +1 -0
  101. package/build/content-type-builder-translation-sv-json.c608b9ca.chunk.js +1 -0
  102. package/build/content-type-builder-translation-tr-json.949e22eb.chunk.js +1 -0
  103. package/build/content-type-builder-translation-zh-json.b79513e4.chunk.js +1 -0
  104. package/build/content-type-builder.8a9a77f9.chunk.js +127 -0
  105. package/build/email-settings-page.c6e62f6b.chunk.js +15 -0
  106. package/build/email-translation-tr-json.8aa034bb.chunk.js +1 -0
  107. package/build/en-json.1abdade9.chunk.js +1 -0
  108. package/build/{i18n-settings-page.195d42fe.chunk.js → i18n-settings-page.ee572037.chunk.js} +1 -1
  109. package/build/i18n-translation-tr-json.34ca9d61.chunk.js +1 -0
  110. package/build/index.html +1 -1
  111. package/build/main.91f6e21e.js +4099 -0
  112. package/build/runtime~main.447b0382.js +2 -0
  113. package/build/sk-json.2af48064.chunk.js +1 -0
  114. package/build/sso-settings-page.91924df1.chunk.js +41 -0
  115. package/build/tr-json.eac8bd79.chunk.js +1 -0
  116. package/build/upload-settings.326cd9fd.chunk.js +89 -0
  117. package/build/upload-translation-en-json.32cf9aff.chunk.js +1 -0
  118. package/build/upload-translation-sk-json.fe86c53b.chunk.js +1 -0
  119. package/build/upload-translation-tr-json.b173223a.chunk.js +1 -0
  120. package/build/upload.2977cb13.chunk.js +38 -0
  121. package/build/users-advanced-settings-page.0c0b8230.chunk.js +13 -0
  122. package/build/users-email-settings-page.18d4a475.chunk.js +28 -0
  123. package/build/{users-permissions-translation-dk-json.fe39c74b.chunk.js → users-permissions-translation-dk-json.bad0b786.chunk.js} +1 -1
  124. package/build/{users-permissions-translation-en-json.765abf48.chunk.js → users-permissions-translation-en-json.aeab388a.chunk.js} +1 -1
  125. package/build/{users-permissions-translation-es-json.1bb9cde2.chunk.js → users-permissions-translation-es-json.152a923f.chunk.js} +1 -1
  126. package/build/{users-permissions-translation-ko-json.3be77775.chunk.js → users-permissions-translation-ko-json.6bd0ae22.chunk.js} +1 -1
  127. package/build/{users-permissions-translation-pl-json.1dbdd4a1.chunk.js → users-permissions-translation-pl-json.c6a02992.chunk.js} +1 -1
  128. package/build/{users-permissions-translation-sv-json.d5d11648.chunk.js → users-permissions-translation-sv-json.370d6eee.chunk.js} +1 -1
  129. package/build/users-permissions-translation-tr-json.9bebc250.chunk.js +1 -0
  130. package/build/{users-permissions-translation-zh-json.92f406f9.chunk.js → users-permissions-translation-zh-json.1fea833f.chunk.js} +1 -1
  131. package/build/users-providers-settings-page.25dd858e.chunk.js +1 -0
  132. package/build/users-roles-settings-page.8482a999.chunk.js +30 -0
  133. package/build/{webhook-edit-page.14ad1e6e.chunk.js → webhook-edit-page.dcc3d145.chunk.js} +4 -4
  134. package/build/{webhook-list-page.b87821f2.chunk.js → webhook-list-page.894e6959.chunk.js} +1 -1
  135. package/ee/server/services/passport/provider-registry.js +1 -1
  136. package/package.json +16 -22
  137. package/server/controllers/admin.js +2 -0
  138. package/server/routes/admin.js +1 -1
  139. package/server/services/metrics.js +5 -2
  140. package/server/services/role.js +1 -0
  141. package/utils/get-plugins-path.js +17 -3
  142. package/webpack.alias.js +0 -2
  143. package/admin/src/content-manager/components/BackHeader/index.js +0 -8
  144. package/admin/src/content-manager/components/Block/components.js +0 -28
  145. package/admin/src/content-manager/components/Block/index.js +0 -43
  146. package/admin/src/content-manager/components/Container/index.js +0 -7
  147. package/admin/src/content-manager/components/CustomInputCheckbox/components.js +0 -77
  148. package/admin/src/content-manager/components/CustomInputCheckbox/index.js +0 -53
  149. package/admin/src/content-manager/components/DynamicComponentCard/Wrapper.js +0 -63
  150. package/admin/src/content-manager/components/FilterOptionsCTA/index.js +0 -14
  151. package/admin/src/content-manager/components/FormTitle/index.js +0 -22
  152. package/admin/src/content-manager/components/FormWrapper/index.js +0 -20
  153. package/admin/src/content-manager/components/InputJSON/FieldError.js +0 -38
  154. package/admin/src/content-manager/components/LayoutTitle/index.js +0 -19
  155. package/admin/src/content-manager/components/PlusButton/index.js +0 -52
  156. package/admin/src/content-manager/components/PreviewCarret/components.js +0 -27
  157. package/admin/src/content-manager/components/PreviewCarret/index.js +0 -22
  158. package/admin/src/content-manager/components/RepeatableComponent/AccordionGroupCustom/index.js +0 -122
  159. package/admin/src/content-manager/components/RepeatableComponent/AddFieldButton.js +0 -58
  160. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js +0 -72
  161. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/IconButtonCustoms.js +0 -32
  162. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +0 -322
  163. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/connect.js +0 -11
  164. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/index.js +0 -2
  165. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js +0 -30
  166. package/admin/src/content-manager/components/RepeatableComponent/utils/connect.js +0 -11
  167. package/admin/src/content-manager/components/RepeatableComponent/utils/select.js +0 -12
  168. package/admin/src/content-manager/components/SectionTitle/Title.js +0 -11
  169. package/admin/src/content-manager/components/SectionTitle/index.js +0 -26
  170. package/admin/src/content-manager/hooks/__test__/usePrev.test.js +0 -26
  171. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/DynamicTable/TableRows/CellValue.js +0 -19
  172. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/DynamicTable/TableRows/index.js +0 -65
  173. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/ActionItem.js +0 -25
  174. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/ModalDialog/index.js +0 -76
  175. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useFormatTimeStamp.js +0 -24
  176. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/index.js +0 -84
  177. package/admin/src/pages/SettingsPage/pages/AuditLogs/ListView/utils/tableHeaders.js +0 -37
  178. package/admin/src/pages/SettingsPage/pages/AuditLogs/ProtectedListPage/index.js +0 -12
  179. package/build/1551f4f60c37af51121f.woff2 +0 -0
  180. package/build/1920.74a262e7.chunk.js +0 -245
  181. package/build/1e59d2330b4c6deb84b3.ttf +0 -0
  182. package/build/20fd1704ea223900efa9.woff2 +0 -0
  183. package/build/2285773e6b4b172f07d9.woff +0 -0
  184. package/build/23f19bb08961f37aaf69.eot +0 -0
  185. package/build/2438.61291207.chunk.js +0 -2183
  186. package/build/2517.9b4940f3.chunk.js +0 -117
  187. package/build/2f517e09eb2ca6650ff5.svg +0 -3717
  188. package/build/4306.f03c2b46.chunk.js +0 -98
  189. package/build/4318.7931eee7.chunk.js +0 -30
  190. package/build/4689f52cc96215721344.svg +0 -801
  191. package/build/491974d108fe4002b2aa.ttf +0 -0
  192. package/build/4986.3820d11d.chunk.js +0 -145
  193. package/build/504.9aeff724.chunk.js +0 -758
  194. package/build/527940b104eb2ea366c8.ttf +0 -0
  195. package/build/77206a6bb316fa0aded5.eot +0 -0
  196. package/build/7a3337626410ca2f4071.woff2 +0 -0
  197. package/build/7a8b4f130182d19a2d7c.svg +0 -5034
  198. package/build/805.e991a370.chunk.js +0 -138
  199. package/build/8b43027f47b20503057d.eot +0 -0
  200. package/build/9707.a0cc4ad8.chunk.js +0 -70
  201. package/build/9bbb245e67a133f6e486.eot +0 -0
  202. package/build/Admin-authenticatedApp.ac85652e.chunk.js +0 -80
  203. package/build/Admin_pluginsPage.3c872de7.chunk.js +0 -6
  204. package/build/admin-app.77179e07.chunk.js +0 -112
  205. package/build/admin-audit-logs.334ee871.chunk.js +0 -1
  206. package/build/admin-edit-roles-page.23f15909.chunk.js +0 -1
  207. package/build/admin-edit-users.283b49ed.chunk.js +0 -10
  208. package/build/api-tokens-create-page.93dd0689.chunk.js +0 -1
  209. package/build/api-tokens-edit-page.b0adac81.chunk.js +0 -1
  210. package/build/bb58e57c48a3e911f15f.woff +0 -0
  211. package/build/be9ee23c0c6390141475.ttf +0 -0
  212. package/build/c1e38fd9e0e74ba58f7a.svg +0 -2671
  213. package/build/content-manager.01e04e11.chunk.js +0 -1200
  214. package/build/content-type-builder-list-view.4412efc3.chunk.js +0 -201
  215. package/build/content-type-builder-translation-de-json.0d7696b9.chunk.js +0 -1
  216. package/build/content-type-builder-translation-dk-json.4729f055.chunk.js +0 -1
  217. package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +0 -1
  218. package/build/content-type-builder-translation-es-json.333cf47f.chunk.js +0 -1
  219. package/build/content-type-builder-translation-ko-json.51201b12.chunk.js +0 -1
  220. package/build/content-type-builder-translation-pl-json.4a42349b.chunk.js +0 -1
  221. package/build/content-type-builder-translation-pt-BR-json.6fe3b8d1.chunk.js +0 -1
  222. package/build/content-type-builder-translation-sv-json.6deff030.chunk.js +0 -1
  223. package/build/content-type-builder-translation-tr-json.2e52bc60.chunk.js +0 -1
  224. package/build/content-type-builder-translation-zh-json.3b0afd31.chunk.js +0 -1
  225. package/build/content-type-builder.aa4ec633.chunk.js +0 -145
  226. package/build/d878b0a6a1144760244f.woff2 +0 -0
  227. package/build/eeccf4f66002c6f2ba24.woff +0 -0
  228. package/build/email-settings-page.d44a57cb.chunk.js +0 -15
  229. package/build/email-translation-tr-json.87f2feb3.chunk.js +0 -1
  230. package/build/en-json.57917cb1.chunk.js +0 -1
  231. package/build/f691f37e57f04c152e23.woff +0 -0
  232. package/build/fontawesome-css-all.15068c6e.chunk.js +0 -4618
  233. package/build/fontawesome-css.418f40da.chunk.js +0 -6
  234. package/build/fontawesome-js.252cc5f3.chunk.js +0 -7
  235. package/build/main.f31112a5.js +0 -2034
  236. package/build/runtime~main.81f05721.js +0 -2
  237. package/build/sk-json.7ba4b330.chunk.js +0 -1
  238. package/build/sso-settings-page.9f091262.chunk.js +0 -1
  239. package/build/tr-json.9c44ea0c.chunk.js +0 -1
  240. package/build/upload-settings.450cab1a.chunk.js +0 -18
  241. package/build/upload-translation-en-json.86da7b0a.chunk.js +0 -1
  242. package/build/upload-translation-sk-json.b03d4904.chunk.js +0 -1
  243. package/build/upload.a73936d9.chunk.js +0 -64
  244. package/build/users-advanced-settings-page.dc23bc56.chunk.js +0 -13
  245. package/build/users-email-settings-page.6541d372.chunk.js +0 -28
  246. package/build/users-permissions-translation-tr-json.cdc49a3c.chunk.js +0 -1
  247. package/build/users-providers-settings-page.e11a2f64.chunk.js +0 -33
  248. package/build/users-roles-settings-page.445e5e16.chunk.js +0 -30
@@ -157,13 +157,17 @@ class InputJSON extends React.Component {
157
157
  }
158
158
 
159
159
  return (
160
- <FieldWrapper name={this.props.name} hint={this.props.description} error={this.props.error}>
160
+ <FieldWrapper
161
+ name={this.props.name}
162
+ hint={this.props.description}
163
+ error={this.props.error}
164
+ required={this.props.required}
165
+ >
161
166
  <Stack spacing={1}>
162
167
  <Label
163
168
  intlLabel={this.props.intlLabel}
164
- labelAction={this.props.labelAction}
165
169
  name={this.props.name}
166
- required={this.props.required}
170
+ labelAction={this.props.labelAction}
167
171
  />
168
172
  <StyledBox error={this.props.error}>
169
173
  <EditorWrapper disabled={this.props.disabled}>
@@ -9,6 +9,7 @@ import { Stack } from '@strapi/design-system/Stack';
9
9
  import { useContentTypeLayout } from '../../hooks';
10
10
  import FieldComponent from '../FieldComponent';
11
11
  import Inputs from '../Inputs';
12
+ import useLazyComponents from '../../hooks/useLazyComponents';
12
13
 
13
14
  const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, name }) => {
14
15
  const { getComponentLayout } = useContentTypeLayout();
@@ -18,6 +19,8 @@ const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, nam
18
19
  );
19
20
  const fields = componentLayoutData.layouts.edit;
20
21
 
22
+ const { lazyComponentStore } = useLazyComponents();
23
+
21
24
  return (
22
25
  <Box
23
26
  background={isFromDynamicZone ? '' : 'neutral100'}
@@ -67,6 +70,7 @@ const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, nam
67
70
  metadatas={metadatas}
68
71
  queryInfos={queryInfos}
69
72
  size={size}
73
+ customFieldInputs={lazyComponentStore}
70
74
  />
71
75
  </GridItem>
72
76
  );
@@ -12,6 +12,7 @@ import { FieldLabel, FieldError, FieldHint, Field } from '@strapi/design-system/
12
12
  import { TextButton } from '@strapi/design-system/TextButton';
13
13
  import { Typography } from '@strapi/design-system/Typography';
14
14
  import { Tooltip } from '@strapi/design-system/Tooltip';
15
+ import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
15
16
 
16
17
  import Cross from '@strapi/icons/Cross';
17
18
  import Refresh from '@strapi/icons/Refresh';
@@ -20,26 +21,22 @@ import { Relation } from './components/Relation';
20
21
  import { RelationItem } from './components/RelationItem';
21
22
  import { RelationList } from './components/RelationList';
22
23
  import { Option } from './components/Option';
23
- import { RELATION_ITEM_HEIGHT } from './constants';
24
+ import { RELATION_GUTTER, RELATION_ITEM_HEIGHT } from './constants';
25
+
24
26
  import { usePrev } from '../../hooks';
25
27
 
26
- const LinkEllipsis = styled(Link)`
27
- white-space: nowrap;
28
- overflow: hidden;
29
- text-overflow: ellipsis;
30
- display: inherit;
31
- `;
28
+ export const LinkEllipsis = styled(Link)`
29
+ display: block;
32
30
 
33
- const BoxEllipsis = styled(Box)`
34
31
  > span {
35
32
  white-space: nowrap;
36
33
  overflow: hidden;
37
34
  text-overflow: ellipsis;
38
- display: inherit;
35
+ display: block;
39
36
  }
40
37
  `;
41
38
 
42
- const DisconnectButton = styled.button`
39
+ export const DisconnectButton = styled.button`
43
40
  svg path {
44
41
  fill: ${({ theme }) => theme.colors.neutral500};
45
42
  }
@@ -51,9 +48,11 @@ const DisconnectButton = styled.button`
51
48
  `;
52
49
 
53
50
  const RelationInput = ({
51
+ canReorder,
54
52
  description,
55
53
  disabled,
56
54
  error,
55
+ iconButtonAriaLabel,
57
56
  id,
58
57
  name,
59
58
  numberOfRelationsToDisplay,
@@ -61,11 +60,17 @@ const RelationInput = ({
61
60
  labelAction,
62
61
  labelLoadMore,
63
62
  labelDisconnectRelation,
63
+ listAriaDescription,
64
+ liveText,
64
65
  loadingMessage,
66
+ onCancel,
67
+ onDropItem,
68
+ onGrabItem,
65
69
  noRelationsMessage,
66
70
  onRelationConnect,
67
71
  onRelationLoadMore,
68
72
  onRelationDisconnect,
73
+ onRelationReorder,
69
74
  onSearchNextPage,
70
75
  onSearch,
71
76
  placeholder,
@@ -76,9 +81,10 @@ const RelationInput = ({
76
81
  size,
77
82
  }) => {
78
83
  const [value, setValue] = useState(null);
84
+ const [overflow, setOverflow] = useState('');
85
+
79
86
  const listRef = useRef();
80
87
  const outerListRef = useRef();
81
- const [overflow, setOverflow] = useState('');
82
88
 
83
89
  const { data } = searchResults;
84
90
 
@@ -88,9 +94,11 @@ const RelationInput = ({
88
94
  const dynamicListHeight = useMemo(
89
95
  () =>
90
96
  totalNumberOfRelations > numberOfRelationsToDisplay
91
- ? Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT +
97
+ ? Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) *
98
+ (RELATION_ITEM_HEIGHT + RELATION_GUTTER) +
92
99
  RELATION_ITEM_HEIGHT / 2
93
- : Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT,
100
+ : Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) *
101
+ (RELATION_ITEM_HEIGHT + RELATION_GUTTER),
94
102
  [totalNumberOfRelations, numberOfRelationsToDisplay]
95
103
  );
96
104
 
@@ -144,6 +152,9 @@ const RelationInput = ({
144
152
  };
145
153
  }, [paginatedRelations, relations, numberOfRelationsToDisplay, totalNumberOfRelations]);
146
154
 
155
+ /**
156
+ * --- ReactSelect Workaround START ---
157
+ */
147
158
  /**
148
159
  * This code is being isolated because it's a hack to fix a placement bug in
149
160
  * `react-select` where when the options prop is updated the position of the
@@ -199,12 +210,28 @@ const RelationInput = ({
199
210
  const handleMenuClose = () => {
200
211
  setIsMenuOpen(false);
201
212
  };
213
+ /**
214
+ * --- ReactSelect Workaround END ---
215
+ */
202
216
 
203
217
  const handleMenuOpen = () => {
204
218
  setIsMenuOpen(true);
205
219
  onSearch();
206
220
  };
207
221
 
222
+ /**
223
+ *
224
+ * @param {number} newIndex
225
+ * @param {number} currentIndex
226
+ *
227
+ * @returns {void}
228
+ */
229
+ const handleUpdatePositionOfRelation = (newIndex, currentIndex) => {
230
+ if (onRelationReorder && newIndex >= 0 && newIndex < relations.length) {
231
+ onRelationReorder(currentIndex, newIndex);
232
+ }
233
+ };
234
+
208
235
  const previewRelationsLength = usePrev(relations.length);
209
236
  /**
210
237
  * @type {React.MutableRefObject<'onChange' | 'loadMore'>}
@@ -228,18 +255,20 @@ const RelationInput = ({
228
255
  ) {
229
256
  listRef.current.scrollToItem(0, 'start');
230
257
  }
258
+
259
+ updatedRelationsWith.current = undefined;
231
260
  }, [previewRelationsLength, relations]);
232
261
 
262
+ const ariaDescriptionId = `${name}-item-instructions`;
263
+
233
264
  return (
234
- <Field error={error} name={name} hint={description} id={id}>
265
+ <Field error={error} name={name} hint={description} id={id} required={required}>
235
266
  <Relation
236
267
  totalNumberOfRelations={totalNumberOfRelations}
237
268
  size={size}
238
269
  search={
239
270
  <>
240
- <FieldLabel action={labelAction} required={required}>
241
- {label}
242
- </FieldLabel>
271
+ <FieldLabel action={labelAction}>{label}</FieldLabel>
243
272
  <ReactSelect
244
273
  // position fixed doesn't update position on scroll
245
274
  // react select doesn't update menu position on options change
@@ -291,63 +320,38 @@ const RelationInput = ({
291
320
  )
292
321
  }
293
322
  >
294
- <RelationList overflow={overflow}>
295
- <List
296
- height={dynamicListHeight}
297
- ref={listRef}
298
- outerRef={outerListRef}
299
- itemCount={totalNumberOfRelations}
300
- itemSize={RELATION_ITEM_HEIGHT}
301
- itemData={relations}
302
- innerElementType="ol"
303
- >
304
- {({ data, index, style }) => {
305
- const { publicationState, href, mainField, id } = data[index];
306
- const statusColor = publicationState === 'draft' ? 'secondary' : 'success';
307
-
308
- return (
309
- <RelationItem
310
- disabled={disabled}
311
- key={`relation-${name}-${id}`}
312
- endAction={
313
- <DisconnectButton
314
- data-testid={`remove-relation-${id}`}
315
- disabled={disabled}
316
- type="button"
317
- onClick={() => onRelationDisconnect(data[index])}
318
- aria-label={labelDisconnectRelation}
319
- >
320
- <Icon width="12px" as={Cross} />
321
- </DisconnectButton>
322
- }
323
- style={style}
324
- >
325
- <BoxEllipsis minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
326
- <Tooltip description={mainField ?? `${id}`}>
327
- {href ? (
328
- <LinkEllipsis to={href} disabled={disabled}>
329
- {mainField ?? id}
330
- </LinkEllipsis>
331
- ) : (
332
- <Typography textColor={disabled ? 'neutral600' : 'primary600'} ellipsis>
333
- {mainField ?? id}
334
- </Typography>
335
- )}
336
- </Tooltip>
337
- </BoxEllipsis>
338
-
339
- {publicationState && (
340
- <Status variant={statusColor} showBullet={false} size="S">
341
- <Typography fontWeight="bold" textColor={`${statusColor}700`}>
342
- {publicationStateTranslations[publicationState]}
343
- </Typography>
344
- </Status>
345
- )}
346
- </RelationItem>
347
- );
348
- }}
349
- </List>
350
- </RelationList>
323
+ {relations.length > 0 && (
324
+ <RelationList overflow={overflow}>
325
+ <VisuallyHidden id={ariaDescriptionId}>{listAriaDescription}</VisuallyHidden>
326
+ <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>
327
+ <List
328
+ height={dynamicListHeight}
329
+ ref={listRef}
330
+ outerRef={outerListRef}
331
+ itemCount={totalNumberOfRelations}
332
+ itemSize={RELATION_ITEM_HEIGHT + RELATION_GUTTER}
333
+ itemData={{
334
+ name,
335
+ ariaDescribedBy: ariaDescriptionId,
336
+ canDrag: canReorder,
337
+ disabled,
338
+ handleCancel: onCancel,
339
+ handleDropItem: onDropItem,
340
+ handleGrabItem: onGrabItem,
341
+ iconButtonAriaLabel,
342
+ labelDisconnectRelation,
343
+ onRelationDisconnect,
344
+ publicationStateTranslations,
345
+ relations,
346
+ updatePositionOfRelation: handleUpdatePositionOfRelation,
347
+ }}
348
+ itemKey={(index) => `${relations[index].mainField}_${relations[index].id}`}
349
+ innerElementType="ol"
350
+ >
351
+ {ListItem}
352
+ </List>
353
+ </RelationList>
354
+ )}
351
355
  {(description || error) && (
352
356
  <Box paddingTop={2}>
353
357
  <FieldHint />
@@ -389,11 +393,16 @@ const SearchResults = PropTypes.shape({
389
393
  });
390
394
 
391
395
  RelationInput.defaultProps = {
396
+ canReorder: false,
392
397
  description: undefined,
393
398
  disabled: false,
394
399
  error: undefined,
395
400
  labelAction: null,
396
401
  labelLoadMore: null,
402
+ liveText: undefined,
403
+ onCancel: undefined,
404
+ onDropItem: undefined,
405
+ onGrabItem: undefined,
397
406
  required: false,
398
407
  relations: { data: [] },
399
408
  searchResults: { data: [] },
@@ -401,20 +410,28 @@ RelationInput.defaultProps = {
401
410
 
402
411
  RelationInput.propTypes = {
403
412
  error: PropTypes.string,
413
+ canReorder: PropTypes.bool,
404
414
  description: PropTypes.string,
405
415
  disabled: PropTypes.bool,
416
+ iconButtonAriaLabel: PropTypes.string.isRequired,
406
417
  id: PropTypes.string.isRequired,
407
418
  label: PropTypes.string.isRequired,
408
419
  labelAction: PropTypes.element,
409
420
  labelLoadMore: PropTypes.string,
410
421
  labelDisconnectRelation: PropTypes.string.isRequired,
422
+ listAriaDescription: PropTypes.string.isRequired,
423
+ liveText: PropTypes.string,
411
424
  loadingMessage: PropTypes.string.isRequired,
412
425
  name: PropTypes.string.isRequired,
413
426
  noRelationsMessage: PropTypes.string.isRequired,
414
427
  numberOfRelationsToDisplay: PropTypes.number.isRequired,
428
+ onCancel: PropTypes.func,
429
+ onDropItem: PropTypes.func,
430
+ onGrabItem: PropTypes.func,
415
431
  onRelationConnect: PropTypes.func.isRequired,
416
432
  onRelationDisconnect: PropTypes.func.isRequired,
417
433
  onRelationLoadMore: PropTypes.func.isRequired,
434
+ onRelationReorder: PropTypes.func.isRequired,
418
435
  onSearch: PropTypes.func.isRequired,
419
436
  onSearchNextPage: PropTypes.func.isRequired,
420
437
  placeholder: PropTypes.string.isRequired,
@@ -428,4 +445,118 @@ RelationInput.propTypes = {
428
445
  relations: RelationsResult,
429
446
  };
430
447
 
448
+ /**
449
+ * This is in a seperate component to enforce passing all the props the component requires to react-window
450
+ * to ensure drag & drop correctly works.
451
+ */
452
+ const ListItem = ({ data, index, style }) => {
453
+ const {
454
+ ariaDescribedBy,
455
+ canDrag,
456
+ disabled,
457
+ handleCancel,
458
+ handleDropItem,
459
+ handleGrabItem,
460
+ iconButtonAriaLabel,
461
+ name,
462
+ labelDisconnectRelation,
463
+ onRelationDisconnect,
464
+ publicationStateTranslations,
465
+ relations,
466
+ updatePositionOfRelation,
467
+ } = data;
468
+ const { publicationState, href, mainField, id } = relations[index];
469
+ const statusColor = publicationState === 'draft' ? 'secondary' : 'success';
470
+
471
+ return (
472
+ <RelationItem
473
+ ariaDescribedBy={ariaDescribedBy}
474
+ canDrag={canDrag}
475
+ disabled={disabled}
476
+ displayValue={mainField ?? id}
477
+ iconButtonAriaLabel={iconButtonAriaLabel}
478
+ id={id}
479
+ index={index}
480
+ name={name}
481
+ endAction={
482
+ <DisconnectButton
483
+ data-testid={`remove-relation-${id}`}
484
+ disabled={disabled}
485
+ type="button"
486
+ onClick={() => onRelationDisconnect(relations[index])}
487
+ aria-label={labelDisconnectRelation}
488
+ >
489
+ <Icon width="12px" as={Cross} />
490
+ </DisconnectButton>
491
+ }
492
+ onCancel={handleCancel}
493
+ onDropItem={handleDropItem}
494
+ onGrabItem={handleGrabItem}
495
+ status={publicationState || undefined}
496
+ style={{
497
+ ...style,
498
+ bottom: style.bottom ?? 0 + RELATION_GUTTER,
499
+ height: style.height ?? 0 - RELATION_GUTTER,
500
+ }}
501
+ updatePositionOfRelation={updatePositionOfRelation}
502
+ >
503
+ <Box minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
504
+ <Tooltip description={mainField ?? `${id}`}>
505
+ {href ? (
506
+ <LinkEllipsis to={href} disabled={disabled}>
507
+ {mainField ?? id}
508
+ </LinkEllipsis>
509
+ ) : (
510
+ <Typography textColor={disabled ? 'neutral600' : 'primary600'} ellipsis>
511
+ {mainField ?? id}
512
+ </Typography>
513
+ )}
514
+ </Tooltip>
515
+ </Box>
516
+
517
+ {publicationState && (
518
+ <Status variant={statusColor} showBullet={false} size="S">
519
+ <Typography fontWeight="bold" textColor={`${statusColor}700`}>
520
+ {publicationStateTranslations[publicationState]}
521
+ </Typography>
522
+ </Status>
523
+ )}
524
+ </RelationItem>
525
+ );
526
+ };
527
+
528
+ ListItem.defaultProps = {
529
+ data: {},
530
+ };
531
+
532
+ ListItem.propTypes = {
533
+ data: PropTypes.shape({
534
+ ariaDescribedBy: PropTypes.string.isRequired,
535
+ canDrag: PropTypes.bool.isRequired,
536
+ disabled: PropTypes.bool.isRequired,
537
+ handleCancel: PropTypes.func,
538
+ handleDropItem: PropTypes.func,
539
+ handleGrabItem: PropTypes.func,
540
+ iconButtonAriaLabel: PropTypes.string.isRequired,
541
+ labelDisconnectRelation: PropTypes.string.isRequired,
542
+ name: PropTypes.string.isRequired,
543
+ onRelationDisconnect: PropTypes.func.isRequired,
544
+ publicationStateTranslations: PropTypes.shape({
545
+ draft: PropTypes.string.isRequired,
546
+ published: PropTypes.string.isRequired,
547
+ }).isRequired,
548
+ relations: PropTypes.arrayOf(
549
+ PropTypes.shape({
550
+ href: PropTypes.string,
551
+ id: PropTypes.number.isRequired,
552
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
553
+ mainField: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
554
+ })
555
+ ),
556
+ updatePositionOfRelation: PropTypes.func.isRequired,
557
+ }),
558
+ index: PropTypes.number.isRequired,
559
+ style: PropTypes.object.isRequired,
560
+ };
561
+
431
562
  export default RelationInput;
@@ -1,47 +1,161 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import styled from 'styled-components';
4
- import { Box } from '@strapi/design-system/Box';
5
- import { Flex } from '@strapi/design-system/Flex';
4
+ import { getEmptyImage } from 'react-dnd-html5-backend';
6
5
 
7
- const ChildrenWrapper = styled(Flex)`
6
+ import { Box, Flex, Stack, IconButton } from '@strapi/design-system';
7
+ import { Drag } from '@strapi/icons';
8
+
9
+ import { useDragAndDrop } from '../../../hooks/useDragAndDrop';
10
+
11
+ import { composeRefs, ItemTypes } from '../../../utils';
12
+
13
+ import { RELATION_GUTTER } from '../constants';
14
+
15
+ export const StackWrapper = styled(Stack)`
8
16
  width: 100%;
9
17
  /* Used to prevent endAction to be pushed out of container */
10
18
  min-width: 0;
11
19
  `;
12
20
 
13
- export const RelationItem = ({ children, disabled, endAction, style, ...props }) => {
21
+ export const ChildrenWrapper = styled(Flex)`
22
+ width: 100%;
23
+ /* Used to prevent endAction to be pushed out of container */
24
+ min-width: 0;
25
+ `;
26
+
27
+ export const RelationItem = ({
28
+ ariaDescribedBy,
29
+ children,
30
+ displayValue,
31
+ canDrag,
32
+ disabled,
33
+ endAction,
34
+ iconButtonAriaLabel,
35
+ style,
36
+ id,
37
+ index,
38
+ name,
39
+ onCancel,
40
+ onDropItem,
41
+ onGrabItem,
42
+ status,
43
+ updatePositionOfRelation,
44
+ ...props
45
+ }) => {
46
+ const [{ handlerId, isDragging, handleKeyDown }, relationRef, dropRef, dragRef, dragPreviewRef] =
47
+ useDragAndDrop(canDrag && !disabled, {
48
+ type: `${ItemTypes.RELATION}_${name}`,
49
+ index,
50
+ item: {
51
+ displayedValue: displayValue,
52
+ status,
53
+ id,
54
+ },
55
+ onGrabItem,
56
+ onDropItem,
57
+ onCancel,
58
+ onMoveItem: updatePositionOfRelation,
59
+ dropSensitivity: 'immediate',
60
+ });
61
+
62
+ const composedRefs = composeRefs(relationRef, dragRef);
63
+
64
+ useEffect(() => {
65
+ dragPreviewRef(getEmptyImage());
66
+ }, [dragPreviewRef]);
67
+
14
68
  return (
15
- <Box style={style} as="li">
16
- <Flex
17
- paddingTop={2}
18
- paddingBottom={2}
19
- paddingLeft={4}
20
- paddingRight={4}
21
- hasRadius
22
- borderSize={1}
23
- background={disabled ? 'neutral150' : 'neutral0'}
24
- borderColor="neutral200"
25
- justifyContent="space-between"
26
- {...props}
27
- >
28
- <ChildrenWrapper justifyContent="space-between">{children}</ChildrenWrapper>
29
- {endAction && <Box paddingLeft={4}>{endAction}</Box>}
30
- </Flex>
69
+ <Box
70
+ style={style}
71
+ as="li"
72
+ ref={dropRef}
73
+ aria-describedby={ariaDescribedBy}
74
+ cursor={canDrag ? 'all-scroll' : 'default'}
75
+ >
76
+ {isDragging ? (
77
+ <RelationItemPlaceholder />
78
+ ) : (
79
+ <Flex
80
+ paddingTop={2}
81
+ paddingBottom={2}
82
+ paddingLeft={canDrag ? 2 : 4}
83
+ paddingRight={4}
84
+ hasRadius
85
+ borderSize={1}
86
+ background={disabled ? 'neutral150' : 'neutral0'}
87
+ borderColor="neutral200"
88
+ justifyContent="space-between"
89
+ ref={canDrag ? composedRefs : undefined}
90
+ data-handler-id={handlerId}
91
+ {...props}
92
+ >
93
+ <StackWrapper spacing={1} horizontal>
94
+ {canDrag ? (
95
+ <IconButton
96
+ forwardedAs="div"
97
+ role="button"
98
+ tabIndex={0}
99
+ aria-label={iconButtonAriaLabel}
100
+ noBorder
101
+ onKeyDown={handleKeyDown}
102
+ >
103
+ <Drag />
104
+ </IconButton>
105
+ ) : null}
106
+ <ChildrenWrapper justifyContent="space-between">{children}</ChildrenWrapper>
107
+ </StackWrapper>
108
+ {endAction && <Box paddingLeft={4}>{endAction}</Box>}
109
+ </Flex>
110
+ )}
31
111
  </Box>
32
112
  );
33
113
  };
34
114
 
115
+ const RelationItemPlaceholder = () => (
116
+ <Box
117
+ paddingTop={2}
118
+ paddingBottom={2}
119
+ paddingLeft={4}
120
+ paddingRight={4}
121
+ hasRadius
122
+ borderStyle="dashed"
123
+ borderColor="primary600"
124
+ borderWidth="1px"
125
+ background="primary100"
126
+ height={`calc(100% - ${RELATION_GUTTER}px)`}
127
+ />
128
+ );
129
+
35
130
  RelationItem.defaultProps = {
131
+ ariaDescribedBy: '',
132
+ canDrag: false,
133
+ displayValue: '',
36
134
  disabled: false,
37
135
  endAction: undefined,
136
+ onCancel: undefined,
137
+ onDropItem: undefined,
138
+ onGrabItem: undefined,
38
139
  style: undefined,
140
+ status: undefined,
141
+ updatePositionOfRelation: undefined,
39
142
  };
40
143
 
41
144
  RelationItem.propTypes = {
145
+ ariaDescribedBy: PropTypes.string,
146
+ canDrag: PropTypes.bool,
42
147
  children: PropTypes.node.isRequired,
148
+ displayValue: PropTypes.string,
43
149
  disabled: PropTypes.bool,
44
150
  endAction: PropTypes.node,
151
+ iconButtonAriaLabel: PropTypes.string.isRequired,
152
+ id: PropTypes.number.isRequired,
153
+ index: PropTypes.number.isRequired,
154
+ name: PropTypes.string.isRequired,
155
+ onCancel: PropTypes.func,
156
+ onDropItem: PropTypes.func,
157
+ onGrabItem: PropTypes.func,
158
+ status: PropTypes.string,
45
159
  style: PropTypes.shape({
46
160
  height: PropTypes.number,
47
161
  left: PropTypes.number,
@@ -49,4 +163,5 @@ RelationItem.propTypes = {
49
163
  right: PropTypes.number,
50
164
  width: PropTypes.string,
51
165
  }),
166
+ updatePositionOfRelation: PropTypes.func,
52
167
  };
@@ -2,7 +2,6 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import styled from 'styled-components';
4
4
  import { Box } from '@strapi/design-system/Box';
5
- import { Stack } from '@strapi/design-system/Stack';
6
5
 
7
6
  const ShadowBox = styled(Box)`
8
7
  position: relative;
@@ -37,7 +36,7 @@ const ShadowBox = styled(Box)`
37
36
  export const RelationList = ({ children, overflow, ...props }) => {
38
37
  return (
39
38
  <ShadowBox overflowDirection={overflow} {...props}>
40
- <Stack spacing={1}>{children}</Stack>
39
+ {children}
41
40
  </ShadowBox>
42
41
  );
43
42
  };
@@ -1 +1,2 @@
1
1
  export const RELATION_ITEM_HEIGHT = 50;
2
+ export const RELATION_GUTTER = 4;
@@ -1 +1,2 @@
1
1
  export { default as RelationInput } from './RelationInput';
2
+ export * from './RelationInput';