@strapi/admin 4.4.3 → 4.5.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/CollectionTypeFormWrapper/index.js +35 -1
  2. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +11 -7
  3. package/admin/src/content-manager/components/DynamicTable/CellContent/index.js +6 -5
  4. package/admin/src/content-manager/components/DynamicTable/TableRows/index.js +5 -0
  5. package/admin/src/content-manager/components/DynamicTable/index.js +1 -1
  6. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +73 -20
  7. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +166 -26
  8. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +17 -1
  9. package/admin/src/content-manager/components/Inputs/index.js +12 -4
  10. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +2 -0
  11. package/admin/src/content-manager/components/RelationInput/RelationInput.js +427 -0
  12. package/admin/src/content-manager/components/{SelectWrapper → RelationInput/components}/Option.js +15 -25
  13. package/admin/src/content-manager/components/RelationInput/components/Relation.js +48 -0
  14. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +52 -0
  15. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +52 -0
  16. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  17. package/admin/src/content-manager/components/RelationInput/index.js +1 -0
  18. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +261 -0
  19. package/admin/src/content-manager/components/RelationInputDataManager/constants.js +8 -0
  20. package/admin/src/content-manager/components/RelationInputDataManager/index.js +1 -0
  21. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/connect.js +0 -1
  22. package/admin/src/content-manager/components/RelationInputDataManager/utils/getRelationLink.js +5 -0
  23. package/admin/src/content-manager/components/RelationInputDataManager/utils/index.js +4 -0
  24. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeRelations.js +65 -0
  25. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeSearchResults.js +12 -0
  26. package/admin/src/content-manager/components/RelationInputDataManager/utils/select.js +98 -0
  27. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +5 -0
  28. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +42 -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 +81 -0
  32. package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFields.js +4 -4
  33. package/admin/src/content-manager/pages/EditSettingsView/index.js +21 -49
  34. package/admin/src/content-manager/pages/EditSettingsView/reducer.js +0 -25
  35. package/admin/src/content-manager/pages/EditView/Header/index.js +121 -140
  36. package/admin/src/content-manager/pages/EditView/Header/utils/index.js +0 -1
  37. package/admin/src/content-manager/pages/EditView/Header/utils/select.js +4 -2
  38. package/admin/src/content-manager/pages/EditView/index.js +12 -65
  39. package/admin/src/content-manager/pages/ListView/FieldPicker/index.js +0 -1
  40. package/admin/src/content-manager/pages/ListView/index.js +0 -1
  41. package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +0 -1
  42. package/admin/src/content-manager/utils/formatLayoutToApi.js +1 -3
  43. package/admin/src/pages/HomePage/SocialLinks.js +1 -1
  44. package/admin/src/translations/ar.json +0 -1
  45. package/admin/src/translations/ca.json +1 -8
  46. package/admin/src/translations/cs.json +0 -2
  47. package/admin/src/translations/de.json +4 -12
  48. package/admin/src/translations/dk.json +3 -11
  49. package/admin/src/translations/en.json +4 -11
  50. package/admin/src/translations/es.json +156 -168
  51. package/admin/src/translations/fr.json +3 -11
  52. package/admin/src/translations/gu.json +608 -617
  53. package/admin/src/translations/hi.json +689 -698
  54. package/admin/src/translations/hu.json +2 -10
  55. package/admin/src/translations/id.json +2 -10
  56. package/admin/src/translations/it.json +2 -10
  57. package/admin/src/translations/ja.json +2 -11
  58. package/admin/src/translations/ko.json +2 -11
  59. package/admin/src/translations/ml.json +689 -698
  60. package/admin/src/translations/ms.json +0 -2
  61. package/admin/src/translations/nl.json +3 -11
  62. package/admin/src/translations/pl.json +2 -11
  63. package/admin/src/translations/pt-BR.json +3 -11
  64. package/admin/src/translations/pt.json +0 -1
  65. package/admin/src/translations/ru.json +489 -501
  66. package/admin/src/translations/sa.json +85 -93
  67. package/admin/src/translations/sk.json +3 -10
  68. package/admin/src/translations/sv.json +3 -9
  69. package/admin/src/translations/th.json +0 -2
  70. package/admin/src/translations/tr.json +0 -1
  71. package/admin/src/translations/uk.json +0 -2
  72. package/admin/src/translations/vi.json +0 -1
  73. package/admin/src/translations/zh-Hans.json +4 -13
  74. package/admin/src/translations/zh.json +3 -11
  75. package/build/{8773.51992277.chunk.js → 1939.e3c87653.chunk.js} +50 -50
  76. package/build/8738.a30a2160.chunk.js +461 -0
  77. package/build/962.8651ba3f.chunk.js +184 -0
  78. package/build/Admin-authenticatedApp.883449a5.chunk.js +80 -0
  79. package/build/{Admin_homePage.6d5e3236.chunk.js → Admin_homePage.4b2be829.chunk.js} +1 -1
  80. package/build/{ar-json.d4cb26d9.chunk.js → ar-json.3489463d.chunk.js} +1 -1
  81. package/build/{ca-json.d16c1d28.chunk.js → ca-json.82df6eab.chunk.js} +1 -1
  82. package/build/content-manager.933dc286.chunk.js +1201 -0
  83. package/build/{cs-json.c8f28ba8.chunk.js → cs-json.ce49da5c.chunk.js} +1 -1
  84. package/build/{de-json.a9b514dc.chunk.js → de-json.0ad554eb.chunk.js} +1 -1
  85. package/build/{dk-json.09e8d145.chunk.js → dk-json.e195ea1a.chunk.js} +1 -1
  86. package/build/{en-json.e936d40e.chunk.js → en-json.1889403c.chunk.js} +1 -1
  87. package/build/{es-json.3a9c7c09.chunk.js → es-json.09f80f6e.chunk.js} +1 -1
  88. package/build/{fr-json.4ed1fc2c.chunk.js → fr-json.606d056b.chunk.js} +1 -1
  89. package/build/{gu-json.d8311297.chunk.js → gu-json.9881264f.chunk.js} +1 -1
  90. package/build/{hi-json.0edb8d29.chunk.js → hi-json.83dcf48f.chunk.js} +1 -1
  91. package/build/{hu-json.7855529a.chunk.js → hu-json.6f328bce.chunk.js} +1 -1
  92. package/build/{id-json.df9618f2.chunk.js → id-json.1f3c4303.chunk.js} +1 -1
  93. package/build/index.html +1 -1
  94. package/build/{it-json.a21bf078.chunk.js → it-json.494ac432.chunk.js} +1 -1
  95. package/build/{ja-json.7b0d9067.chunk.js → ja-json.6f262117.chunk.js} +1 -1
  96. package/build/{ko-json.983c1f8f.chunk.js → ko-json.36dc3b9a.chunk.js} +1 -1
  97. package/build/main.63e7ea0a.js +9338 -0
  98. package/build/{ml-json.8dd021c8.chunk.js → ml-json.9566bf9a.chunk.js} +1 -1
  99. package/build/{ms-json.836ed013.chunk.js → ms-json.ed51e902.chunk.js} +1 -1
  100. package/build/{nl-json.29d2eb37.chunk.js → nl-json.94c3a289.chunk.js} +1 -1
  101. package/build/{pl-json.1f04f00c.chunk.js → pl-json.ccc6ef23.chunk.js} +1 -1
  102. package/build/{pt-BR-json.b4bc8efe.chunk.js → pt-BR-json.744f024d.chunk.js} +1 -1
  103. package/build/{pt-json.c23020ab.chunk.js → pt-json.3161ca22.chunk.js} +1 -1
  104. package/build/{ru-json.7ab40ccf.chunk.js → ru-json.d22ea13c.chunk.js} +1 -1
  105. package/build/{runtime~main.7faf633a.js → runtime~main.3a5e1b07.js} +1 -1
  106. package/build/{sa-json.c5a9f4ea.chunk.js → sa-json.8fb1c04d.chunk.js} +1 -1
  107. package/build/{sk-json.e4c24c4e.chunk.js → sk-json.6c7335d4.chunk.js} +1 -1
  108. package/build/{sv-json.c3f471ae.chunk.js → sv-json.2e589a7d.chunk.js} +1 -1
  109. package/build/{th-json.a59ffb32.chunk.js → th-json.72e8de3d.chunk.js} +1 -1
  110. package/build/{tr-json.276e59fe.chunk.js → tr-json.9c44ea0c.chunk.js} +1 -1
  111. package/build/{uk-json.5b5b9c27.chunk.js → uk-json.c4cd2e24.chunk.js} +1 -1
  112. package/build/{upload-translation-en-json.004a86c1.chunk.js → upload-translation-en-json.86da7b0a.chunk.js} +1 -1
  113. package/build/{vi-json.bf3424be.chunk.js → vi-json.f7890025.chunk.js} +1 -1
  114. package/build/{zh-Hans-json.9c99f8d4.chunk.js → zh-Hans-json.a4d7dc69.chunk.js} +1 -1
  115. package/build/{zh-json.451a0271.chunk.js → zh-json.66aa2ae1.chunk.js} +1 -1
  116. package/package.json +7 -7
  117. package/admin/src/content-manager/components/SelectMany/ListItem.js +0 -102
  118. package/admin/src/content-manager/components/SelectMany/index.js +0 -148
  119. package/admin/src/content-manager/components/SelectOne/SingleValue.js +0 -67
  120. package/admin/src/content-manager/components/SelectOne/index.js +0 -97
  121. package/admin/src/content-manager/components/SelectWrapper/Label.js +0 -60
  122. package/admin/src/content-manager/components/SelectWrapper/index.js +0 -356
  123. package/admin/src/content-manager/components/SelectWrapper/utils/index.js +0 -2
  124. package/admin/src/content-manager/components/SelectWrapper/utils/select.js +0 -45
  125. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFieldButton.js +0 -135
  126. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFields.js +0 -103
  127. package/admin/src/content-manager/pages/EditView/Header/utils/getDraftRelations.js +0 -62
  128. package/build/1669.d1b29c28.chunk.js +0 -1
  129. package/build/524.8a540ac1.chunk.js +0 -644
  130. package/build/Admin-authenticatedApp.88fa40ac.chunk.js +0 -80
  131. package/build/content-manager.8bddf2e6.chunk.js +0 -1178
  132. package/build/main.6650d2e7.js +0 -9338
