nodeRef}
+ aria-label={action.getDisplayName(actionContext)}
+ title={action.getDisplayName(actionContext)}
+ data-test-subj={`dataGridColumnCellAction-${action.id}`}
+ iconType={action.getIconType(actionContext)!}
+ onClick={() => {
+ action.execute(actionContext);
+ }}
+ >
+ {action.getDisplayName(actionContext)}
+ extraContentNodeRef} />
+
+ );
+ };
diff --git a/packages/kbn-cell-actions/src/hooks/use_load_actions.test.ts b/packages/kbn-cell-actions/src/hooks/use_load_actions.test.ts
new file mode 100644
index 0000000000000..74cb5091a5efb
--- /dev/null
+++ b/packages/kbn-cell-actions/src/hooks/use_load_actions.test.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { act, renderHook } from '@testing-library/react-hooks';
+import { makeAction, makeActionContext } from '../mocks/helpers';
+import { useBulkLoadActions, useLoadActions, useLoadActionsFn } from './use_load_actions';
+
+const action = makeAction('action-1', 'icon', 1);
+const mockGetActions = jest.fn(async () => [action]);
+jest.mock('../context/cell_actions_context', () => ({
+ useCellActionsContext: () => ({ getActions: mockGetActions }),
+}));
+
+describe('useLoadActions', () => {
+ const actionContext = makeActionContext();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('loads actions when useLoadActions called', async () => {
+ const { result, waitForNextUpdate } = renderHook(useLoadActions, {
+ initialProps: actionContext,
+ });
+
+ expect(result.current.value).toBeUndefined();
+ expect(result.current.loading).toEqual(true);
+ expect(mockGetActions).toHaveBeenCalledTimes(1);
+ expect(mockGetActions).toHaveBeenCalledWith(actionContext);
+
+ await waitForNextUpdate();
+
+ expect(result.current.value).toEqual([action]);
+ expect(result.current.loading).toEqual(false);
+ });
+
+ it('loads actions when useLoadActionsFn function is called', async () => {
+ const { result, waitForNextUpdate } = renderHook(useLoadActionsFn);
+ const [{ value: valueBeforeCall, loading: loadingBeforeCall }, loadActions] = result.current;
+
+ expect(valueBeforeCall).toBeUndefined();
+ expect(loadingBeforeCall).toEqual(false);
+ expect(mockGetActions).not.toHaveBeenCalled();
+
+ act(() => {
+ loadActions(actionContext);
+ });
+
+ const [{ value: valueAfterCall, loading: loadingAfterCall }] = result.current;
+ expect(valueAfterCall).toBeUndefined();
+ expect(loadingAfterCall).toEqual(true);
+ expect(mockGetActions).toHaveBeenCalledTimes(1);
+ expect(mockGetActions).toHaveBeenCalledWith(actionContext);
+
+ await waitForNextUpdate();
+
+ const [{ value: valueAfterUpdate, loading: loadingAfterUpdate }] = result.current;
+ expect(valueAfterUpdate).toEqual([action]);
+ expect(loadingAfterUpdate).toEqual(false);
+ });
+
+ it('loads bulk actions array when useBulkLoadActions is called', async () => {
+ const actionContext2 = makeActionContext({ trigger: { id: 'triggerId2' } });
+ const actionContexts = [actionContext, actionContext2];
+ const { result, waitForNextUpdate } = renderHook(useBulkLoadActions, {
+ initialProps: actionContexts,
+ });
+
+ expect(result.current.value).toBeUndefined();
+ expect(result.current.loading).toEqual(true);
+ expect(mockGetActions).toHaveBeenCalledTimes(2);
+ expect(mockGetActions).toHaveBeenCalledWith(actionContext);
+ expect(mockGetActions).toHaveBeenCalledWith(actionContext2);
+
+ await waitForNextUpdate();
+
+ expect(result.current.value).toEqual([[action], [action]]);
+ expect(result.current.loading).toEqual(false);
+ });
+});
diff --git a/packages/kbn-cell-actions/src/hooks/use_load_actions.ts b/packages/kbn-cell-actions/src/hooks/use_load_actions.ts
new file mode 100644
index 0000000000000..85d8f64042f93
--- /dev/null
+++ b/packages/kbn-cell-actions/src/hooks/use_load_actions.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import useAsync from 'react-use/lib/useAsync';
+import useAsyncFn from 'react-use/lib/useAsyncFn';
+import { useCellActionsContext } from '../context/cell_actions_context';
+import { CellActionExecutionContext } from '../types';
+
+/**
+ * Performs the getActions async call and returns its value
+ */
+export const useLoadActions = (context: CellActionExecutionContext) => {
+ const { getActions } = useCellActionsContext();
+ return useAsync(() => getActions(context), []);
+};
+
+/**
+ * Returns a function to perform the getActions async call
+ */
+export const useLoadActionsFn = () => {
+ const { getActions } = useCellActionsContext();
+ return useAsyncFn(getActions, []);
+};
+
+/**
+ * Groups getActions calls for an array of contexts in one async bulk operation
+ */
+export const useBulkLoadActions = (contexts: CellActionExecutionContext[]) => {
+ const { getActions } = useCellActionsContext();
+ return useAsync(() => Promise.all(contexts.map((context) => getActions(context))), []);
+};
diff --git a/packages/kbn-cell-actions/src/index.ts b/packages/kbn-cell-actions/src/index.ts
new file mode 100644
index 0000000000000..69c4b0fbc6e13
--- /dev/null
+++ b/packages/kbn-cell-actions/src/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { CellActions } from './components';
+export { CellActionsProvider } from './context';
+export { useDataGridColumnsCellActions, type UseDataGridColumnsCellActionsProps } from './hooks';
+export { CellActionsMode } from './types';
+export type { CellAction, CellActionExecutionContext } from './types';
diff --git a/packages/kbn-cell-actions/src/mocks/helpers.ts b/packages/kbn-cell-actions/src/mocks/helpers.ts
new file mode 100644
index 0000000000000..21f1047f384ad
--- /dev/null
+++ b/packages/kbn-cell-actions/src/mocks/helpers.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { CellActionExecutionContext } from '../types';
+
+export const makeAction = (actionsName: string, icon: string = 'icon', order?: number) => ({
+ id: actionsName,
+ type: actionsName,
+ order,
+ getIconType: () => icon,
+ getDisplayName: () => actionsName,
+ getDisplayNameTooltip: () => actionsName,
+ isCompatible: () => Promise.resolve(true),
+ execute: () => {
+ alert(actionsName);
+ return Promise.resolve();
+ },
+});
+
+export const makeActionContext = (
+ override: Partial = {}
+): CellActionExecutionContext => ({
+ trigger: { id: 'triggerId' },
+ field: {
+ name: 'fieldName',
+ type: 'keyword',
+ },
+ ...override,
+});
diff --git a/packages/kbn-cell-actions/src/types.ts b/packages/kbn-cell-actions/src/types.ts
new file mode 100644
index 0000000000000..592a412e7fb68
--- /dev/null
+++ b/packages/kbn-cell-actions/src/types.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type {
+ Action,
+ ActionExecutionContext,
+ UiActionsService,
+} from '@kbn/ui-actions-plugin/public';
+
+export type CellAction = Action;
+
+export interface CellActionsProviderProps {
+ /**
+ * Please assign `uiActions.getTriggerCompatibleActions` function.
+ * This function should return a list of actions for a triggerId that are compatible with the provided context.
+ */
+ getTriggerCompatibleActions: UiActionsService['getTriggerCompatibleActions'];
+}
+
+export type GetActions = (context: CellActionExecutionContext) => Promise;
+
+export interface CellActionField {
+ /**
+ * Field name.
+ * Example: 'host.name'
+ */
+ name: string;
+ /**
+ * Field type.
+ * Example: 'keyword'
+ */
+ type: string;
+ /**
+ * Field value.
+ * Example: 'My-Laptop'
+ */
+ value?: string | string[] | null;
+}
+
+export interface PartitionedActions {
+ extraActions: CellAction[];
+ visibleActions: CellAction[];
+}
+
+export interface CellActionExecutionContext extends ActionExecutionContext {
+ field: CellActionField;
+ /**
+ * Ref to a DOM node where the action can add custom HTML.
+ */
+ extraContentNodeRef?: React.MutableRefObject;
+
+ /**
+ * Ref to the node where the cell action are rendered.
+ */
+ nodeRef?: React.MutableRefObject;
+
+ /**
+ * Extra configurations for actions.
+ */
+ metadata?: Record;
+}
+
+export enum CellActionsMode {
+ HOVER = 'hover',
+ INLINE = 'inline',
+}
+
+export interface CellActionsProps {
+ /**
+ * Common set of properties used by most actions.
+ */
+ field: CellActionField;
+ /**
+ * The trigger in which the actions are registered.
+ */
+ triggerId: string;
+ /**
+ * UI configuration. Possible options are `HOVER` and `INLINE`.
+ *
+ * `HOVER` shows the actions when the children component is hovered.
+ *
+ * `INLINE` always shows the actions.
+ */
+ mode: CellActionsMode;
+
+ /**
+ * It displays a tooltip for every action button when `true`.
+ */
+ showActionTooltips?: boolean;
+ /**
+ * It shows 'more actions' button when the number of actions is bigger than this parameter.
+ */
+ visibleCellActions?: number;
+ /**
+ * Custom set of properties used by some actions.
+ * An action might require a specific set of metadata properties to render.
+ * This data is sent directly to actions.
+ */
+ metadata?: Record;
+}
diff --git a/packages/kbn-cell-actions/tsconfig.json b/packages/kbn-cell-actions/tsconfig.json
new file mode 100644
index 0000000000000..63c76dfbfaa45
--- /dev/null
+++ b/packages/kbn-cell-actions/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "target/types",
+ "types": [
+ "jest",
+ "node",
+ "react",
+ "@emotion/react/types/css-prop",
+ "@testing-library/jest-dom",
+ "@testing-library/react"
+ ]
+ },
+ "include": ["**/*.ts", "**/*.tsx"],
+ "kbn_references": [
+ "@kbn/ui-theme",
+ "@kbn/i18n",
+ "@kbn/ui-actions-plugin",
+ ],
+ "exclude": ["target/**/*"]
+}
diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts
index 99918241190df..9e2ba93dc4229 100644
--- a/src/dev/storybook/aliases.ts
+++ b/src/dev/storybook/aliases.ts
@@ -11,6 +11,7 @@ export const storybookAliases = {
apm: 'x-pack/plugins/apm/.storybook',
canvas: 'x-pack/plugins/canvas/storybook',
cases: 'packages/kbn-cases-components/.storybook',
+ cell_actions: 'packages/kbn-cell-actions/.storybook',
ci_composite: '.ci/.storybook',
cloud_chat: 'x-pack/plugins/cloud_integrations/cloud_chat/.storybook',
coloring: 'packages/kbn-coloring/.storybook',
diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh
index 1c6faa93d01d1..17460c4e08012 100755
--- a/test/scripts/jenkins_storybook.sh
+++ b/test/scripts/jenkins_storybook.sh
@@ -6,6 +6,7 @@ cd "$KIBANA_DIR"
yarn storybook --site apm
yarn storybook --site canvas
+yarn storybook --site cell_actions
yarn storybook --site ci_composite
yarn storybook --site content_management
yarn storybook --site custom_integrations
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 8b2aed59945cf..ce8e855125478 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -92,6 +92,8 @@
"@kbn/cases-fixture-plugin/*": ["x-pack/test/functional_with_es_ssl/plugins/cases/*"],
"@kbn/cases-plugin": ["x-pack/plugins/cases"],
"@kbn/cases-plugin/*": ["x-pack/plugins/cases/*"],
+ "@kbn/cell-actions": ["packages/kbn-cell-actions"],
+ "@kbn/cell-actions/*": ["packages/kbn-cell-actions/*"],
"@kbn/chart-expressions-common": ["src/plugins/chart_expressions/common"],
"@kbn/chart-expressions-common/*": ["src/plugins/chart_expressions/common/*"],
"@kbn/chart-icons": ["packages/kbn-chart-icons"],
diff --git a/yarn.lock b/yarn.lock
index ecda18a72e2ba..f2e8684ba222f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2825,6 +2825,10 @@
version "0.0.0"
uid ""
+"@kbn/cell-actions@link:packages/kbn-cell-actions":
+ version "0.0.0"
+ uid ""
+
"@kbn/chart-expressions-common@link:src/plugins/chart_expressions/common":
version "0.0.0"
uid ""