diff --git a/packages/react-storage/src/components/StorageBrowser/composables/ActionStart.tsx b/packages/react-storage/src/components/StorageBrowser/composables/ActionStart.tsx
new file mode 100644
index 00000000000..daa3a8e9f4e
--- /dev/null
+++ b/packages/react-storage/src/components/StorageBrowser/composables/ActionStart.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { ButtonElement } from '../context/elements';
+import { CLASS_BASE } from '../views/constants';
+
+export interface ActionStartProps {
+ onStart?: () => void;
+ isDisabled?: boolean;
+ label?: string;
+}
+
+export const ActionStart = ({
+ onStart,
+ isDisabled,
+ label,
+}: ActionStartProps): React.JSX.Element => (
+
+ {label}
+
+);
diff --git a/packages/react-storage/src/components/StorageBrowser/composables/__tests__/ActionStart.spec.tsx b/packages/react-storage/src/components/StorageBrowser/composables/__tests__/ActionStart.spec.tsx
new file mode 100644
index 00000000000..7cf3e408565
--- /dev/null
+++ b/packages/react-storage/src/components/StorageBrowser/composables/__tests__/ActionStart.spec.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { ActionStart } from '../ActionStart';
+import { CLASS_BASE } from '../../views/constants';
+
+describe('ActionStart', () => {
+ it('renders a button element', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ });
+
+ it('renders a button with the expected className', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toHaveClass(`${CLASS_BASE}__action-start`);
+ });
+
+ it('renders a button with the expected text', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toHaveTextContent('Start');
+ });
+
+ it('renders a button with the expected disabled state', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toBeDisabled();
+ });
+});
diff --git a/packages/react-storage/src/components/StorageBrowser/composables/types.ts b/packages/react-storage/src/components/StorageBrowser/composables/types.ts
index ec6e6c9bc72..e4da6a57559 100644
--- a/packages/react-storage/src/components/StorageBrowser/composables/types.ts
+++ b/packages/react-storage/src/components/StorageBrowser/composables/types.ts
@@ -1,11 +1,13 @@
import { DataTableProps } from './DataTable';
import { StatusDisplayProps } from './StatusDisplay';
import { DataRefreshProps } from './DataRefresh';
+import { ActionStartProps } from './ActionStart';
export interface Composables {
DataRefresh: React.ComponentType;
DataTable: React.ComponentType;
StatusDisplay: React.ComponentType;
+ ActionStart: React.ComponentType;
}
export interface ComposablesContext {
diff --git a/packages/react-storage/src/components/StorageBrowser/controls/ActionStartControl.tsx b/packages/react-storage/src/components/StorageBrowser/controls/ActionStartControl.tsx
new file mode 100644
index 00000000000..57cb5620473
--- /dev/null
+++ b/packages/react-storage/src/components/StorageBrowser/controls/ActionStartControl.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+import { ControlProps } from './types';
+import { useResolvedComposable } from './hooks/useResolvedComposable';
+import { ActionStart } from '../composables/ActionStart';
+import { useActionStart } from './hooks/useActionStart';
+import { ViewElement } from '../context/elements';
+
+export const ActionStartControl = ({
+ className,
+}: ControlProps): React.JSX.Element | null => {
+ const props = useActionStart();
+ const ResolvedActionStart = useResolvedComposable(ActionStart, 'ActionStart');
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-storage/src/components/StorageBrowser/controls/__tests__/ActionStartControl.spec.tsx b/packages/react-storage/src/components/StorageBrowser/controls/__tests__/ActionStartControl.spec.tsx
new file mode 100644
index 00000000000..c48f5d608dc
--- /dev/null
+++ b/packages/react-storage/src/components/StorageBrowser/controls/__tests__/ActionStartControl.spec.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { ActionStartControl } from '../ActionStartControl';
+import * as useActionStartModule from '../hooks/useActionStart';
+
+describe('ActionStartControl', () => {
+ const useActionStartSpy = jest.spyOn(useActionStartModule, 'useActionStart');
+
+ afterEach(() => {
+ useActionStartSpy.mockClear();
+ });
+
+ afterAll(() => {
+ useActionStartSpy.mockRestore();
+ });
+
+ it('renders', () => {
+ useActionStartSpy.mockReturnValue({
+ isDisabled: false,
+ onStart: jest.fn(),
+ label: 'Start',
+ });
+ render();
+
+ const button = screen.getByRole('button', {
+ name: 'Start',
+ });
+
+ expect(button).toBeInTheDocument();
+ });
+
+ it('renders with custom label', () => {
+ useActionStartSpy.mockReturnValue({
+ isDisabled: false,
+ onStart: jest.fn(),
+ label: 'Custom Label',
+ });
+ render();
+
+ const button = screen.getByRole('button', {
+ name: 'Custom Label',
+ });
+
+ expect(button).toBeInTheDocument();
+ });
+
+ it('calls onStart when button is clicked', () => {
+ const mockOnStart = jest.fn();
+ useActionStartSpy.mockReturnValue({
+ isDisabled: false,
+ onStart: mockOnStart,
+ label: 'Start',
+ });
+ render();
+
+ const button = screen.getByRole('button', {
+ name: 'Start',
+ });
+
+ button.click();
+
+ expect(mockOnStart).toHaveBeenCalled();
+ });
+});
diff --git a/packages/react-storage/src/components/StorageBrowser/controls/hooks/__tests__/useActionStart.spec.ts b/packages/react-storage/src/components/StorageBrowser/controls/hooks/__tests__/useActionStart.spec.ts
new file mode 100644
index 00000000000..49190cf635c
--- /dev/null
+++ b/packages/react-storage/src/components/StorageBrowser/controls/hooks/__tests__/useActionStart.spec.ts
@@ -0,0 +1,48 @@
+import * as controlsContextModule from '../../../controls/context';
+import { ControlsContext } from '../../types';
+import { useActionStart } from '../useActionStart';
+
+describe('useActionStart', () => {
+ const controlsContext: ControlsContext = {
+ data: {
+ actionStartLabel: 'Start',
+ },
+ actionsConfig: {
+ isCancelable: true,
+ type: 'BATCH_ACTION',
+ },
+ onActionStart: jest.fn(),
+ };
+
+ const useControlsContextSpy = jest.spyOn(
+ controlsContextModule,
+ 'useControlsContext'
+ );
+
+ afterEach(() => {
+ useControlsContextSpy.mockClear();
+ });
+
+ afterAll(() => {
+ useControlsContextSpy.mockRestore();
+ });
+
+ it('returns object as it is received from ControlsContext', () => {
+ useControlsContextSpy.mockReturnValue(controlsContext);
+
+ expect(useActionStart()).toStrictEqual({
+ label: controlsContext.data.actionStartLabel,
+ onStart: expect.any(Function),
+ isDisabled: controlsContext.data.isActionStartDisabled,
+ });
+ });
+
+ it('calls onActionStart from ControlsContext when onStart is called', () => {
+ useControlsContextSpy.mockReturnValue(controlsContext);
+
+ const { onStart } = useActionStart();
+ onStart!();
+
+ expect(controlsContext.onActionStart).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/react-storage/src/components/StorageBrowser/controls/hooks/useActionStart.tsx b/packages/react-storage/src/components/StorageBrowser/controls/hooks/useActionStart.tsx
new file mode 100644
index 00000000000..a461b09ef29
--- /dev/null
+++ b/packages/react-storage/src/components/StorageBrowser/controls/hooks/useActionStart.tsx
@@ -0,0 +1,14 @@
+import { ActionStartProps } from '../../composables/ActionStart';
+import { useControlsContext } from '../../controls/context';
+
+export const useActionStart = (): ActionStartProps => {
+ const {
+ data: { actionStartLabel, isActionStartDisabled },
+ onActionStart,
+ } = useControlsContext();
+ return {
+ label: actionStartLabel,
+ isDisabled: isActionStartDisabled,
+ onStart: onActionStart,
+ };
+};
diff --git a/packages/react-storage/src/components/StorageBrowser/controls/types.ts b/packages/react-storage/src/components/StorageBrowser/controls/types.ts
index a99cf419de6..2314159b5d9 100644
--- a/packages/react-storage/src/components/StorageBrowser/controls/types.ts
+++ b/packages/react-storage/src/components/StorageBrowser/controls/types.ts
@@ -35,6 +35,8 @@ export interface ControlsContext {
taskCounts?: TaskCounts;
tableData?: TableData;
isDataRefreshDisabled?: boolean;
+ actionStartLabel?: string;
+ isActionStartDisabled?: boolean;
};
actionsConfig?: {
type:
@@ -45,4 +47,5 @@ export interface ControlsContext {
isCancelable?: boolean;
};
onRefresh?: () => void;
+ onActionStart?: () => void;
}
diff --git a/packages/react-storage/src/components/StorageBrowser/views/Controls/Controls.tsx b/packages/react-storage/src/components/StorageBrowser/views/Controls/Controls.tsx
index 599d2c32019..35cd4b734f7 100644
--- a/packages/react-storage/src/components/StorageBrowser/views/Controls/Controls.tsx
+++ b/packages/react-storage/src/components/StorageBrowser/views/Controls/Controls.tsx
@@ -7,7 +7,6 @@ import { MessageControl } from './Message';
import { NavigateControl } from './Navigate';
import { OverwriteControl } from './Overwrite';
import { PaginateControl } from './Paginate';
-import { PrimaryControl } from './Primary';
import { SearchControl } from './Search';
import { TableControl } from './Table';
import { TitleControl } from './Title';
@@ -21,7 +20,6 @@ export interface Controls {
Message: typeof MessageControl;
Overwrite: typeof OverwriteControl;
Paginate: typeof PaginateControl;
- Primary: typeof PrimaryControl;
Navigate: typeof NavigateControl;
Search: typeof SearchControl;
Table: typeof TableControl;
@@ -37,7 +35,6 @@ export const Controls: Controls = {
Message: MessageControl,
Overwrite: OverwriteControl,
Paginate: PaginateControl,
- Primary: PrimaryControl,
Navigate: NavigateControl,
Search: SearchControl,
Table: TableControl,
diff --git a/packages/react-storage/src/components/StorageBrowser/views/Controls/Primary.tsx b/packages/react-storage/src/components/StorageBrowser/views/Controls/Primary.tsx
deleted file mode 100644
index 41cf65c7649..00000000000
--- a/packages/react-storage/src/components/StorageBrowser/views/Controls/Primary.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-
-import { CLASS_BASE } from '../constants';
-import { ButtonElement } from '../../context/elements/definitions';
-
-const BLOCK_NAME = `${CLASS_BASE}__primary`;
-interface PrimaryControlProps {
- onClick?: () => void;
- disabled?: boolean;
- children?: React.ReactNode;
-}
-
-export const PrimaryControl = ({
- onClick,
- disabled,
- children,
-}: PrimaryControlProps): React.JSX.Element => (
-
- {children ?? 'Start'}
-
-);
diff --git a/packages/react-storage/src/components/StorageBrowser/views/Controls/__tests__/Primary.spec.tsx b/packages/react-storage/src/components/StorageBrowser/views/Controls/__tests__/Primary.spec.tsx
deleted file mode 100644
index 763840fe8ea..00000000000
--- a/packages/react-storage/src/components/StorageBrowser/views/Controls/__tests__/Primary.spec.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import { PrimaryControl } from '../Primary';
-
-describe('PrimaryControl', () => {
- it('renders the PrimaryControl', () => {
- render();
-
- const button = screen.getByRole('button', {
- name: 'Start',
- });
-
- expect(button).toBeInTheDocument();
- });
-});
diff --git a/packages/react-storage/src/components/StorageBrowser/views/Controls/index.ts b/packages/react-storage/src/components/StorageBrowser/views/Controls/index.ts
index fcddfad8b60..96dfbd0f956 100644
--- a/packages/react-storage/src/components/StorageBrowser/views/Controls/index.ts
+++ b/packages/react-storage/src/components/StorageBrowser/views/Controls/index.ts
@@ -7,7 +7,6 @@ export { MessageControl } from './Message';
export { NavigateControl, NavigateItem } from './Navigate';
export { OverwriteControl } from './Overwrite';
export { PaginateControl } from './Paginate';
-export { PrimaryControl } from './Primary';
export { SearchControl } from './Search';
export { TitleControl } from './Title';
export { TableControl, LocationDetailViewTable } from './Table';
diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/CreateFolderControls.tsx b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/CreateFolderControls.tsx
index 2cede4cc6b9..b0d2a5695f6 100644
--- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/CreateFolderControls.tsx
+++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/CreateFolderControls.tsx
@@ -9,8 +9,12 @@ import { useStore } from '../../providers/store';
import { Controls } from '../Controls';
import { Title } from './Controls/Title';
+import { ActionStartControl } from '../../controls/ActionStartControl';
+import { ControlsContext } from '../../controls/types';
+import { ControlsContextProvider } from '../../controls/context';
+import { CLASS_BASE } from '../constants';
-const { Exit, Message, Primary } = Controls;
+const { Exit, Message } = Controls;
export const isValidFolderName = (name: string | undefined): boolean =>
!!name?.length && !name.includes('/');
@@ -88,31 +92,34 @@ export const CreateFolderControls = ({
handleCreateAction({ prefix: '', options: { reset: true } });
};
- const primaryProps =
- result?.status === 'COMPLETE'
- ? {
- onClick: () => {
- handleClose();
- },
- children: 'Folder created',
- }
- : {
- onClick: () => {
- handleCreateFolder();
- },
- children: 'Create Folder',
- disabled: !folderName || !!fieldValidationError,
- };
+ const hasCompletedStatus = result?.status === 'COMPLETE';
+
+ // FIXME: Eventually comes from useView hook
+ const contextValue: ControlsContext = {
+ data: {
+ actionStartLabel: hasCompletedStatus ? 'Folder created' : 'Create Folder',
+ isActionStartDisabled: !hasCompletedStatus
+ ? !folderName || !!fieldValidationError
+ : undefined,
+ },
+ actionsConfig: {
+ type: 'SINGLE_ACTION',
+ isCancelable: true,
+ },
+ onActionStart: hasCompletedStatus ? handleClose : handleCreateFolder,
+ };
return (
- <>
+
{
handleClose();
}}
/>
-
+
) : null}
- >
+
);
};
diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/DeleteFilesControls.tsx b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/DeleteFilesControls.tsx
index a355c2657cf..80368f1a0f5 100644
--- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/DeleteFilesControls.tsx
+++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/DeleteFilesControls.tsx
@@ -11,8 +11,9 @@ import { StatusDisplayControl } from '../../controls/StatusDisplayControl';
import { ControlsContext } from '../../controls/types';
import { useStore } from '../../providers/store';
import { getDeleteActionViewTableData } from './utils';
+import { ActionStartControl } from '../../controls/ActionStartControl';
-const { Exit, Primary } = Controls;
+const { Exit } = Controls;
export const DeleteFilesControls = ({
onClose: _onClose,
@@ -40,22 +41,21 @@ export const DeleteFilesControls = ({
});
const contextValue: ControlsContext = {
- data: { taskCounts, tableData },
+ data: {
+ taskCounts,
+ tableData,
+ isActionStartDisabled: disablePrimary,
+ actionStartLabel: 'Start',
+ },
actionsConfig: { type: 'BATCH_ACTION', isCancelable: true },
+ onActionStart: onStart,
};
return (
- {
- onStart();
- }}
- >
- Start
-
+
void) | undefined;
@@ -329,8 +330,24 @@ export const UploadControls = ({
// FIXME: Eventually comes from useView hook
const contextValue: ControlsContext = {
- data: { taskCounts },
- actionsConfig: { type: 'BATCH_ACTION', isCancelable: true },
+ data: {
+ taskCounts,
+ isActionStartDisabled: disablePrimary,
+ actionStartLabel: 'Start',
+ },
+ actionsConfig: {
+ type: 'BATCH_ACTION',
+ isCancelable: true,
+ },
+ onActionStart: () => {
+ if (hasInvalidPrefix) return;
+
+ handleProcess({
+ config: getInput(),
+ prefix,
+ options: { preventOverwrite },
+ });
+ },
};
return (
@@ -347,20 +364,7 @@ export const UploadControls = ({
}}
/>
- {
- if (hasInvalidPrefix) return;
-
- handleProcess({
- config: getInput(),
- prefix,
- options: { preventOverwrite },
- });
- }}
- >
- Start
-
+