@strapi/admin 4.6.0-alpha.0 → 4.6.0-beta.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 (132) hide show
  1. package/admin/src/content-manager/components/ComponentInitializer/index.js +1 -7
  2. package/admin/src/content-manager/components/{RepeatableComponent/DragPreview.js → DragLayer/ComponentDragPreview.js} +25 -12
  3. package/admin/src/content-manager/components/DragLayer/RelationDragPreview.js +75 -0
  4. package/admin/src/content-manager/components/DragLayer/index.js +23 -7
  5. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +130 -84
  6. package/admin/src/content-manager/components/DynamicZone/index.js +99 -24
  7. package/admin/src/content-manager/components/DynamicZone/utils/select.js +9 -5
  8. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +76 -14
  9. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +23 -23
  10. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +24 -5
  11. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/recursivelyFindPathsBasedOnCondition.js +8 -1
  12. package/admin/src/content-manager/components/Inputs/index.js +5 -19
  13. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +4 -0
  14. package/admin/src/content-manager/components/RelationInput/RelationInput.js +203 -63
  15. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +134 -21
  16. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +1 -2
  17. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  18. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +132 -10
  19. package/admin/src/content-manager/components/RepeatableComponent/components/Accordion.js +77 -0
  20. package/admin/src/content-manager/components/RepeatableComponent/components/Component.js +262 -0
  21. package/admin/src/content-manager/components/RepeatableComponent/{DraggedItem → components}/Preview.js +0 -0
  22. package/admin/src/content-manager/components/RepeatableComponent/index.js +147 -87
  23. package/admin/src/content-manager/components/RepeatableComponent/utils/getComponentErrorKeys.js +1 -1
  24. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +1 -1
  25. package/admin/src/content-manager/components/Wysiwyg/Editor.js +1 -1
  26. package/admin/src/content-manager/hooks/index.js +2 -0
  27. package/admin/src/content-manager/hooks/useDragAndDrop.js +120 -0
  28. package/admin/src/content-manager/hooks/useKeyboardDragAndDrop.js +98 -0
  29. package/admin/src/content-manager/hooks/useLazyComponents/index.js +69 -0
  30. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/components/ErrorFallback.js +13 -0
  31. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/index.js +2 -1
  32. package/admin/src/content-manager/pages/EditView/GridRow/index.js +62 -0
  33. package/admin/src/content-manager/pages/EditView/index.js +74 -154
  34. package/admin/src/content-manager/pages/EditView/selectors.js +14 -0
  35. package/admin/src/content-manager/pages/EditView/utils/createAttributesLayout.js +11 -6
  36. package/admin/src/content-manager/pages/EditView/utils/getCustomFieldUidsFromLayout.js +18 -0
  37. package/admin/src/content-manager/pages/EditView/utils/index.js +1 -0
  38. package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +1 -1
  39. package/admin/src/content-manager/sharedReducers/crudReducer/actions.js +5 -0
  40. package/admin/src/content-manager/sharedReducers/crudReducer/constants.js +2 -0
  41. package/admin/src/content-manager/sharedReducers/crudReducer/reducer.js +7 -0
  42. package/admin/src/content-manager/utils/ItemTypes.js +1 -1
  43. package/admin/src/content-manager/utils/composeRefs.js +28 -0
  44. package/admin/src/content-manager/utils/getMaxTempKey.js +1 -1
  45. package/admin/src/content-manager/utils/index.js +7 -0
  46. package/admin/src/core/utils/axiosInstance.js +4 -2
  47. package/admin/src/hooks/index.js +1 -0
  48. package/admin/src/hooks/useFetchClient/index.js +23 -0
  49. package/admin/src/pages/HomePage/SocialLinks.js +4 -4
  50. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +23 -18
  51. package/admin/src/translations/en.json +6 -0
  52. package/admin/src/translations/ru.json +789 -489
  53. package/admin/src/utils/fetchClient.js +45 -0
  54. package/admin/src/utils/getFetchClient.js +10 -0
  55. package/build/4306.df40a798.chunk.js +98 -0
  56. package/build/{4318.daf31770.chunk.js → 4318.80bdf035.chunk.js} +2 -2
  57. package/build/5057.195a59ff.chunk.js +65 -0
  58. package/build/{805.a1894307.chunk.js → 805.e991a370.chunk.js} +6 -6
  59. package/build/{4986.3820d11d.chunk.js → 8176.b19bc128.chunk.js} +32 -32
  60. package/build/{1233.32d6888d.chunk.js → 8186.55910742.chunk.js} +94 -94
  61. package/build/{8633.8da5488a.chunk.js → 8633.59223842.chunk.js} +1 -1
  62. package/build/8881.c693411a.chunk.js +245 -0
  63. package/build/9161.4a0ab137.chunk.js +2119 -0
  64. package/build/9279.6290c87a.chunk.js +117 -0
  65. package/build/9707.a0cc4ad8.chunk.js +70 -0
  66. package/build/Admin-authenticatedApp.f9e74dc0.chunk.js +80 -0
  67. package/build/{Admin_homePage.b4db4df8.chunk.js → Admin_homePage.8945f71a.chunk.js} +5 -5
  68. package/build/{Admin_marketplace.fa51405b.chunk.js → Admin_marketplace.ed754a4a.chunk.js} +1 -1
  69. package/build/{Admin_pluginsPage.14d2840f.chunk.js → Admin_pluginsPage.3c872de7.chunk.js} +1 -1
  70. package/build/{Admin_profilePage.6c2c8398.chunk.js → Admin_profilePage.c07bdf08.chunk.js} +1 -1
  71. package/build/{Admin_settingsPage.5e740514.chunk.js → Admin_settingsPage.50a8765b.chunk.js} +5 -5
  72. package/build/admin-app.2861b6d2.chunk.js +112 -0
  73. package/build/{admin-edit-roles-page.c7c338b3.chunk.js → admin-edit-roles-page.f407538c.chunk.js} +1 -1
  74. package/build/{admin-edit-users.d254c128.chunk.js → admin-edit-users.85231e4c.chunk.js} +1 -1
  75. package/build/{admin-users.7c423e41.chunk.js → admin-users.a2707644.chunk.js} +2 -2
  76. package/build/api-tokens-create-page.dd4ddfcb.chunk.js +1 -0
  77. package/build/api-tokens-edit-page.821c5a6c.chunk.js +1 -0
  78. package/build/{api-tokens-list-page.fe994b6b.chunk.js → api-tokens-list-page.700e575f.chunk.js} +1 -1
  79. package/build/content-manager.ee948f75.chunk.js +1186 -0
  80. package/build/content-type-builder-list-view.4412efc3.chunk.js +201 -0
  81. package/build/content-type-builder.b132b5f4.chunk.js +145 -0
  82. package/build/email-settings-page.db0d98d1.chunk.js +15 -0
  83. package/build/{en-json.7dd57947.chunk.js → en-json.4a56dca7.chunk.js} +1 -1
  84. package/build/index.html +1 -1
  85. package/build/main.faac89ee.js +2025 -0
  86. package/build/ru-json.8830286f.chunk.js +1 -0
  87. package/build/{runtime~main.6e7d95b9.js → runtime~main.75a15b8e.js} +1 -1
  88. package/build/{sso-settings-page.eb30a02e.chunk.js → sso-settings-page.adb12ac3.chunk.js} +1 -1
  89. package/build/upload-settings.450cab1a.chunk.js +18 -0
  90. package/build/upload.e2034370.chunk.js +64 -0
  91. package/build/users-advanced-settings-page.0c0b8230.chunk.js +13 -0
  92. package/build/users-email-settings-page.3126ff8c.chunk.js +28 -0
  93. package/build/users-providers-settings-page.b7b602e2.chunk.js +33 -0
  94. package/build/users-roles-settings-page.ce5b582d.chunk.js +30 -0
  95. package/build/webhook-edit-page.1215a6b7.chunk.js +75 -0
  96. package/build/{webhook-list-page.42533b59.chunk.js → webhook-list-page.b87821f2.chunk.js} +1 -1
  97. package/ee/server/services/passport/provider-registry.js +1 -1
  98. package/package.json +15 -15
  99. package/utils/get-plugins-path.js +17 -3
  100. package/admin/src/content-manager/components/RepeatableComponent/AccordionGroupCustom/index.js +0 -122
  101. package/admin/src/content-manager/components/RepeatableComponent/AddFieldButton.js +0 -58
  102. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js +0 -72
  103. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/IconButtonCustoms.js +0 -32
  104. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +0 -322
  105. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/connect.js +0 -11
  106. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/index.js +0 -2
  107. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js +0 -30
  108. package/admin/src/content-manager/components/RepeatableComponent/utils/connect.js +0 -11
  109. package/admin/src/content-manager/components/RepeatableComponent/utils/select.js +0 -12
  110. package/admin/src/content-manager/hooks/__test__/usePrev.test.js +0 -26
  111. package/build/2438.afe24949.chunk.js +0 -2525
  112. package/build/2517.5cc235ba.chunk.js +0 -117
  113. package/build/4306.53359960.chunk.js +0 -98
  114. package/build/8881.bfdb6877.chunk.js +0 -245
  115. package/build/9707.932a3c12.chunk.js +0 -70
  116. package/build/Admin-authenticatedApp.cfc3b4c9.chunk.js +0 -80
  117. package/build/admin-app.ee1211cb.chunk.js +0 -112
  118. package/build/api-tokens-create-page.4ca2778d.chunk.js +0 -1
  119. package/build/api-tokens-edit-page.70a791c2.chunk.js +0 -1
  120. package/build/content-manager.794d3373.chunk.js +0 -1200
  121. package/build/content-type-builder-list-view.95012cf0.chunk.js +0 -201
  122. package/build/content-type-builder.95b9d6a2.chunk.js +0 -145
  123. package/build/email-settings-page.4bb3606f.chunk.js +0 -15
  124. package/build/main.a6470578.js +0 -2031
  125. package/build/ru-json.d7cfc2ff.chunk.js +0 -1
  126. package/build/upload-settings.3010911f.chunk.js +0 -18
  127. package/build/upload.9f19f2e8.chunk.js +0 -64
  128. package/build/users-advanced-settings-page.9df41d67.chunk.js +0 -13
  129. package/build/users-email-settings-page.56d82eaf.chunk.js +0 -28
  130. package/build/users-providers-settings-page.96bb7da0.chunk.js +0 -33
  131. package/build/users-roles-settings-page.445e5e16.chunk.js +0 -30
  132. package/build/webhook-edit-page.c5efc08b.chunk.js +0 -75
