@scality/data-browser-library 1.0.3 → 1.0.5

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 (51) hide show
  1. package/dist/components/DataBrowserUI.js +18 -8
  2. package/dist/components/__tests__/BucketList.test.js +74 -1
  3. package/dist/components/__tests__/ObjectList.test.js +94 -2
  4. package/dist/components/buckets/BucketCreate.d.ts +1 -0
  5. package/dist/components/buckets/BucketCreate.js +57 -7
  6. package/dist/components/buckets/BucketDetails.js +0 -1
  7. package/dist/components/buckets/BucketLifecycleFormPage.js +209 -213
  8. package/dist/components/buckets/BucketList.js +25 -4
  9. package/dist/components/buckets/BucketReplicationFormPage.js +9 -3
  10. package/dist/components/buckets/DeleteBucketConfigRuleButton.js +1 -1
  11. package/dist/components/buckets/notifications/BucketNotificationList.js +1 -1
  12. package/dist/components/objects/DeleteObjectButton.d.ts +1 -0
  13. package/dist/components/objects/DeleteObjectButton.js +11 -5
  14. package/dist/components/objects/ObjectDetails/FormComponents.d.ts +9 -0
  15. package/dist/components/objects/ObjectDetails/FormComponents.js +37 -0
  16. package/dist/components/objects/ObjectDetails/ObjectMetadata.js +182 -204
  17. package/dist/components/objects/ObjectDetails/ObjectSummary.js +22 -5
  18. package/dist/components/objects/ObjectDetails/ObjectTags.js +109 -154
  19. package/dist/components/objects/ObjectDetails/__tests__/ObjectMetadata.test.d.ts +1 -0
  20. package/dist/components/objects/ObjectDetails/__tests__/ObjectMetadata.test.js +230 -0
  21. package/dist/components/objects/ObjectDetails/__tests__/ObjectTags.test.d.ts +1 -0
  22. package/dist/components/objects/ObjectDetails/__tests__/ObjectTags.test.js +342 -0
  23. package/dist/components/objects/ObjectDetails/__tests__/formUtils.test.d.ts +1 -0
  24. package/dist/components/objects/ObjectDetails/__tests__/formUtils.test.js +202 -0
  25. package/dist/components/objects/ObjectDetails/index.d.ts +2 -1
  26. package/dist/components/objects/ObjectDetails/index.js +12 -16
  27. package/dist/components/objects/ObjectList.d.ts +3 -2
  28. package/dist/components/objects/ObjectList.js +204 -104
  29. package/dist/components/objects/ObjectPage.js +22 -5
  30. package/dist/components/ui/ArrayFieldActions.js +0 -2
  31. package/dist/components/ui/FilterFormSection.js +17 -36
  32. package/dist/components/ui/FormGrid.d.ts +7 -0
  33. package/dist/components/ui/FormGrid.js +37 -0
  34. package/dist/components/ui/Table.elements.js +1 -0
  35. package/dist/config/types.d.ts +45 -2
  36. package/dist/hooks/__tests__/usePresigningS3Client.test.d.ts +1 -0
  37. package/dist/hooks/__tests__/usePresigningS3Client.test.js +104 -0
  38. package/dist/hooks/factories/index.d.ts +1 -1
  39. package/dist/hooks/factories/index.js +2 -2
  40. package/dist/hooks/factories/useCreateS3MutationHook.d.ts +2 -1
  41. package/dist/hooks/factories/useCreateS3MutationHook.js +10 -3
  42. package/dist/hooks/factories/useCreateS3QueryHook.d.ts +1 -0
  43. package/dist/hooks/factories/useCreateS3QueryHook.js +9 -6
  44. package/dist/hooks/index.d.ts +1 -0
  45. package/dist/hooks/index.js +2 -1
  46. package/dist/hooks/presignedOperations.js +4 -4
  47. package/dist/hooks/useBucketLocations.d.ts +6 -0
  48. package/dist/hooks/useBucketLocations.js +45 -0
  49. package/dist/hooks/usePresigningS3Client.d.ts +13 -0
  50. package/dist/hooks/usePresigningS3Client.js +21 -0
  51. package/package.json +4 -4