@@ -11,7 +11,7 @@ import { getFieldName } from '../../utils';
11
11
  import Wysiwyg from '../Wysiwyg';
12
12
  import InputJSON from '../InputJSON';
13
13
  import InputUID from '../InputUID';
14
- import SelectWrapper from '../SelectWrapper';
14
+ import { RelationInputDataManager } from '../RelationInputDataManager';
15
15
 
16
16
  import {
17
17
  connect,
@@ -24,6 +24,7 @@ import {
24
24
 
25
25
  function Inputs({
26
26
  allowedFields,
27
+ componentUid,
27
28
  fieldSchema,
28
29
  formErrors,
29
30
  isCreatingEntry,
@@ -35,6 +36,7 @@ function Inputs({
35
36
  shouldNotRunValidations,
36
37
  queryInfos,
37
38
  value,
39
+ size,
38
40
  }) {
39
41
  const { fields } = useLibrary();
40
42
  const { formatMessage } = useIntl();
@@ -224,9 +226,10 @@ function Inputs({
224
226
 
225
227
  if (type === 'relation') {
226
228
  return (
227
- <SelectWrapper
229
+ <RelationInputDataManager
228
230
  {...metadatas}
229
231
  {...fieldSchema}
232
+ componentUid={componentUid}
230
233
  description={
231
234
  metadatas.description
232
235
  ? formatMessage({
@@ -252,6 +255,7 @@ function Inputs({
252
255
  : null
253
256
  }
254
257
  queryInfos={queryInfos}
258
+ size={size}
255
259
  value={value}
256
260
  error={error && formatMessage(error)}
257
261
  />
@@ -299,14 +303,17 @@ function Inputs({
299
303
  }
300
304
 
301
305
  Inputs.defaultProps = {
306
+ componentUid: undefined,
302
307
  formErrors: {},
303
308
  labelAction: undefined,
304
- queryInfos: {},
309
+ size: undefined,
305
310
  value: null,
311
+ queryInfos: {},
306
312
  };
307
313
 
308
314
  Inputs.propTypes = {
309
315
  allowedFields: PropTypes.array.isRequired,
316
+ componentUid: PropTypes.string,
310
317
  fieldSchema: PropTypes.object.isRequired,
311
318
  formErrors: PropTypes.object,
312
319
  keys: PropTypes.string.isRequired,
@@ -315,13 +322,14 @@ Inputs.propTypes = {
315
322
  metadatas: PropTypes.object.isRequired,
316
323
  onChange: PropTypes.func.isRequired,
317
324
  readableFields: PropTypes.array.isRequired,
325
+ size: PropTypes.number,
318
326
  shouldNotRunValidations: PropTypes.bool.isRequired,
327
+ value: PropTypes.any,
319
328
  queryInfos: PropTypes.shape({
320
329
  containsKey: PropTypes.string,
321
330
  defaultParams: PropTypes.object,
322
331
  endPoint: PropTypes.string,
323
332
  }),
324
- value: PropTypes.any,
325
333
  };
326
334
 
327
335
  const Memoized = memo(Inputs, isEqual);
@@ -61,10 +61,12 @@ const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, nam
61
61
  return (
62
62
  <GridItem col={size} key={fieldName} s={12} xs={12}>
63
63
  <Inputs
64
+ componentUid={componentUid}
64
65
  keys={keys}
65
66
  fieldSchema={fieldSchema}
66
67
  metadatas={metadatas}
67
68
  queryInfos={queryInfos}
69
+ size={size}
68
70
  />
69
71
  </GridItem>
70
72
  );
@@ -0,0 +1,427 @@
1
+ import React, { useRef, useState, useMemo, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled 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
+
19
+ import { Relation } from './components/Relation';
20
+ import { RelationItem } from './components/RelationItem';
21
+ import { RelationList } from './components/RelationList';
22
+ import { Option } from './components/Option';
23
+ import { RELATION_ITEM_HEIGHT } from './constants';
24
+
25
+ const LinkEllipsis = styled(Link)`
26
+ white-space: nowrap;
27
+ overflow: hidden;
28
+ text-overflow: ellipsis;
29
+ display: inherit;
30
+ `;
31
+
32
+ const BoxEllipsis = styled(Box)`
33
+ > span {
34
+ white-space: nowrap;
35
+ overflow: hidden;
36
+ text-overflow: ellipsis;
37
+ display: inherit;
38
+ }
39
+ `;
40
+
41
+ const DisconnectButton = styled.button`
42
+ svg path {
43
+ fill: ${({ theme }) => theme.colors.neutral500};
44
+ }
45
+
46
+ &:hover svg path,
47
+ &:focus svg path {
48
+ fill: ${({ theme }) => theme.colors.neutral600};
49
+ }
50
+ `;
51
+
52
+ const RelationInput = ({
53
+ description,
54
+ disabled,
55
+ error,
56
+ id,
57
+ name,
58
+ numberOfRelationsToDisplay,
59
+ label,
60
+ labelAction,
61
+ labelLoadMore,
62
+ labelDisconnectRelation,
63
+ loadingMessage,
64
+ onRelationAdd,
65
+ onRelationLoadMore,
66
+ onRelationRemove,
67
+ onSearchClose,
68
+ onSearchNextPage,
69
+ onSearchOpen,
70
+ onSearch,
71
+ placeholder,
72
+ publicationStateTranslations,
73
+ required,
74
+ relations: paginatedRelations,
75
+ searchResults,
76
+ size,
77
+ }) => {
78
+ const [value, setValue] = useState(null);
79
+ const listRef = useRef();
80
+ const outerListRef = useRef();
81
+ const [overflow, setOverflow] = useState('');
82
+
83
+ const {
84
+ data: { pages },
85
+ } = searchResults;
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
+ pages.flat().map((result) => ({
106
+ ...result,
107
+ value: result.id,
108
+ label: result.mainField,
109
+ })),
110
+ [pages]
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
+ /**
147
+ * This code is being isolated because it's a hack to fix a placement bug in
148
+ * `react-select` where when the options prop is updated the position of the
149
+ * menu is not recalculated.
150
+ */
151
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
152
+
153
+ const timeoutRef = useRef();
154
+ const previousOptions = useRef([]);
155
+
156
+ useEffect(() => {
157
+ /**
158
+ * We only really want this effect to fire once when the options
159
+ * change from an empty array to an array with values.
160
+ * Otherwise, it'll fire when the infinite scrolling happens causing
161
+ * the menu to jump to the top all the time when loading more.
162
+ */
163
+ if (options.length > 0 && previousOptions.current.length === 0) {
164
+ setIsMenuOpen((isCurrentlyOpened) => {
165
+ /**
166
+ * If we're currently open and the options changed
167
+ * we want to close and open to ensure the menu's
168
+ * position is correctly calculated
169
+ */
170
+ if (isCurrentlyOpened) {
171
+ timeoutRef.current = setTimeout(() => {
172
+ setIsMenuOpen(true);
173
+ }, 10);
174
+
175
+ return false;
176
+ }
177
+
178
+ return false;
179
+ });
180
+ }
181
+
182
+ return () => {
183
+ previousOptions.current = options || [];
184
+ };
185
+ }, [options]);
186
+
187
+ useEffect(() => {
188
+ return () => {
189
+ /**
190
+ * If the component unmounts and a timer is set we should clear that timer
191
+ */
192
+ if (timeoutRef.current) {
193
+ clearTimeout(timeoutRef.current);
194
+ }
195
+ };
196
+ }, []);
197
+
198
+ const handleMenuClose = () => {
199
+ setIsMenuOpen(false);
200
+
201
+ if (onSearchClose) {
202
+ onSearchClose();
203
+ }
204
+ };
205
+
206
+ const handleMenuOpen = () => {
207
+ setIsMenuOpen(true);
208
+ onSearchOpen();
209
+ };
210
+
211
+ return (
212
+ <Field error={error} name={name} hint={description} id={id}>
213
+ <Relation
214
+ totalNumberOfRelations={totalNumberOfRelations}
215
+ size={size}
216
+ search={
217
+ <>
218
+ <FieldLabel action={labelAction} required={required}>
219
+ {label}
220
+ </FieldLabel>
221
+ <ReactSelect
222
+ // position fixed doesn't update position on scroll
223
+ // react select doesn't update menu position on options change
224
+ menuPosition="absolute"
225
+ menuPlacement="auto"
226
+ components={{ Option }}
227
+ options={options}
228
+ isDisabled={disabled}
229
+ isLoading={searchResults.isLoading}
230
+ error={error}
231
+ inputId={id}
232
+ isSearchable
233
+ isClear
234
+ loadingMessage={loadingMessage}
235
+ onChange={(relation) => {
236
+ setValue(null);
237
+ onRelationAdd(relation);
238
+
239
+ // scroll to the end of the list
240
+ if (relations.length > 0) {
241
+ setTimeout(() => {
242
+ listRef.current.scrollToItem(relations.length, 'end');
243
+ });
244
+ }
245
+ }}
246
+ onInputChange={(value) => {
247
+ setValue(value);
248
+ onSearch(value);
249
+ }}
250
+ onMenuClose={handleMenuClose}
251
+ onMenuOpen={handleMenuOpen}
252
+ menuIsOpen={isMenuOpen}
253
+ onMenuScrollToBottom={() => {
254
+ if (searchResults.hasNextPage) {
255
+ onSearchNextPage();
256
+ }
257
+ }}
258
+ placeholder={placeholder}
259
+ name={name}
260
+ value={value}
261
+ />
262
+ </>
263
+ }
264
+ loadMore={
265
+ shouldDisplayLoadMoreButton && (
266
+ <TextButton
267
+ disabled={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
268
+ onClick={() => onRelationLoadMore()}
269
+ loading={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
270
+ startIcon={<Refresh />}
271
+ >
272
+ {labelLoadMore}
273
+ </TextButton>
274
+ )
275
+ }
276
+ >
277
+ <RelationList overflow={overflow}>
278
+ <List
279
+ height={dynamicListHeight}
280
+ ref={listRef}
281
+ outerRef={outerListRef}
282
+ itemCount={totalNumberOfRelations}
283
+ itemSize={RELATION_ITEM_HEIGHT}
284
+ itemData={relations}
285
+ innerElementType="ol"
286
+ >
287
+ {({ data, index, style }) => {
288
+ const { publicationState, href, mainField, id } = data[index];
289
+ const badgeColor = publicationState === 'draft' ? 'secondary' : 'success';
290
+
291
+ return (
292
+ <RelationItem
293
+ disabled={disabled}
294
+ key={`relation-${name}-${id}`}
295
+ endAction={
296
+ <DisconnectButton
297
+ data-testid={`remove-relation-${id}`}
298
+ disabled={disabled}
299
+ type="button"
300
+ onClick={() => onRelationRemove(data[index])}
301
+ aria-label={labelDisconnectRelation}
302
+ >
303
+ <Icon width="12px" as={Cross} />
304
+ </DisconnectButton>
305
+ }
306
+ style={style}
307
+ >
308
+ <BoxEllipsis minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
309
+ <Tooltip description={mainField ?? `${id}`}>
310
+ {href ? (
311
+ <LinkEllipsis to={href} disabled={disabled}>
312
+ {mainField ?? id}
313
+ </LinkEllipsis>
314
+ ) : (
315
+ <Typography textColor={disabled ? 'neutral600' : 'primary600'} ellipsis>
316
+ {mainField ?? id}
317
+ </Typography>
318
+ )}
319
+ </Tooltip>
320
+ </BoxEllipsis>
321
+
322
+ {publicationState && (
323
+ <Badge
324
+ borderSize={1}
325
+ borderColor={`${badgeColor}200`}
326
+ backgroundColor={`${badgeColor}100`}
327
+ textColor={`${badgeColor}700`}
328
+ shrink={0}
329
+ >
330
+ {publicationStateTranslations[publicationState]}
331
+ </Badge>
332
+ )}
333
+ </RelationItem>
334
+ );
335
+ }}
336
+ </List>
337
+ </RelationList>
338
+ {(description || error) && (
339
+ <Box paddingTop={2}>
340
+ <FieldHint />
341
+ <FieldError />
342
+ </Box>
343
+ )}
344
+ </Relation>
345
+ </Field>
346
+ );
347
+ };
348
+
349
+ const ReactQueryRelationResult = PropTypes.shape({
350
+ data: PropTypes.shape({
351
+ pages: PropTypes.arrayOf(
352
+ PropTypes.arrayOf(
353
+ PropTypes.shape({
354
+ href: PropTypes.string,
355
+ id: PropTypes.number.isRequired,
356
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
357
+ mainField: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
358
+ })
359
+ )
360
+ ),
361
+ }),
362
+ hasNextPage: PropTypes.bool,
363
+ isFetchingNextPage: PropTypes.bool.isRequired,
364
+ isLoading: PropTypes.bool.isRequired,
365
+ isSuccess: PropTypes.bool.isRequired,
366
+ });
367
+
368
+ const ReactQuerySearchResult = PropTypes.shape({
369
+ data: PropTypes.shape({
370
+ pages: PropTypes.arrayOf(
371
+ PropTypes.arrayOf(
372
+ PropTypes.shape({
373
+ id: PropTypes.number.isRequired,
374
+ mainField: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
375
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
376
+ })
377
+ )
378
+ ),
379
+ }),
380
+ hasNextPage: PropTypes.bool,
381
+ isLoading: PropTypes.bool.isRequired,
382
+ isSuccess: PropTypes.bool.isRequired,
383
+ });
384
+
385
+ RelationInput.defaultProps = {
386
+ description: undefined,
387
+ disabled: false,
388
+ error: undefined,
389
+ labelAction: null,
390
+ labelLoadMore: null,
391
+ onSearchClose: undefined,
392
+ required: false,
393
+ relations: [],
394
+ searchResults: [],
395
+ };
396
+
397
+ RelationInput.propTypes = {
398
+ error: PropTypes.string,
399
+ description: PropTypes.string,
400
+ disabled: PropTypes.bool,
401
+ id: PropTypes.string.isRequired,
402
+ label: PropTypes.string.isRequired,
403
+ labelAction: PropTypes.element,
404
+ labelLoadMore: PropTypes.string,
405
+ labelDisconnectRelation: PropTypes.string.isRequired,
406
+ loadingMessage: PropTypes.func.isRequired,
407
+ name: PropTypes.string.isRequired,
408
+ numberOfRelationsToDisplay: PropTypes.number.isRequired,
409
+ onRelationAdd: PropTypes.func.isRequired,
410
+ onRelationRemove: PropTypes.func.isRequired,
411
+ onRelationLoadMore: PropTypes.func.isRequired,
412
+ onSearch: PropTypes.func.isRequired,
413
+ onSearchNextPage: PropTypes.func.isRequired,
414
+ onSearchClose: PropTypes.func,
415
+ onSearchOpen: PropTypes.func.isRequired,
416
+ placeholder: PropTypes.string.isRequired,
417
+ publicationStateTranslations: PropTypes.shape({
418
+ draft: PropTypes.string.isRequired,
419
+ published: PropTypes.string.isRequired,
420
+ }).isRequired,
421
+ required: PropTypes.bool,
422
+ searchResults: ReactQuerySearchResult,
423
+ size: PropTypes.number.isRequired,
424
+ relations: ReactQueryRelationResult,
425
+ };
426
+
427
+ 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={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>
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
+ };