diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f0d096ae..942330234 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Added
- Extended existing height and readOnly props from `CodeEditorProps` to `AutoSuggestionProps` & `ExtendedCodeEditorProps` to be configurable from ``
+- Added additional actions to ``
## [24.3.0] - 2025-06-05
diff --git a/src/cmem/ActivityControl/ActivityControlWidget.stories.tsx b/src/cmem/ActivityControl/ActivityControlWidget.stories.tsx
index 21c1bd13c..952955b33 100644
--- a/src/cmem/ActivityControl/ActivityControlWidget.stories.tsx
+++ b/src/cmem/ActivityControl/ActivityControlWidget.stories.tsx
@@ -1,9 +1,17 @@
-import React from "react";
+import React, { useMemo, useState } from "react";
import { loremIpsum } from "react-lorem-ipsum";
+import { OverlaysProvider } from "@blueprintjs/core";
import { Meta, StoryFn } from "@storybook/react";
import { helpersArgTypes } from "../../../.storybook/helpers";
-import { ActivityControlWidget, Tag, TagList } from "../../../index";
+import {
+ ActivityControlWidget,
+ ActivityControlWidgetAction,
+ IconButton,
+ SimpleDialog,
+ Tag,
+ TagList,
+} from "../../../index";
export default {
title: "Cmem/ActivityControlWidget",
@@ -19,7 +27,7 @@ const Template: StoryFn = (args) => = (args) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const params = useMemo(
+ () => ({
+ ...commonWidgetArgs,
+ ...args,
+ additionalActions: args.additionalActions ?? [
+ setIsOpen(true)} />,
+ ],
+ }),
+ []
+ );
+
+ return (
+
+
+ setIsOpen(false)}
+ canOutsideClickClose
+ canEscapeKeyClose
+ >
+ Modal content
+
+
+ );
+};
diff --git a/src/cmem/ActivityControl/ActivityControlWidget.tsx b/src/cmem/ActivityControl/ActivityControlWidget.tsx
index e59ef64ca..efda5060f 100644
--- a/src/cmem/ActivityControl/ActivityControlWidget.tsx
+++ b/src/cmem/ActivityControl/ActivityControlWidget.tsx
@@ -81,6 +81,10 @@ export interface ActivityControlWidgetProps extends TestableComponent {
* execution timer messages for waiting and running times.
*/
timerExecutionMsg?: JSX.Element | null;
+ /**
+ * additional actions that can serve as a complex component, positioned between the default actions and the context menu
+ */
+ additionalActions?: React.ReactElement[];
}
interface IActivityContextMenu extends TestableComponent {
@@ -110,11 +114,13 @@ interface IActivityMenuAction extends ActivityControlWidgetAction {
/** Shows the status of activities and supports actions on these activities. */
export function ActivityControlWidget(props: ActivityControlWidgetProps) {
const {
- "data-test-id": dataTestId,
+ "data-test-id": dataTestIdLegacy,
+ "data-testid": dataTestId,
progressBar,
progressSpinner,
activityActions,
activityContextMenu,
+ additionalActions,
small,
border,
hasSpacing,
@@ -126,10 +132,19 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
} = props;
const spinnerClassNames = (progressSpinner?.className ?? "") + ` ${eccgui}-spinner--permanent`;
const widget = (
-
+
{progressBar && }
{(progressSpinner || progressSpinnerFinishedIcon) && (
-
+
{progressSpinnerFinishedIcon ? (
React.cloneElement(progressSpinnerFinishedIcon as JSX.Element, { small, large: !small })
) : (
@@ -145,13 +160,21 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
)}
{props.label && (
-
+
{React.cloneElement(labelWrapper, {}, props.label)}
{timerExecutionMsg && (props.statusMessage || tags) && <> ({timerExecutionMsg})>}
)}
{(props.statusMessage || tags) && (
-
+
{tags}
{props.statusMessage && (
@@ -172,21 +195,35 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
)}
{timerExecutionMsg && !(props.statusMessage || tags) && (
- {timerExecutionMsg}
+
+ {timerExecutionMsg}
+
)}
-
+
{activityActions &&
activityActions.map((action, idx) => {
return (
);
})}
+ {additionalActions}
{activityContextMenu && activityContextMenu.menuItems.length > 0 && (
{
+ it("Renders basic widget with actions and handles clicks", () => {
+ const mockAction1 = jest.fn();
+ const mockAction2 = jest.fn();
+ const actions: ActivityControlWidgetAction[] = [
+ {
+ "data-testid": "action-1",
+ icon: "item-reload",
+ action: mockAction1,
+ tooltip: "Action 1",
+ },
+ {
+ "data-testid": "action-2",
+ icon: "item-start",
+ action: mockAction2,
+ tooltip: "Action 2",
+ },
+ ];
+
+ render(
+
+ );
+
+ const button1 = screen.getByTestId("action-1");
+ const button2 = screen.getByTestId("action-2");
+
+ const label = screen.getByTestId("basic-widget-label");
+ const statusMessage = screen.getByTestId("basic-widget-status-message");
+ const actionsContainer = screen.getByTestId("basic-widget-actions");
+
+ expect(label).toBeInTheDocument();
+ expect(statusMessage).toBeInTheDocument();
+ expect(actionsContainer).toBeInTheDocument();
+
+ expect(label).toHaveTextContent("Basic widget");
+ expect(statusMessage).toHaveTextContent("Status message");
+
+ expect(button1).toBeInTheDocument();
+ expect(button2).toBeInTheDocument();
+
+ fireEvent.click(button1);
+ expect(mockAction1).toHaveBeenCalledTimes(1);
+
+ fireEvent.click(button2);
+ expect(mockAction2).toHaveBeenCalledTimes(1);
+ });
+
+ it("Renders widget with tags", () => {
+ const tags = (
+
+ Tag one
+ Other tag
+
+ );
+ render();
+
+ const label = screen.getByTestId("widget-with-tags-label");
+ const statusMessage = screen.getByTestId("widget-with-tags-status-message");
+
+ expect(label).toBeInTheDocument();
+ expect(statusMessage).toBeInTheDocument();
+
+ expect(label).toHaveTextContent("Widget with tags");
+ expect(statusMessage).toHaveTextContent("Tag one");
+ expect(statusMessage).toHaveTextContent("Other tag");
+ });
+
+ it("Renders widget with additional actions and handles click", () => {
+ const mockAction = jest.fn();
+ const additionalActions = [
+ ,
+ ];
+ render();
+
+ const customButton = screen.getByTestId("additional-action");
+ expect(customButton).toBeInTheDocument();
+
+ fireEvent.click(customButton);
+ expect(mockAction).toHaveBeenCalledTimes(1);
+ });
+});