@@ -0,0 +1,342 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useToast } from "@scality/core-ui";
3
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
+ import { render, screen, waitFor } from "@testing-library/react";
5
+ import user_event from "@testing-library/user-event";
6
+ import { DataBrowserUICustomizationProvider } from "../../../../contexts/DataBrowserUICustomizationContext.js";
7
+ import { useDeleteObjectTagging, useObjectTagging, useSetObjectTagging } from "../../../../hooks/index.js";
8
+ import { ObjectTags } from "../ObjectTags.js";
9
+ jest.mock('../../../../hooks');
10
+ jest.mock('@scality/core-ui', ()=>{
11
+ const actual = jest.requireActual('@scality/core-ui');
12
+ return {
13
+ ...actual,
14
+ useToast: jest.fn()
15
+ };
16
+ });
17
+ jest.mock('../../../providers/DataBrowserProvider', ()=>({
18
+ ...jest.requireActual('../../../providers/DataBrowserProvider'),
19
+ useInvalidateQueries: jest.fn(()=>jest.fn().mockResolvedValue(void 0))
20
+ }));
21
+ const mockUseObjectTagging = jest.mocked(useObjectTagging);
22
+ const mockUseSetObjectTagging = jest.mocked(useSetObjectTagging);
23
+ const mockUseDeleteObjectTagging = jest.mocked(useDeleteObjectTagging);
24
+ const mockUseToast = jest.mocked(useToast);
25
+ const mockShowToast = jest.fn();
26
+ const setupMockDefaults = (tagsData = {
27
+ TagSet: []
28
+ })=>{
29
+ mockUseObjectTagging.mockReturnValue({
30
+ data: tagsData,
31
+ status: 'success'
32
+ });
33
+ mockUseSetObjectTagging.mockReturnValue({
34
+ mutateAsync: jest.fn().mockResolvedValue({}),
35
+ reset: jest.fn()
36
+ });
37
+ mockUseDeleteObjectTagging.mockReturnValue({
38
+ mutateAsync: jest.fn().mockResolvedValue({}),
39
+ reset: jest.fn()
40
+ });
41
+ mockUseToast.mockReturnValue({
42
+ showToast: mockShowToast
43
+ });
44
+ };
45
+ const renderWithProviders = (ui)=>{
46
+ const queryClient = new QueryClient({
47
+ defaultOptions: {
48
+ queries: {
49
+ retry: false
50
+ },
51
+ mutations: {
52
+ retry: false
53
+ }
54
+ }
55
+ });
56
+ return render(/*#__PURE__*/ jsx(QueryClientProvider, {
57
+ client: queryClient,
58
+ children: /*#__PURE__*/ jsx(DataBrowserUICustomizationProvider, {
59
+ config: {},
60
+ children: ui
61
+ })
62
+ }));
63
+ };
64
+ const defaultProps = {
65
+ bucketName: 'test-bucket',
66
+ objectKey: 'test-object.txt'
67
+ };
68
+ const getSaveButton = ()=>screen.getByRole('button', {
69
+ name: /save/i
70
+ });
71
+ describe('ObjectTags', ()=>{
72
+ beforeEach(()=>{
73
+ jest.clearAllMocks();
74
+ });
75
+ describe('Loading and Error States', ()=>{
76
+ it('should show loader when tags are loading', ()=>{
77
+ mockUseObjectTagging.mockReturnValue({
78
+ data: void 0,
79
+ status: 'pending'
80
+ });
81
+ mockUseSetObjectTagging.mockReturnValue({
82
+ reset: jest.fn()
83
+ });
84
+ mockUseDeleteObjectTagging.mockReturnValue({
85
+ reset: jest.fn()
86
+ });
87
+ mockUseToast.mockReturnValue({
88
+ showToast: mockShowToast
89
+ });
90
+ const { container } = renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
91
+ ...defaultProps
92
+ }));
93
+ expect(container.querySelectorAll('svg').length).toBeGreaterThan(0);
94
+ });
95
+ it('should show error message when tags fail to load', ()=>{
96
+ mockUseObjectTagging.mockReturnValue({
97
+ data: void 0,
98
+ status: 'error'
99
+ });
100
+ mockUseSetObjectTagging.mockReturnValue({
101
+ reset: jest.fn()
102
+ });
103
+ mockUseDeleteObjectTagging.mockReturnValue({
104
+ reset: jest.fn()
105
+ });
106
+ mockUseToast.mockReturnValue({
107
+ showToast: mockShowToast
108
+ });
109
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
110
+ ...defaultProps
111
+ }));
112
+ expect(screen.getByText('Error loading tags')).toBeInTheDocument();
113
+ });
114
+ });
115
+ describe('Empty Row Validation', ()=>{
116
+ it('should keep Save disabled when no tags exist and nothing is changed', async ()=>{
117
+ setupMockDefaults({
118
+ TagSet: []
119
+ });
120
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
121
+ ...defaultProps
122
+ }));
123
+ await waitFor(()=>{
124
+ expect(screen.getByLabelText('Tag 1 key')).toBeInTheDocument();
125
+ });
126
+ expect(getSaveButton()).toBeDisabled();
127
+ });
128
+ it('should not block Save when a filled tag has a trailing empty row', async ()=>{
129
+ setupMockDefaults({
130
+ TagSet: []
131
+ });
132
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
133
+ ...defaultProps
134
+ }));
135
+ const user = user_event.setup();
136
+ await waitFor(()=>{
137
+ expect(screen.getByLabelText('Tag 1 key')).toBeInTheDocument();
138
+ });
139
+ await user.type(screen.getByLabelText('Tag 1 key'), 'env');
140
+ await user.type(screen.getByLabelText('Tag 1 value'), 'prod');
141
+ await waitFor(()=>{
142
+ expect(screen.getByRole('button', {
143
+ name: /add tag/i
144
+ })).toBeEnabled();
145
+ });
146
+ await user.click(screen.getByRole('button', {
147
+ name: /add tag/i
148
+ }));
149
+ await waitFor(()=>{
150
+ expect(screen.getByLabelText('Tag 2 key')).toBeInTheDocument();
151
+ });
152
+ await waitFor(()=>{
153
+ expect(getSaveButton()).toBeEnabled();
154
+ });
155
+ });
156
+ it('should show validation error when only key is filled (value empty)', async ()=>{
157
+ setupMockDefaults({
158
+ TagSet: []
159
+ });
160
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
161
+ ...defaultProps
162
+ }));
163
+ const user = user_event.setup();
164
+ await waitFor(()=>{
165
+ expect(screen.getByLabelText('Tag 1 key')).toBeInTheDocument();
166
+ });
167
+ await user.type(screen.getByLabelText('Tag 1 key'), 'env');
168
+ await user.type(screen.getByLabelText('Tag 1 value'), 'x');
169
+ await user.clear(screen.getByLabelText('Tag 1 value'));
170
+ await waitFor(()=>{
171
+ expect(screen.getByText('Value cannot be empty or whitespace only')).toBeInTheDocument();
172
+ });
173
+ });
174
+ });
175
+ describe('Remove Button (canRemove)', ()=>{
176
+ it('should disable remove button for a single empty placeholder row', async ()=>{
177
+ setupMockDefaults({
178
+ TagSet: []
179
+ });
180
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
181
+ ...defaultProps
182
+ }));
183
+ await waitFor(()=>{
184
+ expect(screen.getByLabelText('Tag 1 key')).toBeInTheDocument();
185
+ });
186
+ expect(screen.getByRole('button', {
187
+ name: /remove tag/i
188
+ })).toBeDisabled();
189
+ });
190
+ it('should enable remove button when there are multiple rows', async ()=>{
191
+ setupMockDefaults({
192
+ TagSet: [
193
+ {
194
+ Key: 'env',
195
+ Value: 'prod'
196
+ },
197
+ {
198
+ Key: 'team',
199
+ Value: 'platform'
200
+ }
201
+ ]
202
+ });
203
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
204
+ ...defaultProps
205
+ }));
206
+ await waitFor(()=>{
207
+ expect(screen.getByLabelText('Tag 1 key')).toHaveValue('env');
208
+ });
209
+ const removeButtons = screen.getAllByRole('button', {
210
+ name: /remove tag/i
211
+ });
212
+ expect(removeButtons[0]).toBeEnabled();
213
+ expect(removeButtons[1]).toBeEnabled();
214
+ });
215
+ it('should enable Save after removing one of two tags', async ()=>{
216
+ setupMockDefaults({
217
+ TagSet: [
218
+ {
219
+ Key: 'env',
220
+ Value: 'prod'
221
+ },
222
+ {
223
+ Key: 'team',
224
+ Value: 'platform'
225
+ }
226
+ ]
227
+ });
228
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
229
+ ...defaultProps
230
+ }));
231
+ const user = user_event.setup();
232
+ await waitFor(()=>{
233
+ expect(screen.getByLabelText('Tag 1 key')).toHaveValue('env');
234
+ });
235
+ const removeButtons = screen.getAllByRole('button', {
236
+ name: /remove tag/i
237
+ });
238
+ await user.click(removeButtons[0]);
239
+ await waitFor(()=>{
240
+ expect(getSaveButton()).toBeEnabled();
241
+ });
242
+ });
243
+ });
244
+ describe('Duplicate Key Validation', ()=>{
245
+ it('should not count empty rows as duplicates', async ()=>{
246
+ setupMockDefaults({
247
+ TagSet: []
248
+ });
249
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
250
+ ...defaultProps
251
+ }));
252
+ const user = user_event.setup();
253
+ await waitFor(()=>{
254
+ expect(screen.getByLabelText('Tag 1 key')).toBeInTheDocument();
255
+ });
256
+ await user.type(screen.getByLabelText('Tag 1 key'), 'env');
257
+ await user.type(screen.getByLabelText('Tag 1 value'), 'prod');
258
+ await waitFor(()=>{
259
+ expect(screen.getByRole('button', {
260
+ name: /add tag/i
261
+ })).toBeEnabled();
262
+ });
263
+ await user.click(screen.getByRole('button', {
264
+ name: /add tag/i
265
+ }));
266
+ await waitFor(()=>{
267
+ expect(screen.getByLabelText('Tag 2 key')).toBeInTheDocument();
268
+ });
269
+ expect(screen.queryByText(/duplicate key/i)).not.toBeInTheDocument();
270
+ });
271
+ });
272
+ describe('Save Button Change Detection', ()=>{
273
+ it('should disable Save when no actual changes are made', async ()=>{
274
+ setupMockDefaults({
275
+ TagSet: [
276
+ {
277
+ Key: 'env',
278
+ Value: 'prod'
279
+ }
280
+ ]
281
+ });
282
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
283
+ ...defaultProps
284
+ }));
285
+ await waitFor(()=>{
286
+ expect(screen.getByLabelText('Tag 1 key')).toHaveValue('env');
287
+ });
288
+ expect(getSaveButton()).toBeDisabled();
289
+ });
290
+ it('should enable Save when a tag value is modified', async ()=>{
291
+ setupMockDefaults({
292
+ TagSet: [
293
+ {
294
+ Key: 'env',
295
+ Value: 'prod'
296
+ }
297
+ ]
298
+ });
299
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
300
+ ...defaultProps
301
+ }));
302
+ const user = user_event.setup();
303
+ await waitFor(()=>{
304
+ expect(screen.getByLabelText('Tag 1 value')).toHaveValue('prod');
305
+ });
306
+ await user.clear(screen.getByLabelText('Tag 1 value'));
307
+ await user.type(screen.getByLabelText('Tag 1 value'), 'staging');
308
+ await waitFor(()=>{
309
+ expect(getSaveButton()).toBeEnabled();
310
+ });
311
+ });
312
+ it('should keep Save disabled when adding only an empty trailing row to existing tags', async ()=>{
313
+ setupMockDefaults({
314
+ TagSet: [
315
+ {
316
+ Key: 'env',
317
+ Value: 'prod'
318
+ }
319
+ ]
320
+ });
321
+ renderWithProviders(/*#__PURE__*/ jsx(ObjectTags, {
322
+ ...defaultProps
323
+ }));
324
+ const user = user_event.setup();
325
+ await waitFor(()=>{
326
+ expect(screen.getByLabelText('Tag 1 key')).toHaveValue('env');
327
+ });
328
+ await waitFor(()=>{
329
+ expect(screen.getByRole('button', {
330
+ name: /add tag/i
331
+ })).toBeEnabled();
332
+ });
333
+ await user.click(screen.getByRole('button', {
334
+ name: /add tag/i
335
+ }));
336
+ await waitFor(()=>{
337
+ expect(screen.getByLabelText('Tag 2 key')).toBeInTheDocument();
338
+ });
339
+ expect(getSaveButton()).toBeDisabled();
340
+ });
341
+ });
342
+ });
@@ -0,0 +1,202 @@
1
+ import { hasFormDataChanged } from "../formUtils.js";
2
+ const identity = (data)=>data;
3
+ const normalizeTags = (tags)=>tags.filter((tag)=>tag.key.trim() && tag.value.trim()).map((tag)=>({
4
+ key: tag.key.trim(),
5
+ value: tag.value.trim()
6
+ })).sort((a, b)=>a.key.localeCompare(b.key));
7
+ describe('hasFormDataChanged', ()=>{
8
+ describe('basic comparison', ()=>{
9
+ it('should return false when both arrays are empty', ()=>{
10
+ expect(hasFormDataChanged([], [], identity)).toBe(false);
11
+ });
12
+ it('should return false when data is identical', ()=>{
13
+ const data = [
14
+ {
15
+ key: 'a',
16
+ value: '1'
17
+ }
18
+ ];
19
+ expect(hasFormDataChanged(data, data, identity)).toBe(false);
20
+ });
21
+ it('should return true when lengths differ', ()=>{
22
+ const current = [
23
+ {
24
+ key: 'a',
25
+ value: '1'
26
+ }
27
+ ];
28
+ const original = [
29
+ {
30
+ key: 'a',
31
+ value: '1'
32
+ },
33
+ {
34
+ key: 'b',
35
+ value: '2'
36
+ }
37
+ ];
38
+ expect(hasFormDataChanged(current, original, identity)).toBe(true);
39
+ });
40
+ it('should return true when values differ', ()=>{
41
+ const current = [
42
+ {
43
+ key: 'a',
44
+ value: 'changed'
45
+ }
46
+ ];
47
+ const original = [
48
+ {
49
+ key: 'a',
50
+ value: '1'
51
+ }
52
+ ];
53
+ expect(hasFormDataChanged(current, original, identity)).toBe(true);
54
+ });
55
+ it('should return true when keys differ', ()=>{
56
+ const current = [
57
+ {
58
+ key: 'b',
59
+ value: '1'
60
+ }
61
+ ];
62
+ const original = [
63
+ {
64
+ key: 'a',
65
+ value: '1'
66
+ }
67
+ ];
68
+ expect(hasFormDataChanged(current, original, identity)).toBe(true);
69
+ });
70
+ });
71
+ describe('with normalizeTags (filters empty rows, trims, sorts)', ()=>{
72
+ it('should return false when only difference is empty trailing rows', ()=>{
73
+ const current = [
74
+ {
75
+ key: 'env',
76
+ value: 'prod'
77
+ },
78
+ {
79
+ key: '',
80
+ value: ''
81
+ }
82
+ ];
83
+ const original = [
84
+ {
85
+ key: 'env',
86
+ value: 'prod'
87
+ }
88
+ ];
89
+ expect(hasFormDataChanged(current, original, normalizeTags)).toBe(false);
90
+ });
91
+ it('should return false when only difference is whitespace', ()=>{
92
+ const current = [
93
+ {
94
+ key: ' env ',
95
+ value: ' prod '
96
+ }
97
+ ];
98
+ const original = [
99
+ {
100
+ key: 'env',
101
+ value: 'prod'
102
+ }
103
+ ];
104
+ expect(hasFormDataChanged(current, original, normalizeTags)).toBe(false);
105
+ });
106
+ it('should return false when both have empty rows only', ()=>{
107
+ const current = [
108
+ {
109
+ key: '',
110
+ value: ''
111
+ }
112
+ ];
113
+ const original = [
114
+ {
115
+ key: '',
116
+ value: ''
117
+ }
118
+ ];
119
+ expect(hasFormDataChanged(current, original, normalizeTags)).toBe(false);
120
+ });
121
+ it('should return true when a new tag is added', ()=>{
122
+ const current = [
123
+ {
124
+ key: 'env',
125
+ value: 'prod'
126
+ },
127
+ {
128
+ key: 'team',
129
+ value: 'platform'
130
+ }
131
+ ];
132
+ const original = [
133
+ {
134
+ key: 'env',
135
+ value: 'prod'
136
+ }
137
+ ];
138
+ expect(hasFormDataChanged(current, original, normalizeTags)).toBe(true);
139
+ });
140
+ it('should return true when a tag is removed (plus empty placeholder)', ()=>{
141
+ const current = [
142
+ {
143
+ key: '',
144
+ value: ''
145
+ }
146
+ ];
147
+ const original = [
148
+ {
149
+ key: 'env',
150
+ value: 'prod'
151
+ }
152
+ ];
153
+ expect(hasFormDataChanged(current, original, normalizeTags)).toBe(true);
154
+ });
155
+ it('should return false regardless of order', ()=>{
156
+ const current = [
157
+ {
158
+ key: 'team',
159
+ value: 'platform'
160
+ },
161
+ {
162
+ key: 'env',
163
+ value: 'prod'
164
+ }
165
+ ];
166
+ const original = [
167
+ {
168
+ key: 'env',
169
+ value: 'prod'
170
+ },
171
+ {
172
+ key: 'team',
173
+ value: 'platform'
174
+ }
175
+ ];
176
+ expect(hasFormDataChanged(current, original, normalizeTags)).toBe(false);
177
+ });
178
+ it('should return false when multiple empty rows exist alongside unchanged data', ()=>{
179
+ const current = [
180
+ {
181
+ key: 'env',
182
+ value: 'prod'
183
+ },
184
+ {
185
+ key: '',
186
+ value: ''
187
+ },
188
+ {
189
+ key: '',
190
+ value: ''
191
+ }
192
+ ];
193
+ const original = [
194
+ {
195
+ key: 'env',
196
+ value: 'prod'
197
+ }
198
+ ];
199
+ expect(hasFormDataChanged(current, original, normalizeTags)).toBe(false);
200
+ });
201
+ });
202
+ });
@@ -19,7 +19,8 @@ interface ObjectDetailsContextValue {
19
19
  */
20
20
  export declare const ObjectDetailsContext: import("react").Context<ObjectDetailsContextValue | null>;
21
21
  export declare function useObjectDetailsContext(): ObjectDetailsContextValue;
22
- export declare const ObjectDetails: ({ item }: {
22
+ export declare const ObjectDetails: ({ item, multipleSelected }: {
23
23
  item: TableItem | null;
24
+ multipleSelected?: boolean;
24
25
  }) => import("react/jsx-runtime").JSX.Element;
25
26
  export {};
@@ -44,21 +44,18 @@ const CustomTab = ({ config })=>/*#__PURE__*/ jsx(Fragment, {
44
44
  children: config.render()
45
45
  });
46
46
  CustomTab.displayName = 'ObjectDetails.CustomTab';
47
- const ObjectDetails = ({ item })=>{
47
+ const ObjectDetails = ({ item, multipleSelected })=>{
48
48
  const { bucketName } = useParams();
49
49
  const { extraObjectTabs } = useDataBrowserUICustomization();
50
+ const placeholderMessages = {
51
+ deleteMarker: 'A "Delete Marker" is selected',
52
+ folder: 'A "Folder" is selected'
53
+ };
50
54
  const getPlaceholderMessage = ()=>{
51
- if (!bucketName || !item || !item.Key) return /*#__PURE__*/ jsx(Text, {
52
- children: "Select an object to view details"
53
- });
54
- if ('deleteMarker' === item.type) return /*#__PURE__*/ jsx(Text, {
55
- children: 'A "Delete Marker" is selected'
56
- });
57
- if ('folder' === item.type) return /*#__PURE__*/ jsx(Text, {
58
- children: 'A "Folder" is selected'
59
- });
55
+ const message = multipleSelected ? 'Multiple items selected' : item?.type && placeholderMessages[item.type] || 'Select an object to view details';
60
56
  return /*#__PURE__*/ jsx(Text, {
61
- children: "Select an object to view details"
57
+ color: "textSecondary",
58
+ children: message
62
59
  });
63
60
  };
64
61
  const defaultTabsMap = useMemo(()=>({
@@ -66,7 +63,7 @@ const ObjectDetails = ({ item })=>{
66
63
  id: 'summary',
67
64
  label: 'Summary',
68
65
  path: '',
69
- withoutPadding: false,
66
+ withoutPadding: true,
70
67
  query: {
71
68
  tab: ''
72
69
  },
@@ -77,7 +74,7 @@ const ObjectDetails = ({ item })=>{
77
74
  id: 'metadata',
78
75
  label: 'Metadata',
79
76
  path: '',
80
- withoutPadding: false,
77
+ withoutPadding: true,
81
78
  query: {
82
79
  tab: 'metadata'
83
80
  },
@@ -88,7 +85,7 @@ const ObjectDetails = ({ item })=>{
88
85
  id: 'tags',
89
86
  label: 'Tags',
90
87
  path: '',
91
- withoutPadding: false,
88
+ withoutPadding: true,
92
89
  query: {
93
90
  tab: 'tags'
94
91
  },
@@ -129,13 +126,12 @@ const ObjectDetails = ({ item })=>{
129
126
  objectKey,
130
127
  versionId
131
128
  ]);
132
- const isPlaceholder = !bucketName || !item || !item.Key || 'deleteMarker' === item.type || 'folder' === item.type;
129
+ const isPlaceholder = multipleSelected || !bucketName || !item || !item.Key || 'deleteMarker' === item.type || 'folder' === item.type;
133
130
  const placeholderMessage = isPlaceholder ? getPlaceholderMessage() : null;
134
131
  const tabsContent = useMemo(()=>allTabs.map((tab)=>/*#__PURE__*/ jsx(Tabs.Tab, {
135
132
  label: tab.label,
136
133
  path: tab.path,
137
134
  query: tab.query,
138
- withoutPadding: tab.withoutPadding,
139
135
  children: isPlaceholder ? /*#__PURE__*/ jsx(Box, {
140
136
  padding: spacing.r16,
141
137
  display: "flex",
@@ -35,8 +35,9 @@ export declare const isObjectLike: (item: TableItem) => item is ObjectItem | Ver
35
35
  interface ObjectListProps {
36
36
  bucketName: string;
37
37
  prefix: string;
38
- onObjectSelect: (object: TableItem) => void;
38
+ onObjectSelect: (object: TableItem | null) => void;
39
39
  onPrefixChange: (newPrefix: string) => void;
40
+ onSelectedObjectsChange?: (selectedObjects: TableItem[]) => void;
40
41
  }
41
- export declare const ObjectList: ({ bucketName, prefix, onObjectSelect, onPrefixChange }: ObjectListProps) => import("react/jsx-runtime").JSX.Element;
42
+ export declare const ObjectList: ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSelectedObjectsChange, }: ObjectListProps) => import("react/jsx-runtime").JSX.Element;
42
43
  export {};