@@ -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,7 +21,8 @@ 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
28
  const LinkEllipsis = styled(Link)`
@@ -30,7 +32,7 @@ const LinkEllipsis = styled(Link)`
30
32
  display: inherit;
31
33
  `;
32
34
 
33
- const BoxEllipsis = styled(Box)`
35
+ export const BoxEllipsis = styled(Box)`
34
36
  > span {
35
37
  white-space: nowrap;
36
38
  overflow: hidden;
@@ -39,7 +41,7 @@ const BoxEllipsis = styled(Box)`
39
41
  }
40
42
  `;
41
43
 
42
- const DisconnectButton = styled.button`
44
+ export const DisconnectButton = styled.button`
43
45
  svg path {
44
46
  fill: ${({ theme }) => theme.colors.neutral500};
45
47
  }
@@ -51,9 +53,11 @@ const DisconnectButton = styled.button`
51
53
  `;
52
54
 
53
55
  const RelationInput = ({
56
+ canReorder,
54
57
  description,
55
58
  disabled,
56
59
  error,
60
+ iconButtonAriaLabel,
57
61
  id,
58
62
  name,
59
63
  numberOfRelationsToDisplay,
@@ -61,11 +65,17 @@ const RelationInput = ({
61
65
  labelAction,
62
66
  labelLoadMore,
63
67
  labelDisconnectRelation,
68
+ listAriaDescription,
69
+ liveText,
64
70
  loadingMessage,
71
+ onCancel,
72
+ onDropItem,
73
+ onGrabItem,
65
74
  noRelationsMessage,
66
75
  onRelationConnect,
67
76
  onRelationLoadMore,
68
77
  onRelationDisconnect,
78
+ onRelationReorder,
69
79
  onSearchNextPage,
70
80
  onSearch,
71
81
  placeholder,
@@ -76,9 +86,10 @@ const RelationInput = ({
76
86
  size,
77
87
  }) => {
78
88
  const [value, setValue] = useState(null);
89
+ const [overflow, setOverflow] = useState('');
90
+
79
91
  const listRef = useRef();
80
92
  const outerListRef = useRef();
81
- const [overflow, setOverflow] = useState('');
82
93
 
83
94
  const { data } = searchResults;
84
95
 
@@ -88,9 +99,11 @@ const RelationInput = ({
88
99
  const dynamicListHeight = useMemo(
89
100
  () =>
90
101
  totalNumberOfRelations > numberOfRelationsToDisplay
91
- ? Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT +
102
+ ? Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) *
103
+ (RELATION_ITEM_HEIGHT + RELATION_GUTTER) +
92
104
  RELATION_ITEM_HEIGHT / 2
93
- : Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT,
105
+ : Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) *
106
+ (RELATION_ITEM_HEIGHT + RELATION_GUTTER),
94
107
  [totalNumberOfRelations, numberOfRelationsToDisplay]
95
108
  );
96
109
 
@@ -144,6 +157,9 @@ const RelationInput = ({
144
157
  };
145
158
  }, [paginatedRelations, relations, numberOfRelationsToDisplay, totalNumberOfRelations]);
146
159
 
160
+ /**
161
+ * --- ReactSelect Workaround START ---
162
+ */
147
163
  /**
148
164
  * This code is being isolated because it's a hack to fix a placement bug in
149
165
  * `react-select` where when the options prop is updated the position of the
@@ -199,12 +215,28 @@ const RelationInput = ({
199
215
  const handleMenuClose = () => {
200
216
  setIsMenuOpen(false);
201
217
  };
218
+ /**
219
+ * --- ReactSelect Workaround END ---
220
+ */
202
221
 
203
222
  const handleMenuOpen = () => {
204
223
  setIsMenuOpen(true);
205
224
  onSearch();
206
225
  };
207
226
 
227
+ /**
228
+ *
229
+ * @param {number} newIndex
230
+ * @param {number} currentIndex
231
+ *
232
+ * @returns {void}
233
+ */
234
+ const handleUpdatePositionOfRelation = (newIndex, currentIndex) => {
235
+ if (onRelationReorder && newIndex >= 0 && newIndex < relations.length) {
236
+ onRelationReorder(currentIndex, newIndex);
237
+ }
238
+ };
239
+
208
240
  const previewRelationsLength = usePrev(relations.length);
209
241
  /**
210
242
  * @type {React.MutableRefObject<'onChange' | 'loadMore'>}
@@ -228,8 +260,12 @@ const RelationInput = ({
228
260
  ) {
229
261
  listRef.current.scrollToItem(0, 'start');
230
262
  }
263
+
264
+ updatedRelationsWith.current = undefined;
231
265
  }, [previewRelationsLength, relations]);
232
266
 
267
+ const ariaDescriptionId = `${name}-item-instructions`;
268
+
233
269
  return (
234
270
  <Field error={error} name={name} hint={description} id={id}>
235
271
  <Relation
@@ -291,63 +327,40 @@ const RelationInput = ({
291
327
  )
292
328
  }
293
329
  >
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>
330
+ {relations.length > 0 && (
331
+ <RelationList overflow={overflow}>
332
+ <VisuallyHidden id={ariaDescriptionId}>{listAriaDescription}</VisuallyHidden>
333
+ <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>
334
+ <List
335
+ height={dynamicListHeight}
336
+ ref={listRef}
337
+ outerRef={outerListRef}
338
+ itemCount={totalNumberOfRelations}
339
+ itemSize={RELATION_ITEM_HEIGHT + RELATION_GUTTER}
340
+ itemData={{
341
+ name,
342
+ ariaDescribedBy: ariaDescriptionId,
343
+ canDrag: canReorder,
344
+ disabled,
345
+ handleCancel: onCancel,
346
+ handleDropItem: onDropItem,
347
+ handleGrabItem: onGrabItem,
348
+ iconButtonAriaLabel,
349
+ labelDisconnectRelation,
350
+ onRelationDisconnect,
351
+ publicationStateTranslations,
352
+ relations,
353
+ updatePositionOfRelation: handleUpdatePositionOfRelation,
354
+ }}
355
+ itemKey={(index, { relations: relationsItems }) =>
356
+ `${relationsItems[index].mainField}_${relationsItems[index].id}`
357
+ }
358
+ innerElementType="ol"
359
+ >
360
+ {ListItem}
361
+ </List>
362
+ </RelationList>
363
+ )}
351
364
  {(description || error) && (
352
365
  <Box paddingTop={2}>
353
366
  <FieldHint />
@@ -389,11 +402,16 @@ const SearchResults = PropTypes.shape({
389
402
  });
390
403
 
391
404
  RelationInput.defaultProps = {
405
+ canReorder: false,
392
406
  description: undefined,
393
407
  disabled: false,
394
408
  error: undefined,
395
409
  labelAction: null,
396
410
  labelLoadMore: null,
411
+ liveText: undefined,
412
+ onCancel: undefined,
413
+ onDropItem: undefined,
414
+ onGrabItem: undefined,
397
415
  required: false,
398
416
  relations: { data: [] },
399
417
  searchResults: { data: [] },
@@ -401,20 +419,28 @@ RelationInput.defaultProps = {
401
419
 
402
420
  RelationInput.propTypes = {
403
421
  error: PropTypes.string,
422
+ canReorder: PropTypes.bool,
404
423
  description: PropTypes.string,
405
424
  disabled: PropTypes.bool,
425
+ iconButtonAriaLabel: PropTypes.string.isRequired,
406
426
  id: PropTypes.string.isRequired,
407
427
  label: PropTypes.string.isRequired,
408
428
  labelAction: PropTypes.element,
409
429
  labelLoadMore: PropTypes.string,
410
430
  labelDisconnectRelation: PropTypes.string.isRequired,
431
+ listAriaDescription: PropTypes.string.isRequired,
432
+ liveText: PropTypes.string,
411
433
  loadingMessage: PropTypes.string.isRequired,
412
434
  name: PropTypes.string.isRequired,
413
435
  noRelationsMessage: PropTypes.string.isRequired,
414
436
  numberOfRelationsToDisplay: PropTypes.number.isRequired,
437
+ onCancel: PropTypes.func,
438
+ onDropItem: PropTypes.func,
439
+ onGrabItem: PropTypes.func,
415
440
  onRelationConnect: PropTypes.func.isRequired,
416
441
  onRelationDisconnect: PropTypes.func.isRequired,
417
442
  onRelationLoadMore: PropTypes.func.isRequired,
443
+ onRelationReorder: PropTypes.func.isRequired,
418
444
  onSearch: PropTypes.func.isRequired,
419
445
  onSearchNextPage: PropTypes.func.isRequired,
420
446
  placeholder: PropTypes.string.isRequired,
@@ -428,4 +454,118 @@ RelationInput.propTypes = {
428
454
  relations: RelationsResult,
429
455
  };
430
456
 
457
+ /**
458
+ * This is in a seperate component to enforce passing all the props the component requires to react-window
459
+ * to ensure drag & drop correctly works.
460
+ */
461
+ const ListItem = ({ data, index, style }) => {
462
+ const {
463
+ ariaDescribedBy,
464
+ canDrag,
465
+ disabled,
466
+ handleCancel,
467
+ handleDropItem,
468
+ handleGrabItem,
469
+ iconButtonAriaLabel,
470
+ name,
471
+ labelDisconnectRelation,
472
+ onRelationDisconnect,
473
+ publicationStateTranslations,
474
+ relations,
475
+ updatePositionOfRelation,
476
+ } = data;
477
+ const { publicationState, href, mainField, id } = relations[index];
478
+ const statusColor = publicationState === 'draft' ? 'secondary' : 'success';
479
+
480
+ return (
481
+ <RelationItem
482
+ ariaDescribedBy={ariaDescribedBy}
483
+ canDrag={canDrag}
484
+ disabled={disabled}
485
+ displayValue={mainField ?? id}
486
+ iconButtonAriaLabel={iconButtonAriaLabel}
487
+ id={id}
488
+ index={index}
489
+ name={name}
490
+ endAction={
491
+ <DisconnectButton
492
+ data-testid={`remove-relation-${id}`}
493
+ disabled={disabled}
494
+ type="button"
495
+ onClick={() => onRelationDisconnect(relations[index])}
496
+ aria-label={labelDisconnectRelation}
497
+ >
498
+ <Icon width="12px" as={Cross} />
499
+ </DisconnectButton>
500
+ }
501
+ onCancel={handleCancel}
502
+ onDropItem={handleDropItem}
503
+ onGrabItem={handleGrabItem}
504
+ status={publicationState || undefined}
505
+ style={{
506
+ ...style,
507
+ bottom: style.bottom ?? 0 + RELATION_GUTTER,
508
+ height: style.height ?? 0 - RELATION_GUTTER,
509
+ }}
510
+ updatePositionOfRelation={updatePositionOfRelation}
511
+ >
512
+ <BoxEllipsis minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
513
+ <Tooltip description={mainField ?? `${id}`}>
514
+ {href ? (
515
+ <LinkEllipsis to={href} disabled={disabled}>
516
+ {mainField ?? id}
517
+ </LinkEllipsis>
518
+ ) : (
519
+ <Typography textColor={disabled ? 'neutral600' : 'primary600'} ellipsis>
520
+ {mainField ?? id}
521
+ </Typography>
522
+ )}
523
+ </Tooltip>
524
+ </BoxEllipsis>
525
+
526
+ {publicationState && (
527
+ <Status variant={statusColor} showBullet={false} size="S">
528
+ <Typography fontWeight="bold" textColor={`${statusColor}700`}>
529
+ {publicationStateTranslations[publicationState]}
530
+ </Typography>
531
+ </Status>
532
+ )}
533
+ </RelationItem>
534
+ );
535
+ };
536
+
537
+ ListItem.defaultProps = {
538
+ data: {},
539
+ };
540
+
541
+ ListItem.propTypes = {
542
+ data: PropTypes.shape({
543
+ ariaDescribedBy: PropTypes.string.isRequired,
544
+ canDrag: PropTypes.bool.isRequired,
545
+ disabled: PropTypes.bool.isRequired,
546
+ handleCancel: PropTypes.func,
547
+ handleDropItem: PropTypes.func,
548
+ handleGrabItem: PropTypes.func,
549
+ iconButtonAriaLabel: PropTypes.string.isRequired,
550
+ labelDisconnectRelation: PropTypes.string.isRequired,
551
+ name: PropTypes.string.isRequired,
552
+ onRelationDisconnect: PropTypes.func.isRequired,
553
+ publicationStateTranslations: PropTypes.shape({
554
+ draft: PropTypes.string.isRequired,
555
+ published: PropTypes.string.isRequired,
556
+ }).isRequired,
557
+ relations: PropTypes.arrayOf(
558
+ PropTypes.shape({
559
+ href: PropTypes.string,
560
+ id: PropTypes.number.isRequired,
561
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
562
+ mainField: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
563
+ })
564
+ ),
565
+ updatePositionOfRelation: PropTypes.func.isRequired,
566
+ }),
567
+ index: PropTypes.number.isRequired,
568
+ style: PropTypes.object.isRequired,
569
+ };
570
+
431
571
  export default RelationInput;
@@ -1,47 +1,159 @@
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
+ },
54
+ onGrabItem,
55
+ onDropItem,
56
+ onCancel,
57
+ onMoveItem: updatePositionOfRelation,
58
+ });
59
+
60
+ const composedRefs = composeRefs(relationRef, dragRef);
61
+
62
+ useEffect(() => {
63
+ dragPreviewRef(getEmptyImage());
64
+ }, [dragPreviewRef]);
65
+
14
66
  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>
67
+ <Box
68
+ style={style}
69
+ as="li"
70
+ ref={dropRef}
71
+ aria-describedby={ariaDescribedBy}
72
+ cursor={canDrag ? 'all-scroll' : 'default'}
73
+ >
74
+ {isDragging ? (
75
+ <RelationItemPlaceholder />
76
+ ) : (
77
+ <Flex
78
+ paddingTop={2}
79
+ paddingBottom={2}
80
+ paddingLeft={canDrag ? 2 : 4}
81
+ paddingRight={4}
82
+ hasRadius
83
+ borderSize={1}
84
+ background={disabled ? 'neutral150' : 'neutral0'}
85
+ borderColor="neutral200"
86
+ justifyContent="space-between"
87
+ ref={canDrag ? composedRefs : undefined}
88
+ data-handler-id={handlerId}
89
+ {...props}
90
+ >
91
+ <StackWrapper spacing={1} horizontal>
92
+ {canDrag ? (
93
+ <IconButton
94
+ forwardedAs="div"
95
+ role="button"
96
+ tabIndex={0}
97
+ aria-label={iconButtonAriaLabel}
98
+ noBorder
99
+ onKeyDown={handleKeyDown}
100
+ >
101
+ <Drag />
102
+ </IconButton>
103
+ ) : null}
104
+ <ChildrenWrapper justifyContent="space-between">{children}</ChildrenWrapper>
105
+ </StackWrapper>
106
+ {endAction && <Box paddingLeft={4}>{endAction}</Box>}
107
+ </Flex>
108
+ )}
31
109
  </Box>
32
110
  );
33
111
  };
34
112
 
113
+ const RelationItemPlaceholder = () => (
114
+ <Box
115
+ paddingTop={2}
116
+ paddingBottom={2}
117
+ paddingLeft={4}
118
+ paddingRight={4}
119
+ hasRadius
120
+ borderStyle="dashed"
121
+ borderColor="primary600"
122
+ borderWidth="1px"
123
+ background="primary100"
124
+ height={`calc(100% - ${RELATION_GUTTER}px)`}
125
+ />
126
+ );
127
+
35
128
  RelationItem.defaultProps = {
129
+ ariaDescribedBy: '',
130
+ canDrag: false,
131
+ displayValue: '',
36
132
  disabled: false,
37
133
  endAction: undefined,
134
+ onCancel: undefined,
135
+ onDropItem: undefined,
136
+ onGrabItem: undefined,
38
137
  style: undefined,
138
+ status: undefined,
139
+ updatePositionOfRelation: undefined,
39
140
  };
40
141
 
41
142
  RelationItem.propTypes = {
143
+ ariaDescribedBy: PropTypes.string,
144
+ canDrag: PropTypes.bool,
42
145
  children: PropTypes.node.isRequired,
146
+ displayValue: PropTypes.string,
43
147
  disabled: PropTypes.bool,
44
148
  endAction: PropTypes.node,
149
+ iconButtonAriaLabel: PropTypes.string.isRequired,
150
+ id: PropTypes.number.isRequired,
151
+ index: PropTypes.number.isRequired,
152
+ name: PropTypes.string.isRequired,
153
+ onCancel: PropTypes.func,
154
+ onDropItem: PropTypes.func,
155
+ onGrabItem: PropTypes.func,
156
+ status: PropTypes.string,
45
157
  style: PropTypes.shape({
46
158
  height: PropTypes.number,
47
159
  left: PropTypes.number,
@@ -49,4 +161,5 @@ RelationItem.propTypes = {
49
161
  right: PropTypes.number,
50
162
  width: PropTypes.string,
51
163
  }),
164
+ updatePositionOfRelation: PropTypes.func,
52
165
  };
@@ -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;