@strapi/admin 4.5.4 → 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 (90) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +2 -13
  2. package/admin/src/content-manager/components/ComponentInitializer/index.js +1 -7
  3. package/admin/src/content-manager/components/{RepeatableComponent/DragPreview.js → DragLayer/ComponentDragPreview.js} +25 -12
  4. package/admin/src/content-manager/components/DragLayer/RelationDragPreview.js +75 -0
  5. package/admin/src/content-manager/components/DragLayer/index.js +23 -7
  6. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +130 -84
  7. package/admin/src/content-manager/components/DynamicZone/index.js +99 -24
  8. package/admin/src/content-manager/components/DynamicZone/utils/select.js +9 -5
  9. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +37 -11
  10. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +18 -22
  11. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +24 -5
  12. package/admin/src/content-manager/components/RelationInput/RelationInput.js +209 -64
  13. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +134 -21
  14. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +1 -2
  15. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  16. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +131 -9
  17. package/admin/src/content-manager/components/RepeatableComponent/components/Accordion.js +77 -0
  18. package/admin/src/content-manager/components/RepeatableComponent/components/Component.js +262 -0
  19. package/admin/src/content-manager/components/RepeatableComponent/{DraggedItem → components}/Preview.js +0 -0
  20. package/admin/src/content-manager/components/RepeatableComponent/index.js +147 -87
  21. package/admin/src/content-manager/components/RepeatableComponent/utils/getComponentErrorKeys.js +1 -1
  22. package/admin/src/content-manager/hooks/index.js +2 -0
  23. package/admin/src/content-manager/hooks/useDragAndDrop.js +120 -0
  24. package/admin/src/content-manager/hooks/useKeyboardDragAndDrop.js +98 -0
  25. package/admin/src/content-manager/utils/ItemTypes.js +1 -1
  26. package/admin/src/content-manager/utils/composeRefs.js +28 -0
  27. package/admin/src/content-manager/utils/getMaxTempKey.js +1 -1
  28. package/admin/src/content-manager/utils/index.js +7 -0
  29. package/admin/src/pages/App/index.js +13 -20
  30. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +3 -2
  31. package/admin/src/translations/en.json +6 -0
  32. package/admin/src/translations/tr.json +5 -485
  33. package/admin/src/utils/index.js +0 -1
  34. package/build/4318.80bdf035.chunk.js +30 -0
  35. package/build/{8176.e929d326.chunk.js → 8176.b19bc128.chunk.js} +32 -32
  36. package/build/{1233.802422fa.chunk.js → 8186.55910742.chunk.js} +96 -96
  37. package/build/{8633.43ec9042.chunk.js → 8633.59223842.chunk.js} +1 -1
  38. package/build/Admin-authenticatedApp.f9e74dc0.chunk.js +80 -0
  39. package/build/{Admin_profilePage.60ab80bb.chunk.js → Admin_profilePage.c07bdf08.chunk.js} +1 -1
  40. package/build/{Admin_settingsPage.6ef8acc9.chunk.js → Admin_settingsPage.50a8765b.chunk.js} +1 -1
  41. package/build/admin-app.2861b6d2.chunk.js +112 -0
  42. package/build/admin-edit-users.85231e4c.chunk.js +10 -0
  43. package/build/{admin-users.e64fb0f1.chunk.js → admin-users.a2707644.chunk.js} +2 -2
  44. package/build/api-tokens-create-page.dd4ddfcb.chunk.js +1 -0
  45. package/build/api-tokens-edit-page.821c5a6c.chunk.js +1 -0
  46. package/build/content-manager.ee948f75.chunk.js +1186 -0
  47. package/build/content-type-builder-translation-tr-json.2e52bc60.chunk.js +1 -0
  48. package/build/email-settings-page.db0d98d1.chunk.js +15 -0
  49. package/build/email-translation-tr-json.87f2feb3.chunk.js +1 -0
  50. package/build/{en-json.7dd57947.chunk.js → en-json.4a56dca7.chunk.js} +1 -1
  51. package/build/index.html +1 -1
  52. package/build/main.faac89ee.js +2025 -0
  53. package/build/runtime~main.75a15b8e.js +2 -0
  54. package/build/sso-settings-page.adb12ac3.chunk.js +1 -0
  55. package/build/tr-json.9c44ea0c.chunk.js +1 -0
  56. package/build/{upload.74540aab.chunk.js → upload.e2034370.chunk.js} +3 -3
  57. package/build/users-permissions-translation-tr-json.cdc49a3c.chunk.js +1 -0
  58. package/package.json +9 -9
  59. package/server/controllers/admin.js +0 -2
  60. package/server/routes/admin.js +1 -1
  61. package/server/services/metrics.js +2 -5
  62. package/admin/src/content-manager/components/RepeatableComponent/AccordionGroupCustom/index.js +0 -122
  63. package/admin/src/content-manager/components/RepeatableComponent/AddFieldButton.js +0 -58
  64. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/DraggingSibling.js +0 -72
  65. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/IconButtonCustoms.js +0 -32
  66. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +0 -326
  67. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/connect.js +0 -11
  68. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/index.js +0 -2
  69. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js +0 -30
  70. package/admin/src/content-manager/components/RepeatableComponent/utils/connect.js +0 -11
  71. package/admin/src/content-manager/components/RepeatableComponent/utils/select.js +0 -12
  72. package/admin/src/content-manager/hooks/__test__/usePrev.test.js +0 -26
  73. package/admin/src/utils/uniqueAdminHash.js +0 -22
  74. package/build/4318.9283c350.chunk.js +0 -30
  75. package/build/Admin-authenticatedApp.0da578b8.chunk.js +0 -80
  76. package/build/admin-app.a3277e72.chunk.js +0 -112
  77. package/build/admin-edit-users.5547b126.chunk.js +0 -10
  78. package/build/api-tokens-create-page.93dd0689.chunk.js +0 -1
  79. package/build/api-tokens-edit-page.b0adac81.chunk.js +0 -1
  80. package/build/content-manager.f9630c3b.chunk.js +0 -1197
  81. package/build/content-type-builder-translation-tr-json.949e22eb.chunk.js +0 -1
  82. package/build/email-settings-page.c6e62f6b.chunk.js +0 -15
  83. package/build/email-translation-tr-json.8aa034bb.chunk.js +0 -1
  84. package/build/i18n-translation-tr-json.34ca9d61.chunk.js +0 -1
  85. package/build/main.71f24343.js +0 -2034
  86. package/build/runtime~main.1115f82b.js +0 -2
  87. package/build/sso-settings-page.feed2f45.chunk.js +0 -1
  88. package/build/tr-json.eac8bd79.chunk.js +0 -1
  89. package/build/upload-translation-tr-json.b173223a.chunk.js +0 -1
  90. package/build/users-permissions-translation-tr-json.9bebc250.chunk.js +0 -1
