@strapi/admin 4.4.0-rc.1 → 4.5.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/admin/src/StrapiApp.js +12 -4
  2. package/admin/src/components/Providers/index.js +10 -14
  3. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +11 -1
  4. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +11 -7
  5. package/admin/src/content-manager/components/DynamicTable/CellContent/index.js +6 -5
  6. package/admin/src/content-manager/components/DynamicTable/TableRows/index.js +5 -0
  7. package/admin/src/content-manager/components/DynamicTable/index.js +1 -1
  8. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +23 -17
  9. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +123 -24
  10. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +17 -1
  11. package/admin/src/content-manager/components/FieldTypeIcon/index.js +1 -31
  12. package/admin/src/content-manager/components/Inputs/index.js +22 -36
  13. package/admin/src/content-manager/components/RelationInput/RelationInput.js +364 -0
  14. package/admin/src/content-manager/components/{SelectWrapper → RelationInput/components}/Option.js +15 -25
  15. package/admin/src/content-manager/components/RelationInput/components/Relation.js +48 -0
  16. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +52 -0
  17. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +52 -0
  18. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  19. package/admin/src/content-manager/components/RelationInput/index.js +1 -0
  20. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +250 -0
  21. package/admin/src/content-manager/components/RelationInputDataManager/constants.js +8 -0
  22. package/admin/src/content-manager/components/RelationInputDataManager/index.js +1 -0
  23. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/connect.js +0 -1
  24. package/admin/src/content-manager/components/RelationInputDataManager/utils/getRelationLink.js +5 -0
  25. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/index.js +1 -0
  26. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeRelations.js +58 -0
  27. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/select.js +31 -1
  28. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +19 -2
  29. package/admin/src/content-manager/hooks/useFetchContentTypeLayout/utils/formatLayouts.js +7 -69
  30. package/admin/src/content-manager/hooks/useRelation/index.js +1 -0
  31. package/admin/src/content-manager/hooks/useRelation/useRelation.js +73 -0
  32. package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFields.js +4 -4
  33. package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +2 -7
  34. package/admin/src/content-manager/pages/EditSettingsView/index.js +22 -51
  35. package/admin/src/content-manager/pages/EditSettingsView/reducer.js +0 -25
  36. package/admin/src/content-manager/pages/EditView/Header/index.js +3 -90
  37. package/admin/src/content-manager/pages/EditView/Header/utils/index.js +0 -1
  38. package/admin/src/content-manager/pages/EditView/Header/utils/select.js +0 -2
  39. package/admin/src/content-manager/pages/EditView/index.js +93 -155
  40. package/admin/src/content-manager/pages/ListView/FieldPicker/index.js +0 -1
  41. package/admin/src/content-manager/pages/ListView/index.js +0 -1
  42. package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +0 -1
  43. package/admin/src/content-manager/utils/formatLayoutToApi.js +1 -3
  44. package/admin/src/core/apis/index.js +0 -1
  45. package/admin/src/hooks/index.js +0 -1
  46. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +197 -215
  47. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +1 -2
  48. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +0 -1
  49. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +36 -3
  50. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +11 -13
  51. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +2 -3
  52. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
  53. package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
  54. package/admin/src/permissions/defaultPermissions.js +6 -2
  55. package/admin/src/translations/ar.json +0 -1
  56. package/admin/src/translations/ca.json +0 -9
  57. package/admin/src/translations/cs.json +0 -2
  58. package/admin/src/translations/de.json +0 -11
  59. package/admin/src/translations/dk.json +0 -11
  60. package/admin/src/translations/en.json +0 -28
  61. package/admin/src/translations/es.json +0 -11
  62. package/admin/src/translations/fr.json +0 -11
  63. package/admin/src/translations/gu.json +0 -11
  64. package/admin/src/translations/hi.json +0 -11
  65. package/admin/src/translations/hu.json +0 -10
  66. package/admin/src/translations/id.json +0 -10
  67. package/admin/src/translations/it.json +0 -10
  68. package/admin/src/translations/ja.json +0 -11
  69. package/admin/src/translations/ko.json +0 -11
  70. package/admin/src/translations/ml.json +0 -11
  71. package/admin/src/translations/ms.json +0 -2
  72. package/admin/src/translations/nl.json +0 -11
  73. package/admin/src/translations/pl.json +0 -11
  74. package/admin/src/translations/pt-BR.json +0 -11
  75. package/admin/src/translations/pt.json +0 -1
  76. package/admin/src/translations/ru.json +0 -10
  77. package/admin/src/translations/sa.json +0 -11
  78. package/admin/src/translations/sk.json +0 -10
  79. package/admin/src/translations/sv.json +0 -9
  80. package/admin/src/translations/th.json +0 -2
  81. package/admin/src/translations/tr.json +0 -1
  82. package/admin/src/translations/uk.json +0 -2
  83. package/admin/src/translations/vi.json +0 -1
  84. package/admin/src/translations/zh-Hans.json +0 -12
  85. package/admin/src/translations/zh.json +0 -11
  86. package/build/7098.40dcd7bf.chunk.js +1 -0
  87. package/build/8851.e4ac62f2.chunk.js +158 -0
  88. package/build/{8773.c06c24c0.chunk.js → 9311.7cc03f29.chunk.js} +291 -108
  89. package/build/{Admin-authenticatedApp.99b4868f.chunk.js → Admin-authenticatedApp.e39f36c9.chunk.js} +3 -3
  90. package/build/{Admin_homePage.6d5e3236.chunk.js → Admin_homePage.118926e0.chunk.js} +1 -1
  91. package/build/{Admin_profilePage.da32abbc.chunk.js → Admin_profilePage.9d50ac44.chunk.js} +1 -1
  92. package/build/{Admin_settingsPage.98e2a62b.chunk.js → Admin_settingsPage.98a711e5.chunk.js} +16 -16
  93. package/build/admin-app.4f7618a9.chunk.js +112 -0
  94. package/build/admin-edit-roles-page.554ba3fa.chunk.js +1 -0
  95. package/build/api-tokens-create-page.4c262d6e.chunk.js +1 -0
  96. package/build/api-tokens-edit-page.10a9d368.chunk.js +1 -0
  97. package/build/api-tokens-list-page.442c9f3c.chunk.js +15 -0
  98. package/build/{ar-json.d4cb26d9.chunk.js → ar-json.3489463d.chunk.js} +1 -1
  99. package/build/{ca-json.d16c1d28.chunk.js → ca-json.a16899ae.chunk.js} +1 -1
  100. package/build/content-manager.7d57c9d1.chunk.js +1200 -0
  101. package/build/content-type-builder-list-view.8cc534e0.chunk.js +194 -0
  102. package/build/content-type-builder-translation-en-json.201bfb78.chunk.js +1 -0
  103. package/build/content-type-builder.684df7a4.chunk.js +142 -0
  104. package/build/{cs-json.c8f28ba8.chunk.js → cs-json.ce49da5c.chunk.js} +1 -1
  105. package/build/{de-json.a9b514dc.chunk.js → de-json.aa6026b3.chunk.js} +1 -1
  106. package/build/{dk-json.09e8d145.chunk.js → dk-json.fac2bcfb.chunk.js} +1 -1
  107. package/build/en-json.0c69c7d7.chunk.js +1 -0
  108. package/build/{es-json.3a9c7c09.chunk.js → es-json.d672e181.chunk.js} +1 -1
  109. package/build/{fr-json.4ed1fc2c.chunk.js → fr-json.71a16175.chunk.js} +1 -1
  110. package/build/{gu-json.d8311297.chunk.js → gu-json.ca345cd1.chunk.js} +1 -1
  111. package/build/{hi-json.0edb8d29.chunk.js → hi-json.50c7e6d4.chunk.js} +1 -1
  112. package/build/{hu-json.7855529a.chunk.js → hu-json.e0521dcc.chunk.js} +1 -1
  113. package/build/{id-json.df9618f2.chunk.js → id-json.4b1ff8d6.chunk.js} +1 -1
  114. package/build/index.html +1 -1
  115. package/build/{it-json.a21bf078.chunk.js → it-json.86bac220.chunk.js} +1 -1
  116. package/build/{ja-json.7b0d9067.chunk.js → ja-json.4e44e36b.chunk.js} +1 -1
  117. package/build/{ko-json.983c1f8f.chunk.js → ko-json.1003756e.chunk.js} +1 -1
  118. package/build/main.b47db1a3.js +9337 -0
  119. package/build/{ml-json.8dd021c8.chunk.js → ml-json.c7774425.chunk.js} +1 -1
  120. package/build/{ms-json.836ed013.chunk.js → ms-json.ed51e902.chunk.js} +1 -1
  121. package/build/{nl-json.29d2eb37.chunk.js → nl-json.f58ea235.chunk.js} +1 -1
  122. package/build/{pl-json.1f04f00c.chunk.js → pl-json.fed96aba.chunk.js} +1 -1
  123. package/build/{pt-BR-json.b4bc8efe.chunk.js → pt-BR-json.073799ab.chunk.js} +1 -1
  124. package/build/{pt-json.c23020ab.chunk.js → pt-json.3161ca22.chunk.js} +1 -1
  125. package/build/{ru-json.7ab40ccf.chunk.js → ru-json.7ad2cbbf.chunk.js} +1 -1
  126. package/build/{runtime~main.4ee06902.js → runtime~main.feeac6d3.js} +1 -1
  127. package/build/{sa-json.c5a9f4ea.chunk.js → sa-json.f0f704f0.chunk.js} +1 -1
  128. package/build/{sk-json.e4c24c4e.chunk.js → sk-json.a848961b.chunk.js} +1 -1
  129. package/build/sso-settings-page.445184e0.chunk.js +1 -0
  130. package/build/{sv-json.c3f471ae.chunk.js → sv-json.b038acbe.chunk.js} +1 -1
  131. package/build/{th-json.a59ffb32.chunk.js → th-json.72e8de3d.chunk.js} +1 -1
  132. package/build/{tr-json.276e59fe.chunk.js → tr-json.9c44ea0c.chunk.js} +1 -1
  133. package/build/{uk-json.5b5b9c27.chunk.js → uk-json.c4cd2e24.chunk.js} +1 -1
  134. package/build/{vi-json.bf3424be.chunk.js → vi-json.f7890025.chunk.js} +1 -1
  135. package/build/{webhook-edit-page.9e46fc3f.chunk.js → webhook-edit-page.d2ea3351.chunk.js} +1 -1
  136. package/build/{zh-Hans-json.9c99f8d4.chunk.js → zh-Hans-json.03d2bda1.chunk.js} +1 -1
  137. package/build/{zh-json.451a0271.chunk.js → zh-json.3d0cc664.chunk.js} +1 -1
  138. package/package.json +7 -8
  139. package/server/bootstrap.js +1 -19
  140. package/server/config/admin-actions.js +0 -20
  141. package/server/content-types/api-token.js +1 -25
  142. package/server/content-types/index.js +0 -1
  143. package/server/controllers/api-token.js +1 -24
  144. package/server/controllers/index.js +0 -1
  145. package/server/routes/api-tokens.js +0 -11
  146. package/server/routes/index.js +0 -2
  147. package/server/services/api-token.js +29 -310
  148. package/server/services/constants.js +0 -10
  149. package/server/services/permission/engine-hooks.js +82 -0
  150. package/server/services/permission/engine.js +226 -36
  151. package/server/services/permission.js +1 -4
  152. package/server/strategies/admin.js +1 -7
  153. package/server/strategies/api-token.js +11 -71
  154. package/server/validation/api-tokens.js +2 -12
  155. package/admin/src/content-manager/components/SelectMany/ListItem.js +0 -102
  156. package/admin/src/content-manager/components/SelectMany/index.js +0 -148
  157. package/admin/src/content-manager/components/SelectOne/SingleValue.js +0 -67
  158. package/admin/src/content-manager/components/SelectOne/index.js +0 -97
  159. package/admin/src/content-manager/components/SelectWrapper/Label.js +0 -60
  160. package/admin/src/content-manager/components/SelectWrapper/index.js +0 -356
  161. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFieldButton.js +0 -135
  162. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFields.js +0 -103
  163. package/admin/src/content-manager/pages/EditView/Header/utils/getDraftRelations.js +0 -62
  164. package/admin/src/contexts/ApiTokenPermissions/index.js +0 -24
  165. package/admin/src/core/apis/CustomFields.js +0 -80
  166. package/admin/src/hooks/useRegenerate/index.js +0 -34
  167. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +0 -56
  168. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +0 -41
  169. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +0 -72
  170. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +0 -30
  171. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +0 -150
  172. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +0 -37
  173. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +0 -254
  174. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +0 -77
  175. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +0 -85
  176. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +0 -40
  177. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +0 -68
  178. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +0 -13
  179. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +0 -72
  180. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +0 -16
  181. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +0 -5
  182. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +0 -36
  183. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +0 -63
  184. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +0 -19
  185. package/build/1669.d1b29c28.chunk.js +0 -1
  186. package/build/4318.7d167b58.chunk.js +0 -30
  187. package/build/524.40377968.chunk.js +0 -644
  188. package/build/7379.d246dd38.chunk.js +0 -1
  189. package/build/admin-app.a61d5c2e.chunk.js +0 -112
  190. package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +0 -1
  191. package/build/api-tokens-create-page.93dd0689.chunk.js +0 -1
  192. package/build/api-tokens-edit-page.b0adac81.chunk.js +0 -1
  193. package/build/api-tokens-list-page.bb36535f.chunk.js +0 -16
  194. package/build/content-manager.feb0d540.chunk.js +0 -1178
  195. package/build/content-type-builder-list-view.5b3cd768.chunk.js +0 -194
  196. package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +0 -1
  197. package/build/content-type-builder.a684b2e8.chunk.js +0 -145
  198. package/build/en-json.a9918c93.chunk.js +0 -1
  199. package/build/main.e4065f58.js +0 -9337
  200. package/build/sso-settings-page.9ceb0140.chunk.js +0 -1
  201. package/server/content-types/api-token-permission.js +0 -36
  202. package/server/controllers/content-api.js +0 -15
  203. package/server/routes/content-api.js +0 -20
