From f5ef9a7f5906349b36c5d617b615f4dd98401b7d Mon Sep 17 00:00:00 2001 From: Sean Teramae Date: Tue, 18 Aug 2026 16:10:21 -0700 Subject: [PATCH 1/3] feat(studio): ActionsMenu button Signed-off-by: Sean Teramae --- .../src/components/ActionsMenu/index.tsx | 95 +++++++++++++++++++ .../components/ConfirmationModal/index.tsx | 7 +- .../QuickActionsMenuRoot/index.tsx | 75 +++------------ .../DataDesignerJobActionsMenu/index.tsx | 25 +++-- 4 files changed, 130 insertions(+), 72 deletions(-) create mode 100644 web/packages/common/src/components/ActionsMenu/index.tsx diff --git a/web/packages/common/src/components/ActionsMenu/index.tsx b/web/packages/common/src/components/ActionsMenu/index.tsx new file mode 100644 index 0000000000..4849d78fb6 --- /dev/null +++ b/web/packages/common/src/components/ActionsMenu/index.tsx @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + Button, + Divider, + DropdownContent, + DropdownDividerItemEntry, + DropdownItem, + DropdownRoot, + DropdownTrigger, + Flex, +} from '@nvidia/foundations-react-core'; +import { Sparkles } from 'lucide-react'; +import React, { FC, ReactElement, ReactNode } from 'react'; + +export interface ActionMenuItem { + label: string; + onSelect: () => void; + icon?: ReactElement<{ size?: number; fill?: string; className?: string }>; + disabled?: boolean; + danger?: boolean; + divider?: Omit; +} + +interface ActionsMenuProps { + actions: ActionMenuItem[]; + /** Trigger label. Omit for an icon-only trigger. */ + label?: ReactNode; + /** Trigger icon. Defaults to a sparkles icon when a label is shown. */ + icon?: ReactElement; + kind?: 'primary' | 'secondary' | 'tertiary'; + disabled?: boolean; + ariaLabel?: string; + 'data-testid'?: string; +} + +/* + * ActionsMenu renders a dropdown of actions behind a single trigger button. Use it to consolidate + * page-level actions instead of lining up individual buttons. Pass a `label` for a labeled + * "Actions" trigger, or omit it for an icon-only trigger. + */ +export const ActionsMenu: FC = ({ + actions, + label, + icon, + kind = 'tertiary', + disabled, + ariaLabel = 'Open actions menu', + 'data-testid': testId = 'actions-menu', +}) => { + const triggerIcon = icon ?? (label ? : undefined); + + return ( + + e.stopPropagation()} + showChevron={false} + > + + + + {actions.map((action, key) => ( + + { + e.stopPropagation(); + action.onSelect(); + }} + danger={action.danger} + > + + {action.icon && React.cloneElement(action.icon, { size: 20, fill: 'solid' })} + {action.label} + + + {action.divider && } + + ))} + + + ); +}; diff --git a/web/packages/common/src/components/ConfirmationModal/index.tsx b/web/packages/common/src/components/ConfirmationModal/index.tsx index 252756199d..36528c0185 100644 --- a/web/packages/common/src/components/ConfirmationModal/index.tsx +++ b/web/packages/common/src/components/ConfirmationModal/index.tsx @@ -18,7 +18,10 @@ interface FormFields { type SubmitButtonColor = NonNullable['color']>; -export interface ConfirmationModalProps extends Pick { +export interface ConfirmationModalProps extends Pick< + FormModalProps, + 'open' | 'onClose' | 'cancelButtonText' +> { /** Return true when the action succeeded (shows success toast); false shows error toast. */ onConfirm: () => boolean | Promise; title: string; @@ -50,6 +53,7 @@ export const ConfirmationModal: FC = ({ successText = 'Done.', errorText = 'Something went wrong. Please try again.', submitButtonText = 'Confirm', + cancelButtonText, submitButtonColor, suppressResultToasts = false, onNotify, @@ -100,6 +104,7 @@ export const ConfirmationModal: FC = ({ open={open} title={title} submitButtonText={submitButtonText} + cancelButtonText={cancelButtonText} disabled={isPending} submitDisabled={simpleConfirm ? false : !isValid} loading={isPending} diff --git a/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx index 46f69d303d..877daf76d6 100644 --- a/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx +++ b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx @@ -1,75 +1,24 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { - Button, - Divider, - DropdownContent, - DropdownDividerItemEntry, - DropdownItem, - DropdownRoot, - DropdownTrigger, - Flex, -} from '@nvidia/foundations-react-core'; +import { ActionMenuItem, ActionsMenu } from '@nemo/common/src/components/ActionsMenu'; import { EllipsisVertical } from 'lucide-react'; -import React, { FC } from 'react'; +import { FC } from 'react'; -export interface QuickActionItem { - label: string; - onSelect: () => void; - icon?: React.ReactElement<{ size?: number; fill?: string; className?: string }>; - disabled?: boolean; - danger?: boolean; - divider?: Omit; -} +export type QuickActionItem = ActionMenuItem; interface QuickActionsMenuProps { actions: QuickActionItem[]; } /* - * QuickActionsMenu is a wrapper around Kaizen's Menu component that standardizes - * the alignment of menus and other sizes/behaviours. + * QuickActionsMenu is the icon-only preset of ActionsMenu, used for row- and card-level menus. */ -export const QuickActionsMenuRoot: FC = ({ actions }) => { - return ( - - e.stopPropagation()} - showChevron={false} - > - - - - {actions.map((action, key) => ( - - { - e.stopPropagation(); - action.onSelect(); - }} - danger={action.danger} - > - - {action.icon && React.cloneElement(action.icon, { size: 20, fill: 'solid' })} - {action.label} - - - {action.divider && } - - ))} - - - ); -}; +export const QuickActionsMenuRoot: FC = ({ actions }) => ( + } + ariaLabel="Open quick actions menu" + data-testid="quick-actions-menu" + /> +); diff --git a/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx b/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx index fdf4ebea03..149efb7a66 100644 --- a/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx +++ b/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { - type QuickActionItem, - QuickActionsMenuRoot, -} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; +import { type ActionMenuItem, ActionsMenu } from '@nemo/common/src/components/ActionsMenu'; +import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot'; import { CJobCancellableStatuses } from '@nemo/common/src/constants/query'; import { getDataDesignerListCreateJobsQueryKey, @@ -21,6 +19,10 @@ import { useNavigate } from 'react-router'; interface DataDesignerJobActionsMenuProps { job: DataDesignerJob; + /** 'icon' renders the ellipsis trigger (tables); 'labeled' renders an "Actions" button (details page). */ + variant?: 'icon' | 'labeled'; + /** Page-specific actions listed above the job-level ones, e.g. Transform and Split. */ + additionalActions?: ActionMenuItem[]; /** Include a "View details" entry. Used in the table row, omitted on the details page. */ includeViewDetails?: boolean; /** When provided, adds a "View config" entry that invokes this callback. */ @@ -32,12 +34,14 @@ interface DataDesignerJobActionsMenuProps { } /** - * Quick-actions menu for a single Data Designer job: View details (optional), Clone, Cancel - * (when cancellable), and Delete. Shared by the jobs table and the job details page so both + * Actions menu for a single Data Designer job: any caller-supplied actions, View details + * (optional), Clone, Cancel (when cancellable), and Delete. Shared by the jobs table and the job details page so both * expose the same actions. Owns its own delete modal; cancel errors are surfaced via callback. */ export const DataDesignerJobActionsMenu: FC = ({ job, + variant = 'icon', + additionalActions = [], includeViewDetails = false, onViewConfig, onDeleted, @@ -82,7 +86,8 @@ export const DataDesignerJobActionsMenu: FC = ( const isCancellable = job.status != null && CJobCancellableStatuses.includes(job.status); - const actions: QuickActionItem[] = [ + const actions: ActionMenuItem[] = [ + ...additionalActions, ...(includeViewDetails ? [ { @@ -124,7 +129,11 @@ export const DataDesignerJobActionsMenu: FC = ( return ( <> - + {variant === 'labeled' ? ( + + ) : ( + + )} {showDeleteModal && ( Date: Wed, 19 Aug 2026 11:14:53 -0700 Subject: [PATCH 2/3] fix pr comments Signed-off-by: Sean Teramae --- web/packages/common/src/components/ActionsMenu/index.tsx | 2 +- .../QuickActionsMenu/QuickActionsMenuRoot/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/packages/common/src/components/ActionsMenu/index.tsx b/web/packages/common/src/components/ActionsMenu/index.tsx index 4849d78fb6..97df184047 100644 --- a/web/packages/common/src/components/ActionsMenu/index.tsx +++ b/web/packages/common/src/components/ActionsMenu/index.tsx @@ -12,7 +12,7 @@ import { Flex, } from '@nvidia/foundations-react-core'; import { Sparkles } from 'lucide-react'; -import React, { FC, ReactElement, ReactNode } from 'react'; +import React, { type FC, type ReactElement, type ReactNode } from 'react'; export interface ActionMenuItem { label: string; diff --git a/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx index 877daf76d6..3e70247d4d 100644 --- a/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx +++ b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ActionMenuItem, ActionsMenu } from '@nemo/common/src/components/ActionsMenu'; +import { ActionsMenu, type ActionMenuItem } from '@nemo/common/src/components/ActionsMenu'; import { EllipsisVertical } from 'lucide-react'; -import { FC } from 'react'; +import type { FC } from 'react'; export type QuickActionItem = ActionMenuItem; From 102b68bc9dc2a99bdf65586c4864b4cdfec51742 Mon Sep 17 00:00:00 2001 From: Sean Teramae Date: Wed, 19 Aug 2026 12:19:05 -0700 Subject: [PATCH 3/3] fix: regenerate plugin.d.ts for ActionsMenu rename CI's `git diff --exit-code` check was failing because the generated plugin surface types were not regenerated after the QuickActionsMenu -> ActionsMenu rename and ConfirmationModal cancelButtonText addition. Signed-off-by: Sean Teramae --- web/packages/common/plugin-types/plugin.d.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/web/packages/common/plugin-types/plugin.d.ts b/web/packages/common/plugin-types/plugin.d.ts index 6117b72558..26b3904097 100644 --- a/web/packages/common/plugin-types/plugin.d.ts +++ b/web/packages/common/plugin-types/plugin.d.ts @@ -4035,7 +4035,7 @@ type NotifyFn = (message: string, type?: NotifyType, options?: MessageFnOptions) //#endregion //#region src/components/ConfirmationModal/index.d.ts type SubmitButtonColor = NonNullable['color']>; -interface ConfirmationModalProps extends Pick { +interface ConfirmationModalProps extends Pick { /** Return true when the action succeeded (shows success toast); false shows error toast. */ onConfirm: () => boolean | Promise; title: string; @@ -5639,11 +5639,11 @@ export declare const FileUpload: FC; */ export declare const InputErrorText: FC>>; //#endregion -//#region src/components/QuickActionsMenu/QuickActionsMenuRoot/index.d.ts -interface QuickActionItem { +//#region src/components/ActionsMenu/index.d.ts +interface ActionMenuItem { label: string; onSelect: () => void; - icon?: React$1.ReactElement<{ + icon?: ReactElement<{ size?: number; fill?: string; className?: string; @@ -5652,6 +5652,9 @@ interface QuickActionItem { danger?: boolean; divider?: Omit; } +//#endregion +//#region src/components/QuickActionsMenu/QuickActionsMenuRoot/index.d.ts +type QuickActionItem = ActionMenuItem; interface QuickActionsMenuProps { actions: QuickActionItem[]; }