@@ -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,21 +21,27 @@ 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)`
27
- display: block;
29
+ white-space: nowrap;
30
+ overflow: hidden;
31
+ text-overflow: ellipsis;
32
+ display: inherit;
33
+ `;
28
34
 
35
+ export const BoxEllipsis = styled(Box)`
29
36
  > span {
30
37
  white-space: nowrap;
31
38
  overflow: hidden;
32
39
  text-overflow: ellipsis;
33
- display: block;
40
+ display: inherit;
34
41
  }
35
42
  `;
36
43
 
37
- const DisconnectButton = styled.button`
44
+ export const DisconnectButton = styled.button`
38
45
  svg path {
39
46
  fill: ${({ theme }) => theme.colors.neutral500};
40
47
  }
@@ -46,9 +53,11 @@ const DisconnectButton = styled.button`
46
53
  `;
47
54
 
48
55
  const RelationInput = ({
56
+ canReorder,
49
57
  description,
50
58
  disabled,
51
59
  error,
60
+ iconButtonAriaLabel,
52
61
  id,
53
62
  name,
54
63
  numberOfRelationsToDisplay,
@@ -56,11 +65,17 @@ const RelationInput = ({
56
65
  labelAction,
57
66
  labelLoadMore,
58
67
  labelDisconnectRelation,
68
+ listAriaDescription,
69
+ liveText,
59
70
  loadingMessage,
71
+ onCancel,
72
+ onDropItem,
73
+ onGrabItem,
60
74
  noRelationsMessage,
61
75
  onRelationConnect,
62
76
  onRelationLoadMore,
63
77
  onRelationDisconnect,
78
+ onRelationReorder,
64
79
  onSearchNextPage,
65
80
  onSearch,
66
81
  placeholder,
@@ -71,9 +86,10 @@ const RelationInput = ({
71
86
  size,
72
87
  }) => {
73
88
  const [value, setValue] = useState(null);
89
+ const [overflow, setOverflow] = useState('');
90
+
74
91
  const listRef = useRef();
75
92
  const outerListRef = useRef();
76
- const [overflow, setOverflow] = useState('');
77
93
 
78
94
  const { data } = searchResults;
79
95
 
@@ -83,9 +99,11 @@ const RelationInput = ({
83
99
  const dynamicListHeight = useMemo(
84
100
  () =>
85
101
  totalNumberOfRelations > numberOfRelationsToDisplay
86
- ? Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT +
102
+ ? Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) *
103
+ (RELATION_ITEM_HEIGHT + RELATION_GUTTER) +
87
104
  RELATION_ITEM_HEIGHT / 2
88
- : Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) * RELATION_ITEM_HEIGHT,
105
+ : Math.min(totalNumberOfRelations, numberOfRelationsToDisplay) *
106
+ (RELATION_ITEM_HEIGHT + RELATION_GUTTER),
89
107
  [totalNumberOfRelations, numberOfRelationsToDisplay]
90
108
  );
91
109
 
@@ -139,6 +157,9 @@ const RelationInput = ({
139
157
  };
140
158
  }, [paginatedRelations, relations, numberOfRelationsToDisplay, totalNumberOfRelations]);
141
159
 
160
+ /**
161
+ * --- ReactSelect Workaround START ---
162
+ */
142
163
  /**
143
164
  * This code is being isolated because it's a hack to fix a placement bug in
144
165
  * `react-select` where when the options prop is updated the position of the
@@ -194,12 +215,28 @@ const RelationInput = ({
194
215
  const handleMenuClose = () => {
195
216
  setIsMenuOpen(false);
196
217
  };
218
+ /**
219
+ * --- ReactSelect Workaround END ---
220
+ */
197
221
 
198
222
  const handleMenuOpen = () => {
199
223
  setIsMenuOpen(true);
200
224
  onSearch();
201
225
  };
202
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
+
203
240
  const previewRelationsLength = usePrev(relations.length);
204
241
  /**
205
242
  * @type {React.MutableRefObject<'onChange' | 'loadMore'>}
@@ -223,8 +260,12 @@ const RelationInput = ({
223
260
  ) {
224
261
  listRef.current.scrollToItem(0, 'start');
225
262
  }
263
+
264
+ updatedRelationsWith.current = undefined;
226
265
  }, [previewRelationsLength, relations]);
227
266
 
267
+ const ariaDescriptionId = `${name}-item-instructions`;
268
+
228
269
  return (
229
270
  <Field error={error} name={name} hint={description} id={id}>
230
271
  <Relation
@@ -286,63 +327,40 @@ const RelationInput = ({
286
327
  )
287
328
  }
288
329
  >
289
- <RelationList overflow={overflow}>
290
- <List
291
- height={dynamicListHeight}
292
- ref={listRef}
293
- outerRef={outerListRef}
294
- itemCount={totalNumberOfRelations}
295
- itemSize={RELATION_ITEM_HEIGHT}
296
- itemData={relations}
297
- innerElementType="ol"
298
- >
299
- {({ data, index, style }) => {
300
- const { publicationState, href, mainField, id } = data[index];
301
- const statusColor = publicationState === 'draft' ? 'secondary' : 'success';
302
-
303
- return (
304
- <RelationItem
305
- disabled={disabled}
306
- key={`relation-${name}-${id}`}
307
- endAction={
308
- <DisconnectButton
309
- data-testid={`remove-relation-${id}`}
310
- disabled={disabled}
311
- type="button"
312
- onClick={() => onRelationDisconnect(data[index])}
313
- aria-label={labelDisconnectRelation}
314
- >
315
- <Icon width="12px" as={Cross} />
316
- </DisconnectButton>
317
- }
318
- style={style}
319
- >
320
- <Box minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
321
- <Tooltip description={mainField ?? `${id}`}>
322
- {href ? (
323
- <LinkEllipsis to={href} disabled={disabled}>
324
- {mainField ?? id}
325
- </LinkEllipsis>
326
- ) : (
327
- <Typography textColor={disabled ? 'neutral600' : 'primary600'} ellipsis>
328
- {mainField ?? id}
329
- </Typography>
330
- )}
331
- </Tooltip>
332
- </Box>
333
-
334
- {publicationState && (
335
- <Status variant={statusColor} showBullet={false} size="S">
336
- <Typography fontWeight="bold" textColor={`${statusColor}700`}>
337
- {publicationStateTranslations[publicationState]}
338
- </Typography>
339
- </Status>
340
- )}
341
- </RelationItem>
342
- );
343
- }}
344
- </List>
345
- </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
+ )}
346
364
  {(description || error) && (
347
365
  <Box paddingTop={2}>
348
366
  <FieldHint />
@@ -384,11 +402,16 @@ const SearchResults = PropTypes.shape({
384
402
  });
385
403
 
386
404
  RelationInput.defaultProps = {
405
+ canReorder: false,
387
406
  description: undefined,
388
407
  disabled: false,
389
408
  error: undefined,
390
409
  labelAction: null,
391
410
  labelLoadMore: null,
411
+ liveText: undefined,
412
+ onCancel: undefined,
413
+ onDropItem: undefined,
414
+ onGrabItem: undefined,
392
415
  required: false,
393
416
  relations: { data: [] },
394
417
  searchResults: { data: [] },
@@ -396,20 +419,28 @@ RelationInput.defaultProps = {
396
419
 
397
420
  RelationInput.propTypes = {
398
421
  error: PropTypes.string,
422
+ canReorder: PropTypes.bool,
399
423
  description: PropTypes.string,
400
424
  disabled: PropTypes.bool,
425
+ iconButtonAriaLabel: PropTypes.string.isRequired,
401
426
  id: PropTypes.string.isRequired,
402
427
  label: PropTypes.string.isRequired,
403
428
  labelAction: PropTypes.element,
404
429
  labelLoadMore: PropTypes.string,
405
430
  labelDisconnectRelation: PropTypes.string.isRequired,
431
+ listAriaDescription: PropTypes.string.isRequired,
432
+ liveText: PropTypes.string,
406
433
  loadingMessage: PropTypes.string.isRequired,
407
434
  name: PropTypes.string.isRequired,
408
435
  noRelationsMessage: PropTypes.string.isRequired,
409
436
  numberOfRelationsToDisplay: PropTypes.number.isRequired,
437
+ onCancel: PropTypes.func,
438
+ onDropItem: PropTypes.func,
439
+ onGrabItem: PropTypes.func,
410
440
  onRelationConnect: PropTypes.func.isRequired,
411
441
  onRelationDisconnect: PropTypes.func.isRequired,
412
442
  onRelationLoadMore: PropTypes.func.isRequired,
443
+ onRelationReorder: PropTypes.func.isRequired,
413
444
  onSearch: PropTypes.func.isRequired,
414
445
  onSearchNextPage: PropTypes.func.isRequired,
415
446
  placeholder: PropTypes.string.isRequired,
@@ -423,4 +454,118 @@ RelationInput.propTypes = {
423
454
  relations: RelationsResult,
424
455
  };
425
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
+
426
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;