@@ -5,13 +5,15 @@ import get from 'lodash/get';
5
5
  import omit from 'lodash/omit';
6
6
  import take from 'lodash/take';
7
7
  import isEqual from 'react-fast-compare';
8
- import { GenericInput, NotAllowedInput, useLibrary, useCustomFields } from '@strapi/helper-plugin';
9
- import { useContentTypeLayout } from '../../hooks';
10
- import { getFieldName } from '../../utils';
8
+ import { GenericInput, NotAllowedInput, useLibrary } from '@strapi/helper-plugin';
9
+
11
10
  import Wysiwyg from '../Wysiwyg';
12
11
  import InputJSON from '../InputJSON';
13
12
  import InputUID from '../InputUID';
14
- import SelectWrapper from '../SelectWrapper';
13
+ import { RelationInputDataManager } from '../RelationInputDataManager';
14
+
15
+ import { useContentTypeLayout } from '../../hooks';
16
+ import { getFieldName } from '../../utils';
15
17
 
16
18
  import {
17
19
  connect,
@@ -35,14 +37,14 @@ function Inputs({
35
37
  shouldNotRunValidations,
36
38
  queryInfos,
37
39
  value,
40
+ size,
38
41
  }) {
39
42
  const { fields } = useLibrary();
40
43
  const { formatMessage } = useIntl();
41
44
  const { contentType: currentContentTypeLayout } = useContentTypeLayout();
42
- const customFieldsRegistry = useCustomFields();
43
45
 
44
46
  const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
45
- const { type, customField: customFieldUid } = fieldSchema;
47
+ const type = fieldSchema.type;
46
48
  const error = get(formErrors, [keys], null);
47
49
 
48
50
  const fieldName = useMemo(() => {
@@ -192,19 +194,6 @@ function Inputs({
192
194
  return minutes % metadatas.step === 0 ? metadatas.step : step;
193
195
  }, [inputType, inputValue, metadatas.step, step]);
194
196
 
195
- // Memoize the component to avoid remounting it and losing state
196
- const CustomFieldInput = useMemo(() => {
197
- if (customFieldUid) {
198
- const customField = customFieldsRegistry.get(customFieldUid);
199
- const CustomFieldInput = React.lazy(customField.components.Input);
200
-
201
- return CustomFieldInput;
202
- }
203
-
204
- // Not a custom field, component won't be used
205
- return null;
206
- }, [customFieldUid, customFieldsRegistry]);
207
-
208
197
  if (visible === false) {
209
198
  return null;
210
199
  }
@@ -224,7 +213,7 @@ function Inputs({
224
213
 
225
214
  if (type === 'relation') {
226
215
  return (
227
- <SelectWrapper
216
+ <RelationInputDataManager
228
217
  {...metadatas}
229
218
  {...fieldSchema}
230
219
  description={
@@ -252,24 +241,13 @@ function Inputs({
252
241
  : null
253
242
  }
254
243
  queryInfos={queryInfos}
244
+ size={size}
255
245
  value={value}
256
246
  error={error && formatMessage(error)}
257
247
  />
258
248
  );
259
249
  }
260
250
 
261
- const customInputs = {
262
- json: InputJSON,
263
- uid: InputUID,
264
- media: fields.media,
265
- wysiwyg: Wysiwyg,
266
- ...fields,
267
- };
268
-
269
- if (customFieldUid) {
270
- customInputs[customFieldUid] = CustomFieldInput;
271
- }
272
-
273
251
  return (
274
252
  <GenericInput
275
253
  attribute={fieldSchema}
@@ -282,7 +260,13 @@ function Inputs({
282
260
  error={error}
283
261
  labelAction={labelAction}
284
262
  contentTypeUID={currentContentTypeLayout.uid}
285
- customInputs={customInputs}
263
+ customInputs={{
264
+ json: InputJSON,
265
+ uid: InputUID,
266
+ media: fields.media,
267
+ wysiwyg: Wysiwyg,
268
+ ...fields,
269
+ }}
286
270
  multiple={fieldSchema.multiple || false}
287
271
  name={keys}
288
272
  onChange={onChange}
@@ -290,7 +274,7 @@ function Inputs({
290
274
  placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null}
291
275
  required={fieldSchema.required || false}
292
276
  step={inputStep}
293
- type={customFieldUid || inputType}
277
+ type={inputType}
294
278
  // validations={validations}
295
279
  value={inputValue}
296
280
  withDefaultValue={false}
@@ -301,8 +285,9 @@ function Inputs({
301
285
  Inputs.defaultProps = {
302
286
  formErrors: {},
303
287
  labelAction: undefined,
304
- queryInfos: {},
288
+ size: undefined,
305
289
  value: null,
290
+ queryInfos: {},
306
291
  };
307
292
 
308
293
  Inputs.propTypes = {
@@ -315,13 +300,14 @@ Inputs.propTypes = {
315
300
  metadatas: PropTypes.object.isRequired,
316
301
  onChange: PropTypes.func.isRequired,
317
302
  readableFields: PropTypes.array.isRequired,
303
+ size: PropTypes.number,
318
304
  shouldNotRunValidations: PropTypes.bool.isRequired,
305
+ value: PropTypes.any,
319
306
  queryInfos: PropTypes.shape({
320
307
  containsKey: PropTypes.string,
321
308
  defaultParams: PropTypes.object,
322
309
  endPoint: PropTypes.string,
323
310
  }),
324
- value: PropTypes.any,
325
311
  };
326
312
 
327
313
  const Memoized = memo(Inputs, isEqual);
@@ -0,0 +1,364 @@
1
+ import React, { useRef, useState, useMemo, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled, { keyframes } from 'styled-components';
4
+ import { FixedSizeList as List } from 'react-window';
5
+
6
+ import { ReactSelect } from '@strapi/helper-plugin';
7
+ import { Badge } from '@strapi/design-system/Badge';
8
+ import { Box } from '@strapi/design-system/Box';
9
+ import { Link } from '@strapi/design-system/Link';
10
+ import { Icon } from '@strapi/design-system/Icon';
11
+ import { FieldLabel, FieldError, FieldHint, Field } from '@strapi/design-system/Field';
12
+ import { TextButton } from '@strapi/design-system/TextButton';
13
+ import { Typography } from '@strapi/design-system/Typography';
14
+ import { Tooltip } from '@strapi/design-system/Tooltip';
15
+
16
+ import Cross from '@strapi/icons/Cross';
17
+ import Refresh from '@strapi/icons/Refresh';
18
+ import Loader from '@strapi/icons/Loader';
19
+
20
+ import { Relation } from './components/Relation';
21
+ import { RelationItem } from './components/RelationItem';
22
+ import { RelationList } from './components/RelationList';
23
+ import { Option } from './components/Option';
24
+ import { RELATION_ITEM_HEIGHT } from './constants';
25
+
26
+ const LinkEllipsis = styled(Link)`
27
+ white-space: nowrap;
28
+ overflow: hidden;
29
+ text-overflow: ellipsis;
30
+ display: inherit;
31
+ `;
32
+
33
+ const BoxEllipsis = styled(Box)`
34
+ > span {
35
+ white-space: nowrap;
36
+ overflow: hidden;
37
+ text-overflow: ellipsis;
38
+ display: inherit;
39
+ }
40
+ `;
41
+
42
+ const rotation = keyframes`
43
+ from {
44
+ transform: rotate(0deg);
45
+ }
46
+ to {
47
+ transform: rotate(359deg);
48
+ }
49
+ `;
50
+
51
+ // TODO - to replace with loading prop on TextButton after DS release
52
+ const LoaderWrapper = styled(Box)`
53
+ animation: ${rotation} 2s infinite linear;
54
+ will-change: transform;
55
+ `;
56
+
57
+ const RelationInput = ({
58
+ description,
59
+ disabled,
60
+ error,
61
+ id,
62
+ name,
63
+ numberOfRelationsToDisplay,
64
+ label,
65
+ labelAction,
66
+ labelLoadMore,
67
+ loadingMessage,
68
+ onRelationAdd,
69
+ onRelationLoadMore,
70
+ onRelationRemove,
71
+ onSearchClose,
72
+ onSearchNextPage,
73
+ onSearchOpen,
74
+ onSearch,
75
+ placeholder,
76
+ publicationStateTranslations,
77
+ required,
78
+ relations: paginatedRelations,
79
+ searchResults,
80
+ size,
81
+ }) => {
82
+ const [value, setValue] = useState(null);
83
+ const listRef = useRef();
84
+ const outerListRef = useRef();
85
+ const [overflow, setOverflow] = useState('');
86
+
87
+ const relations = useMemo(() => paginatedRelations.data.pages.flat(), [paginatedRelations]);
88
+ const totalNumberOfRelations = relations.length ?? 0;
89
+
90
+ const dynamicListHeight = useMemo(
91
+ () =>
92
+ totalNumberOfRelations > numberOfRelationsToDisplay
93
+ ? Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT +
94
+ RELATION_ITEM_HEIGHT / 2
95
+ : Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT,
96
+ [totalNumberOfRelations, numberOfRelationsToDisplay]
97
+ );
98
+
99
+ const shouldDisplayLoadMoreButton =
100
+ (!!labelLoadMore && !disabled && paginatedRelations.hasNextPage) ||
101
+ paginatedRelations.isLoading;
102
+
103
+ const options = useMemo(
104
+ () =>
105
+ searchResults.data.pages.flat().map((result) => ({
106
+ ...result,
107
+ value: result.id,
108
+ label: result.mainField,
109
+ })),
110
+ [searchResults]
111
+ );
112
+
113
+ useEffect(() => {
114
+ if (totalNumberOfRelations <= numberOfRelationsToDisplay) {
115
+ return setOverflow('');
116
+ }
117
+
118
+ const handleNativeScroll = (e) => {
119
+ const parentScrollContainerHeight = e.target.parentNode.scrollHeight;
120
+ const maxScrollBottom = e.target.scrollHeight - e.target.scrollTop;
121
+
122
+ if (e.target.scrollTop === 0) {
123
+ return setOverflow('bottom');
124
+ }
125
+
126
+ if (maxScrollBottom === parentScrollContainerHeight) {
127
+ return setOverflow('top');
128
+ }
129
+
130
+ return setOverflow('top-bottom');
131
+ };
132
+
133
+ const outerListRefCurrent = outerListRef?.current;
134
+
135
+ if (!paginatedRelations.isLoading && relations.length > 0 && outerListRefCurrent) {
136
+ outerListRef.current.addEventListener('scroll', handleNativeScroll);
137
+ }
138
+
139
+ return () => {
140
+ if (outerListRefCurrent) {
141
+ outerListRefCurrent.removeEventListener('scroll', handleNativeScroll);
142
+ }
143
+ };
144
+ }, [paginatedRelations, relations, numberOfRelationsToDisplay, totalNumberOfRelations]);
145
+
146
+ return (
147
+ <Field error={error} name={name} hint={description} id={id}>
148
+ <Relation
149
+ totalNumberOfRelations={totalNumberOfRelations}
150
+ size={size}
151
+ search={
152
+ <>
153
+ <FieldLabel action={labelAction} required={required}>
154
+ {label}
155
+ </FieldLabel>
156
+ <ReactSelect
157
+ // position fixed doesn't update position on scroll
158
+ // react select doesn't update menu position on options change
159
+ menuPosition="absolute"
160
+ components={{ Option }}
161
+ options={options}
162
+ isDisabled={disabled}
163
+ isLoading={searchResults.isLoading}
164
+ error={error}
165
+ inputId={id}
166
+ isSearchable
167
+ isClear
168
+ loadingMessage={loadingMessage}
169
+ onChange={(relation) => {
170
+ setValue(null);
171
+ onRelationAdd(relation);
172
+
173
+ // scroll to the end of the list
174
+ if (relations.length > 0) {
175
+ setTimeout(() => {
176
+ listRef.current.scrollToItem(relations.length, 'end');
177
+ });
178
+ }
179
+ }}
180
+ onInputChange={(value) => {
181
+ setValue(value);
182
+ onSearch(value);
183
+ }}
184
+ onMenuClose={onSearchClose}
185
+ onMenuOpen={onSearchOpen}
186
+ onMenuScrollToBottom={() => {
187
+ if (searchResults.hasNextPage) {
188
+ onSearchNextPage();
189
+ }
190
+ }}
191
+ placeholder={placeholder}
192
+ name={name}
193
+ value={value}
194
+ />
195
+ </>
196
+ }
197
+ loadMore={
198
+ shouldDisplayLoadMoreButton && (
199
+ <TextButton
200
+ disabled={paginatedRelations.isLoading}
201
+ onClick={() => onRelationLoadMore()}
202
+ startIcon={
203
+ paginatedRelations.isLoading ? (
204
+ // TODO: To replace with loading prop on TextButton after DS release
205
+ <LoaderWrapper>
206
+ <Loader />
207
+ </LoaderWrapper>
208
+ ) : (
209
+ <Refresh />
210
+ )
211
+ }
212
+ >
213
+ {labelLoadMore}
214
+ </TextButton>
215
+ )
216
+ }
217
+ >
218
+ <RelationList overflow={overflow}>
219
+ <List
220
+ height={dynamicListHeight}
221
+ ref={listRef}
222
+ outerRef={outerListRef}
223
+ itemCount={totalNumberOfRelations}
224
+ itemSize={RELATION_ITEM_HEIGHT}
225
+ itemData={relations}
226
+ innerElementType="ol"
227
+ >
228
+ {({ data, index, style }) => {
229
+ const { publicationState, href, mainField, id } = data[index];
230
+ const badgeColor = publicationState === 'draft' ? 'secondary' : 'success';
231
+
232
+ return (
233
+ <RelationItem
234
+ disabled={disabled}
235
+ key={`relation-${name}-${id}`}
236
+ endAction={
237
+ <button
238
+ data-testid={`remove-relation-${id}`}
239
+ disabled={disabled}
240
+ type="button"
241
+ onClick={() => onRelationRemove(data[index])}
242
+ >
243
+ <Icon width="12px" as={Cross} />
244
+ </button>
245
+ }
246
+ style={style}
247
+ >
248
+ <BoxEllipsis minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
249
+ <Tooltip description={mainField ?? id}>
250
+ {href ? (
251
+ <LinkEllipsis to={href} disabled={disabled}>
252
+ {mainField ?? id}
253
+ </LinkEllipsis>
254
+ ) : (
255
+ <Typography textColor={disabled ? 'neutral600' : 'primary600'} ellipsis>
256
+ {mainField ?? id}
257
+ </Typography>
258
+ )}
259
+ </Tooltip>
260
+ </BoxEllipsis>
261
+
262
+ {publicationState && (
263
+ <Badge
264
+ borderSize={1}
265
+ borderColor={`${badgeColor}200`}
266
+ backgroundColor={`${badgeColor}100`}
267
+ textColor={`${badgeColor}700`}
268
+ shrink={0}
269
+ >
270
+ {publicationStateTranslations[publicationState]}
271
+ </Badge>
272
+ )}
273
+ </RelationItem>
274
+ );
275
+ }}
276
+ </List>
277
+ </RelationList>
278
+ {(description || error) && (
279
+ <Box paddingTop={2}>
280
+ <FieldHint />
281
+ <FieldError />
282
+ </Box>
283
+ )}
284
+ </Relation>
285
+ </Field>
286
+ );
287
+ };
288
+
289
+ const ReactQueryRelationResult = PropTypes.shape({
290
+ data: PropTypes.shape({
291
+ pages: PropTypes.arrayOf(
292
+ PropTypes.arrayOf(
293
+ PropTypes.shape({
294
+ href: PropTypes.string,
295
+ id: PropTypes.number.isRequired,
296
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
297
+ mainField: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
298
+ })
299
+ )
300
+ ),
301
+ }),
302
+ hasNextPage: PropTypes.bool,
303
+ isLoading: PropTypes.bool.isRequired,
304
+ isSuccess: PropTypes.bool.isRequired,
305
+ });
306
+
307
+ const ReactQuerySearchResult = PropTypes.shape({
308
+ data: PropTypes.shape({
309
+ pages: PropTypes.arrayOf(
310
+ PropTypes.arrayOf(
311
+ PropTypes.shape({
312
+ id: PropTypes.number.isRequired,
313
+ mainField: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
314
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
315
+ })
316
+ )
317
+ ),
318
+ }),
319
+ hasNextPage: PropTypes.bool,
320
+ isLoading: PropTypes.bool.isRequired,
321
+ isSuccess: PropTypes.bool.isRequired,
322
+ });
323
+
324
+ RelationInput.defaultProps = {
325
+ description: undefined,
326
+ disabled: false,
327
+ error: undefined,
328
+ labelAction: null,
329
+ labelLoadMore: null,
330
+ required: false,
331
+ relations: [],
332
+ searchResults: [],
333
+ };
334
+
335
+ RelationInput.propTypes = {
336
+ error: PropTypes.string,
337
+ description: PropTypes.string,
338
+ disabled: PropTypes.bool,
339
+ id: PropTypes.string.isRequired,
340
+ label: PropTypes.string.isRequired,
341
+ labelAction: PropTypes.element,
342
+ labelLoadMore: PropTypes.string,
343
+ loadingMessage: PropTypes.func.isRequired,
344
+ name: PropTypes.string.isRequired,
345
+ numberOfRelationsToDisplay: PropTypes.number.isRequired,
346
+ onRelationAdd: PropTypes.func.isRequired,
347
+ onRelationRemove: PropTypes.func.isRequired,
348
+ onRelationLoadMore: PropTypes.func.isRequired,
349
+ onSearch: PropTypes.func.isRequired,
350
+ onSearchNextPage: PropTypes.func.isRequired,
351
+ onSearchClose: PropTypes.func.isRequired,
352
+ onSearchOpen: PropTypes.func.isRequired,
353
+ placeholder: PropTypes.string.isRequired,
354
+ publicationStateTranslations: PropTypes.shape({
355
+ draft: PropTypes.string.isRequired,
356
+ published: PropTypes.string.isRequired,
357
+ }).isRequired,
358
+ required: PropTypes.bool,
359
+ searchResults: ReactQuerySearchResult,
360
+ size: PropTypes.number.isRequired,
361
+ relations: ReactQueryRelationResult,
362
+ };
363
+
364
+ export default RelationInput;
@@ -3,11 +3,12 @@ import styled from 'styled-components';
3
3
  import { components } from 'react-select';
4
4
  import { useIntl } from 'react-intl';
5
5
  import PropTypes from 'prop-types';
6
- import { get, has, isEmpty } from 'lodash';
6
+
7
+ import { pxToRem } from '@strapi/helper-plugin';
7
8
  import { Flex } from '@strapi/design-system/Flex';
8
9
  import { Typography } from '@strapi/design-system/Typography';
9
- import { pxToRem } from '@strapi/helper-plugin';
10
- import { getTrad } from '../../utils';
10
+
11
+ import { getTrad } from '../../../utils';
11
12
 
12
13
  const StyledBullet = styled.div`
13
14
  flex-shrink: 0;
@@ -17,16 +18,15 @@ const StyledBullet = styled.div`
17
18
  background-color: ${({ theme, isDraft }) =>
18
19
  theme.colors[isDraft ? 'secondary600' : 'success600']};
19
20
  border-radius: 50%;
20
- cursor: pointer;
21
21
  `;
22
22
 
23
- const Option = (props) => {
23
+ export const Option = (props) => {
24
24
  const { formatMessage } = useIntl();
25
25
  const Component = components.Option;
26
- const hasDraftAndPublish = has(get(props, 'data.value'), 'publishedAt');
26
+ const { publicationState, mainField, id } = props.data;
27
27
 
28
- if (hasDraftAndPublish) {
29
- const isDraft = isEmpty(get(props, 'data.value.publishedAt'));
28
+ if (publicationState) {
29
+ const isDraft = publicationState === 'draft';
30
30
  const draftMessage = {
31
31
  id: getTrad('components.Select.draft-info-title'),
32
32
  defaultMessage: 'State: Draft',
@@ -41,31 +41,21 @@ const Option = (props) => {
41
41
  <Component {...props}>
42
42
  <Flex>
43
43
  <StyledBullet title={title} isDraft={isDraft} />
44
- <Typography ellipsis>{props.label ?? '-'}</Typography>
44
+ <Typography ellipsis>{mainField ?? id}</Typography>
45
45
  </Flex>
46
46
  </Component>
47
47
  );
48
48
  }
49
49
 
50
- return <Component {...props}>{props.label ?? '-'}</Component>;
51
- };
52
-
53
- Option.defaultProps = {
54
- label: '',
50
+ return <Component {...props}>{mainField ?? id}</Component>;
55
51
  };
56
52
 
57
53
  Option.propTypes = {
58
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
59
54
  isFocused: PropTypes.bool.isRequired,
60
- selectProps: PropTypes.shape({
61
- hasDraftAndPublish: PropTypes.bool,
62
- mainField: PropTypes.shape({
63
- name: PropTypes.string.isRequired,
64
- schema: PropTypes.shape({
65
- type: PropTypes.string.isRequired,
66
- }).isRequired,
67
- }).isRequired,
55
+ data: PropTypes.shape({
56
+ id: PropTypes.number.isRequired,
57
+ isDraft: PropTypes.bool,
58
+ mainField: PropTypes.string,
59
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
68
60
  }).isRequired,
69
61
  };
70
-
71
- export default Option;
@@ -0,0 +1,48 @@
1
+ import PropTypes from 'prop-types';
2
+ import React from 'react';
3
+
4
+ import { Stack } from '@strapi/design-system/Stack';
5
+ import { Flex } from '@strapi/design-system/Flex';
6
+
7
+ export const Relation = ({
8
+ children,
9
+ loadMore,
10
+ search,
11
+ totalNumberOfRelations,
12
+ size,
13
+ ...props
14
+ }) => {
15
+ return (
16
+ <>
17
+ <Flex
18
+ paddingBottom={totalNumberOfRelations > 0 ? 3 : 0}
19
+ gap={2}
20
+ justifyContent="space-between"
21
+ alignItems="end"
22
+ wrap="wrap"
23
+ >
24
+ <Stack basis={size <= 6 ? '100%' : '70%'} spacing={1} {...props}>
25
+ {search}
26
+ </Stack>
27
+
28
+ {loadMore}
29
+ </Flex>
30
+
31
+ {children}
32
+ </>
33
+ );
34
+ };
35
+
36
+ Relation.defaultProps = {
37
+ search: undefined,
38
+ loadMore: undefined,
39
+ totalNumberOfRelations: 0,
40
+ };
41
+
42
+ Relation.propTypes = {
43
+ children: PropTypes.node.isRequired,
44
+ search: PropTypes.node,
45
+ loadMore: PropTypes.node,
46
+ size: PropTypes.number.isRequired,
47
+ totalNumberOfRelations: PropTypes.number,
48
+ };
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import { Box } from '@strapi/design-system/Box';
5
+ import { Flex } from '@strapi/design-system/Flex';
6
+
7
+ const ChildrenWrapper = styled(Flex)`
8
+ width: 100%;
9
+ /* Used to prevent endAction to be pushed out of container */
10
+ min-width: 0;
11
+ `;
12
+
13
+ export const RelationItem = ({ children, disabled, endAction, style, ...props }) => {
14
+ return (
15
+ <Box style={style} as="li">
16
+ <Flex
17
+ paddingTop={3}
18
+ paddingBottom={3}
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>
31
+ </Box>
32
+ );
33
+ };
34
+
35
+ RelationItem.defaultProps = {
36
+ disabled: false,
37
+ endAction: undefined,
38
+ style: undefined,
39
+ };
40
+
41
+ RelationItem.propTypes = {
42
+ children: PropTypes.node.isRequired,
43
+ disabled: PropTypes.bool,
44
+ endAction: PropTypes.node,
45
+ style: PropTypes.shape({
46
+ height: PropTypes.number,
47
+ left: PropTypes.number,
48
+ position: PropTypes.string,
49
+ right: PropTypes.number,
50
+ width: PropTypes.string,
51
+ }),
52
+ };