@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
@@ -7,11 +7,13 @@ import joi from "joi";
7
7
  import { useCallback, useEffect, useMemo, useRef } from "react";
8
8
  import { Controller, FormProvider, useFieldArray, useForm } from "react-hook-form";
9
9
  import { useParams } from "react-router";
10
+ import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
10
11
  import { useGetBucketLifecycle, useSetBucketLifecycle } from "../../hooks/bucketConfiguration.js";
11
12
  import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
12
13
  import { AWS_RULE_LIMITS, STATUS_OPTIONS, buildS3Filter } from "../../utils/s3RuleUtils.js";
13
14
  import { ArrayFieldActions } from "../ui/ArrayFieldActions.js";
14
15
  import { FilterFormSection, createFilterValidationSchema } from "../ui/FilterFormSection.js";
16
+ import { FormCell, FormRow } from "../ui/FormGrid.js";
15
17
  const storageClassOptions = [
16
18
  {
17
19
  value: 'GLACIER',
@@ -50,117 +52,120 @@ const LIFECYCLE_LIMITS = {
50
52
  NONCURRENT_TRANSITION_MIN_DAYS: 30,
51
53
  ABORT_MPU_MIN_DAYS: 1
52
54
  };
53
- const schema = joi.object({
54
- ruleId: joi.string().required().max(AWS_RULE_LIMITS.RULE_ID_MAX_LENGTH).messages({
55
- 'string.empty': 'Rule ID is required',
56
- 'string.max': `Rule ID must not exceed ${AWS_RULE_LIMITS.RULE_ID_MAX_LENGTH} characters`
57
- }),
58
- status: joi.string().valid(...STATUS_OPTIONS.map((o)=>o.value)).required(),
59
- ...createFilterValidationSchema(joi),
60
- transitionsEnabled: joi.boolean(),
61
- transitions: joi.when('transitionsEnabled', {
62
- is: true,
63
- then: joi.array().items(joi.object({
64
- timeType: joi.string().valid('days', 'date').required(),
65
- days: joi.when('timeType', {
66
- is: 'days',
67
- then: joi.number().integer().min(0).required().messages({
68
- 'number.base': 'Days must be a number',
69
- 'number.min': 'Days must be at least 0'
55
+ const createSchema = (hasCustomStorageClassSelector)=>{
56
+ const storageClassRule = hasCustomStorageClassSelector ? joi.string().required().messages({
57
+ 'string.empty': 'Required'
58
+ }) : joi.string().valid(...storageClassOptions.map((o)=>o.value)).required().messages({
59
+ 'string.empty': 'Storage class is required'
60
+ });
61
+ return joi.object({
62
+ ruleId: joi.string().required().max(AWS_RULE_LIMITS.RULE_ID_MAX_LENGTH).messages({
63
+ 'string.empty': 'Rule ID is required',
64
+ 'string.max': `Rule ID must not exceed ${AWS_RULE_LIMITS.RULE_ID_MAX_LENGTH} characters`
65
+ }),
66
+ status: joi.string().valid(...STATUS_OPTIONS.map((o)=>o.value)).required(),
67
+ ...createFilterValidationSchema(joi),
68
+ transitionsEnabled: joi.boolean(),
69
+ transitions: joi.when('transitionsEnabled', {
70
+ is: true,
71
+ then: joi.array().items(joi.object({
72
+ timeType: joi.string().valid('days', 'date').required(),
73
+ days: joi.when('timeType', {
74
+ is: 'days',
75
+ then: joi.number().integer().min(0).required().messages({
76
+ 'number.base': 'Days must be a number',
77
+ 'number.min': 'Days must be at least 0'
78
+ }),
79
+ otherwise: joi.any()
70
80
  }),
71
- otherwise: joi.any()
72
- }),
73
- date: joi.when('timeType', {
74
- is: 'date',
75
- then: joi.string().required().messages({
76
- 'string.empty': 'Date is required'
81
+ date: joi.when('timeType', {
82
+ is: 'date',
83
+ then: joi.string().required().messages({
84
+ 'string.empty': 'Date is required'
85
+ }),
86
+ otherwise: joi.any()
77
87
  }),
78
- otherwise: joi.any()
88
+ storageClass: storageClassRule
89
+ }).custom((value, helpers)=>{
90
+ if (!hasCustomStorageClassSelector && 'days' === value.timeType && value.storageClass && value.days < STORAGE_CLASS_MIN_DAYS[value.storageClass]) return helpers.error('any.custom', {
91
+ message: `${value.storageClass} requires at least ${STORAGE_CLASS_MIN_DAYS[value.storageClass]} days`
92
+ });
93
+ return value;
94
+ })).min(1).messages({
95
+ 'array.min': 'At least one transition is required when enabled'
79
96
  }),
80
- storageClass: joi.string().valid(...storageClassOptions.map((o)=>o.value)).required().messages({
81
- 'string.empty': 'Storage class is required'
82
- })
83
- }).custom((value, helpers)=>{
84
- if ('days' === value.timeType && value.storageClass && value.days < STORAGE_CLASS_MIN_DAYS[value.storageClass]) return helpers.error('any.custom', {
85
- message: `${value.storageClass} requires at least ${STORAGE_CLASS_MIN_DAYS[value.storageClass]} days`
86
- });
87
- return value;
88
- })).min(1).messages({
89
- 'array.min': 'At least one transition is required when enabled'
90
- }),
91
- otherwise: joi.any()
92
- }),
93
- expirationEnabled: joi.boolean(),
94
- expirationType: joi.when('expirationEnabled', {
95
- is: true,
96
- then: joi.string().valid('days', 'date').required(),
97
- otherwise: joi.any()
98
- }),
99
- expirationDays: joi.when('expirationType', {
100
- is: 'days',
101
- then: joi.number().integer().min(1).required().messages({
102
- 'number.base': 'Days must be a number',
103
- 'number.min': 'Days must be at least 1'
97
+ otherwise: joi.any()
104
98
  }),
105
- otherwise: joi.any()
106
- }),
107
- expirationDate: joi.when('expirationType', {
108
- is: 'date',
109
- then: joi.string().required().messages({
110
- 'string.empty': 'Date is required'
99
+ expirationEnabled: joi.boolean(),
100
+ expirationType: joi.when('expirationEnabled', {
101
+ is: true,
102
+ then: joi.string().valid('days', 'date').required(),
103
+ otherwise: joi.any()
111
104
  }),
112
- otherwise: joi.any()
113
- }),
114
- expiredObjectDeleteMarker: joi.boolean(),
115
- noncurrentTransitionsEnabled: joi.boolean(),
116
- noncurrentTransitions: joi.when('noncurrentTransitionsEnabled', {
117
- is: true,
118
- then: joi.array().items(joi.object({
119
- noncurrentDays: joi.number().integer().min(LIFECYCLE_LIMITS.NONCURRENT_TRANSITION_MIN_DAYS).required().messages({
120
- 'number.base': 'Noncurrent days must be a number',
121
- 'number.min': `Noncurrent days must be at least ${LIFECYCLE_LIMITS.NONCURRENT_TRANSITION_MIN_DAYS}`
105
+ expirationDays: joi.when('expirationType', {
106
+ is: 'days',
107
+ then: joi.number().integer().min(1).required().messages({
108
+ 'number.base': 'Days must be a number',
109
+ 'number.min': 'Days must be at least 1'
122
110
  }),
123
- storageClass: joi.string().valid(...storageClassOptions.map((o)=>o.value)).required().messages({
124
- 'string.empty': 'Storage class is required'
111
+ otherwise: joi.any()
112
+ }),
113
+ expirationDate: joi.when('expirationType', {
114
+ is: 'date',
115
+ then: joi.string().required().messages({
116
+ 'string.empty': 'Date is required'
125
117
  }),
126
- newerNoncurrentVersions: joi.number().integer().min(0).optional().allow(null, '')
127
- })).min(1).messages({
128
- 'array.min': 'At least one noncurrent transition is required when enabled'
118
+ otherwise: joi.any()
129
119
  }),
130
- otherwise: joi.any()
131
- }),
132
- noncurrentExpirationEnabled: joi.boolean(),
133
- noncurrentExpirationDays: joi.when('noncurrentExpirationEnabled', {
134
- is: true,
135
- then: joi.number().integer().min(1).required().messages({
136
- 'number.base': 'Noncurrent days must be a number',
137
- 'number.min': 'Noncurrent days must be at least 1'
120
+ expiredObjectDeleteMarker: joi.boolean(),
121
+ noncurrentTransitionsEnabled: joi.boolean(),
122
+ noncurrentTransitions: joi.when('noncurrentTransitionsEnabled', {
123
+ is: true,
124
+ then: joi.array().items(joi.object({
125
+ noncurrentDays: joi.number().integer().min(LIFECYCLE_LIMITS.NONCURRENT_TRANSITION_MIN_DAYS).required().messages({
126
+ 'number.base': 'Noncurrent days must be a number',
127
+ 'number.min': `Noncurrent days must be at least ${LIFECYCLE_LIMITS.NONCURRENT_TRANSITION_MIN_DAYS}`
128
+ }),
129
+ storageClass: storageClassRule,
130
+ newerNoncurrentVersions: joi.number().integer().min(0).optional().allow(null, '')
131
+ })).min(1).messages({
132
+ 'array.min': 'At least one noncurrent transition is required when enabled'
133
+ }),
134
+ otherwise: joi.any()
138
135
  }),
139
- otherwise: joi.any()
140
- }),
141
- noncurrentNewerVersions: joi.number().integer().min(0).optional().allow(null, ''),
142
- abortMpuEnabled: joi.boolean(),
143
- abortMpuDays: joi.when('abortMpuEnabled', {
144
- is: true,
145
- then: joi.number().integer().min(LIFECYCLE_LIMITS.ABORT_MPU_MIN_DAYS).required().messages({
146
- 'number.base': 'Days must be a number',
147
- 'number.min': `Days must be at least ${LIFECYCLE_LIMITS.ABORT_MPU_MIN_DAYS}`
136
+ noncurrentExpirationEnabled: joi.boolean(),
137
+ noncurrentExpirationDays: joi.when('noncurrentExpirationEnabled', {
138
+ is: true,
139
+ then: joi.number().integer().min(1).required().messages({
140
+ 'number.base': 'Noncurrent days must be a number',
141
+ 'number.min': 'Noncurrent days must be at least 1'
142
+ }),
143
+ otherwise: joi.any()
148
144
  }),
149
- otherwise: joi.any()
150
- })
151
- }).custom((value, helpers)=>{
152
- const hasAtLeastOneAction = value.transitionsEnabled || value.expirationEnabled || value.expiredObjectDeleteMarker || value.noncurrentTransitionsEnabled || value.noncurrentExpirationEnabled || value.abortMpuEnabled;
153
- if (!hasAtLeastOneAction) return helpers.error('any.custom', {
154
- message: 'At least one lifecycle action must be enabled (transition, expiration, or abort multipart upload)'
155
- });
156
- if (value.expirationEnabled && value.expiredObjectDeleteMarker) return helpers.error('any.custom', {
157
- message: 'Expired delete markers removal cannot be combined with Days/Date expiration on the same rule'
158
- });
159
- if (('tags' === value.filterType || 'and' === value.filterType) && value.abortMpuEnabled) return helpers.error('any.custom', {
160
- message: 'Tag-based filter cannot be used with Abort Incomplete Multipart Upload'
145
+ noncurrentNewerVersions: joi.number().integer().min(0).optional().allow(null, ''),
146
+ abortMpuEnabled: joi.boolean(),
147
+ abortMpuDays: joi.when('abortMpuEnabled', {
148
+ is: true,
149
+ then: joi.number().integer().min(LIFECYCLE_LIMITS.ABORT_MPU_MIN_DAYS).required().messages({
150
+ 'number.base': 'Days must be a number',
151
+ 'number.min': `Days must be at least ${LIFECYCLE_LIMITS.ABORT_MPU_MIN_DAYS}`
152
+ }),
153
+ otherwise: joi.any()
154
+ })
155
+ }).custom((value, helpers)=>{
156
+ const hasAtLeastOneAction = value.transitionsEnabled || value.expirationEnabled || value.expiredObjectDeleteMarker || value.noncurrentTransitionsEnabled || value.noncurrentExpirationEnabled || value.abortMpuEnabled;
157
+ if (!hasAtLeastOneAction) return helpers.error('any.custom', {
158
+ message: 'At least one lifecycle action must be enabled (transition, expiration, or abort multipart upload)'
159
+ });
160
+ if (value.expirationEnabled && value.expiredObjectDeleteMarker) return helpers.error('any.custom', {
161
+ message: 'Expired delete markers removal cannot be combined with Days/Date expiration on the same rule'
162
+ });
163
+ if (('tags' === value.filterType || 'and' === value.filterType) && value.abortMpuEnabled) return helpers.error('any.custom', {
164
+ message: 'Tag-based filter cannot be used with Abort Incomplete Multipart Upload'
165
+ });
166
+ return value;
161
167
  });
162
- return value;
163
- });
168
+ };
164
169
  const getArrayFieldError = (errors, fieldName, index, propertyName)=>{
165
170
  const fieldErrors = errors?.[fieldName];
166
171
  if (!fieldErrors?.[index]) return;
@@ -280,6 +285,8 @@ const generateStorageClassHelpText = ()=>{
280
285
  });
281
286
  };
282
287
  function BucketLifecycleFormPage() {
288
+ const { storageClassSelector: StorageClassSelector, storageClassLabel } = useDataBrowserUICustomization();
289
+ const hasCustomSelector = !!StorageClassSelector;
283
290
  const { bucketName, ruleId } = useParams();
284
291
  const navigate = useDataBrowserNavigate();
285
292
  const { showToast } = useToast();
@@ -300,13 +307,14 @@ function BucketLifecycleFormPage() {
300
307
  isEditMode,
301
308
  existingRule
302
309
  ]);
303
- const dynamicSchema = useMemo(()=>schema.keys({
310
+ const dynamicSchema = useMemo(()=>createSchema(hasCustomSelector).keys({
304
311
  ruleId: joi.string().required().invalid(...existingRuleIds).messages({
305
312
  'string.empty': 'Rule ID is required',
306
313
  'any.invalid': 'A rule with this ID already exists'
307
314
  })
308
315
  }), [
309
- existingRuleIds
316
+ existingRuleIds,
317
+ hasCustomSelector
310
318
  ]);
311
319
  const methods = useForm({
312
320
  resolver: joiResolver(dynamicSchema),
@@ -356,16 +364,20 @@ function BucketLifecycleFormPage() {
356
364
  const abortMpuEnabled = watch('abortMpuEnabled');
357
365
  const { mutate: setLifecycle, isPending: isSaving } = useSetBucketLifecycle();
358
366
  const transitionsHelpText = useMemo(()=>{
367
+ if (hasCustomSelector) return;
359
368
  if (!transitionsEnabled || 0 === transitionFields.length) return;
360
369
  return generateStorageClassHelpText();
361
370
  }, [
371
+ hasCustomSelector,
362
372
  transitionsEnabled,
363
373
  transitionFields.length
364
374
  ]);
365
375
  const noncurrentTransitionsHelpText = useMemo(()=>{
376
+ if (hasCustomSelector) return;
366
377
  if (!noncurrentTransitionsEnabled || 0 === noncurrentTransitionFields.length) return;
367
378
  return generateStorageClassHelpText();
368
379
  }, [
380
+ hasCustomSelector,
369
381
  noncurrentTransitionsEnabled,
370
382
  noncurrentTransitionFields.length
371
383
  ]);
@@ -388,27 +400,29 @@ function BucketLifecycleFormPage() {
388
400
  timeType: 'days',
389
401
  days: 30,
390
402
  date: '',
391
- storageClass: 'STANDARD_IA'
403
+ storageClass: hasCustomSelector ? '' : 'STANDARD_IA'
392
404
  });
393
405
  }, [
394
406
  isEditMode,
395
407
  transitionsEnabled,
396
408
  transitionFields.length,
397
- appendTransition
409
+ appendTransition,
410
+ hasCustomSelector
398
411
  ]);
399
412
  useEffect(()=>{
400
413
  const prevValue = prevNoncurrentTransitionsEnabledRef.current;
401
414
  prevNoncurrentTransitionsEnabledRef.current = noncurrentTransitionsEnabled;
402
415
  if (!isEditMode && null !== prevValue && !prevValue && noncurrentTransitionsEnabled && 0 === noncurrentTransitionFields.length) appendNoncurrentTransition({
403
416
  noncurrentDays: 30,
404
- storageClass: 'GLACIER',
417
+ storageClass: hasCustomSelector ? '' : 'GLACIER',
405
418
  newerNoncurrentVersions: 0
406
419
  });
407
420
  }, [
408
421
  isEditMode,
409
422
  noncurrentTransitionsEnabled,
410
423
  noncurrentTransitionFields.length,
411
- appendNoncurrentTransition
424
+ appendNoncurrentTransition,
425
+ hasCustomSelector
412
426
  ]);
413
427
  const prevFilterTypeRef = useRef();
414
428
  useEffect(()=>{
@@ -649,47 +663,32 @@ function BucketLifecycleFormPage() {
649
663
  helpErrorPosition: "bottom",
650
664
  content: /*#__PURE__*/ jsxs(Stack, {
651
665
  direction: "vertical",
652
- gap: "r16",
666
+ gap: "r12",
653
667
  children: [
654
- /*#__PURE__*/ jsxs(Stack, {
655
- gap: "r40",
656
- direction: "horizontal",
668
+ /*#__PURE__*/ jsxs(FormRow, {
669
+ columns: "1fr 1fr 2fr",
657
670
  children: [
658
- /*#__PURE__*/ jsx(Box, {
659
- flex: "1/2",
660
- children: /*#__PURE__*/ jsx(Text, {
661
- color: "textSecondary",
662
- children: "Time Type"
663
- })
671
+ /*#__PURE__*/ jsx(Text, {
672
+ color: "textSecondary",
673
+ children: "Time Type"
664
674
  }),
665
- /*#__PURE__*/ jsx(Box, {
666
- flex: "1/2",
667
- children: /*#__PURE__*/ jsx(Text, {
668
- color: "textSecondary",
669
- children: "Value"
670
- })
675
+ /*#__PURE__*/ jsx(Text, {
676
+ color: "textSecondary",
677
+ children: "Value"
671
678
  }),
672
- /*#__PURE__*/ jsx(Box, {
673
- width: convertRemToPixels(4)
679
+ /*#__PURE__*/ jsx(Text, {
680
+ color: "textSecondary",
681
+ children: storageClassLabel || 'Storage Class'
674
682
  }),
675
- /*#__PURE__*/ jsx(Box, {
676
- flex: "1",
677
- children: /*#__PURE__*/ jsx(Text, {
678
- color: "textSecondary",
679
- children: "Storage Class"
680
- })
681
- })
683
+ /*#__PURE__*/ jsx("span", {})
682
684
  ]
683
685
  }),
684
- transitionFields.map((field, index)=>/*#__PURE__*/ jsxs(Stack, {
685
- gap: "r20",
686
- direction: "vertical",
686
+ transitionFields.map((field, index)=>/*#__PURE__*/ jsxs(Box, {
687
687
  children: [
688
- /*#__PURE__*/ jsxs(Stack, {
689
- gap: "r20",
688
+ /*#__PURE__*/ jsxs(FormRow, {
689
+ columns: "1fr 1fr 2fr",
690
690
  children: [
691
- /*#__PURE__*/ jsx(Box, {
692
- flex: "1",
691
+ /*#__PURE__*/ jsx(FormCell, {
693
692
  children: /*#__PURE__*/ jsx(Controller, {
694
693
  name: `transitions.${index}.timeType`,
695
694
  control: control,
@@ -697,7 +696,6 @@ function BucketLifecycleFormPage() {
697
696
  id: `transition-time-type-${index}`,
698
697
  value: field.value,
699
698
  onChange: field.onChange,
700
- size: "1/3",
701
699
  children: [
702
700
  /*#__PURE__*/ jsx(Select.Option, {
703
701
  value: "days",
@@ -711,34 +709,33 @@ function BucketLifecycleFormPage() {
711
709
  })
712
710
  })
713
711
  }),
714
- /*#__PURE__*/ jsx(Box, {
715
- flex: "1",
712
+ /*#__PURE__*/ jsx(FormCell, {
716
713
  children: 'days' === watch(`transitions.${index}.timeType`) ? /*#__PURE__*/ jsx(Input, {
717
714
  id: `transition-days-${index}`,
718
715
  type: "number",
719
716
  placeholder: "Days",
720
- size: "1/2",
721
717
  ...register(`transitions.${index}.days`, {
722
718
  valueAsNumber: true
723
719
  })
724
720
  }) : /*#__PURE__*/ jsx(Input, {
725
721
  id: `transition-date-${index}`,
726
722
  type: "date",
727
- size: "1/2",
728
723
  ...register(`transitions.${index}.date`)
729
724
  })
730
725
  }),
731
- /*#__PURE__*/ jsx(Box, {
732
- flex: "1",
726
+ /*#__PURE__*/ jsx(FormCell, {
733
727
  children: /*#__PURE__*/ jsx(Controller, {
734
728
  name: `transitions.${index}.storageClass`,
735
729
  control: control,
736
- render: ({ field })=>/*#__PURE__*/ jsx(Select, {
730
+ render: ({ field })=>StorageClassSelector ? /*#__PURE__*/ jsx(StorageClassSelector, {
731
+ value: field.value,
732
+ onChange: field.onChange,
733
+ context: "lifecycle"
734
+ }) : /*#__PURE__*/ jsx(Select, {
737
735
  id: `transition-storage-class-${index}`,
738
736
  value: field.value,
739
737
  onChange: field.onChange,
740
- placeholder: "Storage Class",
741
- size: "2/3",
738
+ placeholder: storageClassLabel || 'Storage Class',
742
739
  children: storageClassOptions.map((option)=>/*#__PURE__*/ jsx(Select.Option, {
743
740
  value: option.value,
744
741
  children: option.label
@@ -753,27 +750,39 @@ function BucketLifecycleFormPage() {
753
750
  timeType: 'days',
754
751
  days: 30,
755
752
  date: '',
756
- storageClass: 'STANDARD_IA'
753
+ storageClass: hasCustomSelector ? '' : 'STANDARD_IA'
757
754
  }),
758
755
  canRemove: transitionFields.length > 1
759
756
  })
760
757
  ]
761
758
  }),
762
- getArrayFieldError(errors, 'transitions', index) && /*#__PURE__*/ jsx(Text, {
763
- color: "statusCritical",
764
- children: getArrayFieldError(errors, 'transitions', index)
759
+ getArrayFieldError(errors, 'transitions', index) && /*#__PURE__*/ jsx(Box, {
760
+ paddingTop: spacing.r4,
761
+ children: /*#__PURE__*/ jsx(Text, {
762
+ color: "statusCritical",
763
+ children: getArrayFieldError(errors, 'transitions', index)
764
+ })
765
765
  }),
766
- getArrayFieldError(errors, 'transitions', index, 'days') && /*#__PURE__*/ jsx(Text, {
767
- color: "statusCritical",
768
- children: getArrayFieldError(errors, 'transitions', index, 'days')
766
+ getArrayFieldError(errors, 'transitions', index, 'days') && /*#__PURE__*/ jsx(Box, {
767
+ paddingTop: spacing.r4,
768
+ children: /*#__PURE__*/ jsx(Text, {
769
+ color: "statusCritical",
770
+ children: getArrayFieldError(errors, 'transitions', index, 'days')
771
+ })
769
772
  }),
770
- getArrayFieldError(errors, 'transitions', index, 'storageClass') && /*#__PURE__*/ jsx(Text, {
771
- color: "statusCritical",
772
- children: getArrayFieldError(errors, 'transitions', index, 'storageClass')
773
+ getArrayFieldError(errors, 'transitions', index, 'storageClass') && /*#__PURE__*/ jsx(Box, {
774
+ paddingTop: spacing.r4,
775
+ children: /*#__PURE__*/ jsx(Text, {
776
+ color: "statusCritical",
777
+ children: getArrayFieldError(errors, 'transitions', index, 'storageClass')
778
+ })
773
779
  }),
774
- getArrayFieldError(errors, 'transitions', index, 'date') && /*#__PURE__*/ jsx(Text, {
775
- color: "statusCritical",
776
- children: getArrayFieldError(errors, 'transitions', index, 'date')
780
+ getArrayFieldError(errors, 'transitions', index, 'date') && /*#__PURE__*/ jsx(Box, {
781
+ paddingTop: spacing.r4,
782
+ children: /*#__PURE__*/ jsx(Text, {
783
+ color: "statusCritical",
784
+ children: getArrayFieldError(errors, 'transitions', index, 'date')
785
+ })
777
786
  })
778
787
  ]
779
788
  }, field.id))
@@ -902,83 +911,64 @@ function BucketLifecycleFormPage() {
902
911
  labelHelpTooltip: noncurrentTransitionsHelpText,
903
912
  content: /*#__PURE__*/ jsxs(Stack, {
904
913
  direction: "vertical",
905
- gap: "r16",
914
+ gap: "r12",
906
915
  children: [
907
- /*#__PURE__*/ jsxs(Stack, {
908
- gap: "r40",
909
- direction: "horizontal",
916
+ /*#__PURE__*/ jsxs(FormRow, {
917
+ columns: "1fr 1fr 2fr",
910
918
  children: [
911
- /*#__PURE__*/ jsx(Box, {
912
- flex: "1/2",
913
- children: /*#__PURE__*/ jsx(Text, {
914
- color: "textSecondary",
915
- children: "Noncurrent Days"
916
- })
919
+ /*#__PURE__*/ jsx(Text, {
920
+ color: "textSecondary",
921
+ children: "Noncurrent Days"
917
922
  }),
918
- /*#__PURE__*/ jsx(Box, {
919
- flex: "1/2",
920
- children: /*#__PURE__*/ jsx(Text, {
921
- color: "textSecondary",
922
- children: "Keep Versions"
923
- })
923
+ /*#__PURE__*/ jsx(Text, {
924
+ color: "textSecondary",
925
+ children: "Keep Versions"
924
926
  }),
925
- /*#__PURE__*/ jsx(Box, {
926
- flex: "1",
927
- children: /*#__PURE__*/ jsx(Text, {
928
- color: "textSecondary",
929
- children: "Storage Class"
930
- })
931
- })
927
+ /*#__PURE__*/ jsx(Text, {
928
+ color: "textSecondary",
929
+ children: storageClassLabel || 'Storage Class'
930
+ }),
931
+ /*#__PURE__*/ jsx("span", {})
932
932
  ]
933
933
  }),
934
- noncurrentTransitionFields.map((field, index)=>/*#__PURE__*/ jsxs(Stack, {
935
- gap: "r16",
936
- direction: "vertical",
934
+ noncurrentTransitionFields.map((field, index)=>/*#__PURE__*/ jsxs(Box, {
937
935
  children: [
938
- /*#__PURE__*/ jsxs(Stack, {
939
- gap: "r16",
936
+ /*#__PURE__*/ jsxs(FormRow, {
937
+ columns: "1fr 1fr 2fr",
940
938
  children: [
941
- /*#__PURE__*/ jsx(Box, {
942
- flex: "1",
939
+ /*#__PURE__*/ jsx(FormCell, {
943
940
  children: /*#__PURE__*/ jsx(Input, {
944
941
  id: `noncurrent-transition-days-${index}`,
945
942
  type: "number",
946
943
  placeholder: "Noncurrent days",
947
- size: "1/3",
948
944
  ...register(`noncurrentTransitions.${index}.noncurrentDays`, {
949
945
  valueAsNumber: true
950
946
  })
951
947
  })
952
948
  }),
953
- /*#__PURE__*/ jsx(Box, {
954
- width: convertRemToPixels(2)
955
- }),
956
- /*#__PURE__*/ jsx(Box, {
957
- flex: "1",
949
+ /*#__PURE__*/ jsx(FormCell, {
958
950
  children: /*#__PURE__*/ jsx(Input, {
959
951
  id: `noncurrent-transition-keep-versions-${index}`,
960
952
  type: "number",
961
953
  placeholder: "Keep versions",
962
- size: "1/3",
963
954
  ...register(`noncurrentTransitions.${index}.newerNoncurrentVersions`, {
964
955
  valueAsNumber: true
965
956
  })
966
957
  })
967
958
  }),
968
- /*#__PURE__*/ jsx(Box, {
969
- width: convertRemToPixels(0.5)
970
- }),
971
- /*#__PURE__*/ jsx(Box, {
972
- flex: "1",
959
+ /*#__PURE__*/ jsx(FormCell, {
973
960
  children: /*#__PURE__*/ jsx(Controller, {
974
961
  name: `noncurrentTransitions.${index}.storageClass`,
975
962
  control: control,
976
- render: ({ field })=>/*#__PURE__*/ jsx(Select, {
963
+ render: ({ field })=>StorageClassSelector ? /*#__PURE__*/ jsx(StorageClassSelector, {
964
+ value: field.value,
965
+ onChange: field.onChange,
966
+ context: "lifecycle"
967
+ }) : /*#__PURE__*/ jsx(Select, {
977
968
  id: `noncurrent-transition-storage-class-${index}`,
978
969
  value: field.value,
979
970
  onChange: field.onChange,
980
- placeholder: "Storage Class",
981
- size: "2/3",
971
+ placeholder: storageClassLabel || 'Storage Class',
982
972
  children: storageClassOptions.map((option)=>/*#__PURE__*/ jsx(Select.Option, {
983
973
  value: option.value,
984
974
  children: option.label
@@ -991,20 +981,26 @@ function BucketLifecycleFormPage() {
991
981
  onRemove: ()=>removeNoncurrentTransition(index),
992
982
  onAdd: ()=>appendNoncurrentTransition({
993
983
  noncurrentDays: 30,
994
- storageClass: 'GLACIER',
984
+ storageClass: hasCustomSelector ? '' : 'GLACIER',
995
985
  newerNoncurrentVersions: 0
996
986
  }),
997
987
  canRemove: noncurrentTransitionFields.length > 1
998
988
  })
999
989
  ]
1000
990
  }),
1001
- getArrayFieldError(errors, 'noncurrentTransitions', index, 'noncurrentDays') && /*#__PURE__*/ jsx(Text, {
1002
- color: "statusCritical",
1003
- children: getArrayFieldError(errors, 'noncurrentTransitions', index, 'noncurrentDays')
991
+ getArrayFieldError(errors, 'noncurrentTransitions', index, 'noncurrentDays') && /*#__PURE__*/ jsx(Box, {
992
+ paddingTop: spacing.r4,
993
+ children: /*#__PURE__*/ jsx(Text, {
994
+ color: "statusCritical",
995
+ children: getArrayFieldError(errors, 'noncurrentTransitions', index, 'noncurrentDays')
996
+ })
1004
997
  }),
1005
- getArrayFieldError(errors, 'noncurrentTransitions', index, 'storageClass') && /*#__PURE__*/ jsx(Text, {
1006
- color: "statusCritical",
1007
- children: getArrayFieldError(errors, 'noncurrentTransitions', index, 'storageClass')
998
+ getArrayFieldError(errors, 'noncurrentTransitions', index, 'storageClass') && /*#__PURE__*/ jsx(Box, {
999
+ paddingTop: spacing.r4,
1000
+ children: /*#__PURE__*/ jsx(Text, {
1001
+ color: "statusCritical",
1002
+ children: getArrayFieldError(errors, 'noncurrentTransitions', index, 'storageClass')
1003
+ })
1008
1004
  })
1009
1005
  ]
1010
1006
  }, field.id))