diff --git a/frontend/package.json b/frontend/package.json index 15564fc422f..f3e678542c9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -154,7 +154,7 @@ "@patternfly/react-catalog-view-extension": "~6.3.0", "@patternfly/react-charts": "~8.4.1", "@patternfly/react-code-editor": "~6.4.2", - "@patternfly/react-component-groups": "~6.4.0", + "@patternfly/react-component-groups": "6.4.0-prerelease.17", "@patternfly/react-core": "~6.4.2", "@patternfly/react-data-view": "~6.4.0-prerelease.12", "@patternfly/react-drag-drop": "~6.5.0-prerelease.38", @@ -318,6 +318,7 @@ "glob-parent": "^5.1.2", "hosted-git-info": "^3.0.8", "lodash-es": "^4.17.23", + "@patternfly/react-component-groups": "6.4.0-prerelease.17", "postcss": "^8.2.13" }, "lint-staged": { diff --git a/frontend/packages/console-app/locales/en/console-app.json b/frontend/packages/console-app/locales/en/console-app.json index ce594b3c6a8..688f7f6c085 100644 --- a/frontend/packages/console-app/locales/en/console-app.json +++ b/frontend/packages/console-app/locales/en/console-app.json @@ -465,6 +465,10 @@ "Certificate approval required": "Certificate approval required", "An error occurred. Please try again": "An error occurred. Please try again", "No new Pods or workloads will be placed on this Node until it's marked as schedulable.": "No new Pods or workloads will be placed on this Node until it's marked as schedulable.", + "Failed to mark {{failureCount}} of {{totalCount}} nodes as schedulable": "Failed to mark {{failureCount}} of {{totalCount}} nodes as schedulable", + "Failed to mark {{failureCount}} of {{totalCount}} nodes as unschedulable": "Failed to mark {{failureCount}} of {{totalCount}} nodes as unschedulable", + "Mark as schedulable ({{nodeCount}})": "Mark as schedulable ({{nodeCount}})", + "Mark as unschedulable ({{nodeCount}})": "Mark as unschedulable ({{nodeCount}})", "Identity providers": "Identity providers", "Mapping method": "Mapping method", "Remove identity provider": "Remove identity provider", diff --git a/frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx b/frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx index 2abde6da77d..38274f7a265 100644 --- a/frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx +++ b/frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx @@ -1,7 +1,9 @@ import type { FC, ReactNode } from 'react'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState, useEffect } from 'react'; import './ConsoleDataView.scss'; import { + BulkSelect, + BulkSelectValue, ResponsiveAction, ResponsiveActions, SkeletonTableBody, @@ -80,6 +82,10 @@ export const ConsoleDataView = < mock, isResizable, resetAllColumnWidths, + bulkSelect, + bulkActions, + selection, + actionsBreakpoint = 'lg', }: ConsoleDataViewProps) => { const { t } = useTranslation(); const launchModal = useOverlay(); @@ -100,6 +106,22 @@ export const ConsoleDataView = < matchesAdditionalFilters, }); + // Notify parent of filtered selected items when filters or selection changes + useEffect(() => { + if (selection?.onFilteredSelectionChange) { + const filteredSelectedItems = filteredData.filter((item) => + selection.selectedItems.has(selection.getItemId(item)), + ); + selection.onFilteredSelectionChange(filteredSelectedItems); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + filteredData, + selection?.selectedItems, + selection?.getItemId, + selection?.onFilteredSelectionChange, + ]); + const { dataViewColumns, dataViewRows, pagination } = useConsoleDataViewData< TData, TCustomRowData, @@ -134,6 +156,43 @@ export const ConsoleDataView = < return undefined; }, [filteredData.length, loaded]); + // Create bulkSelect component if selection is provided but bulkSelect prop is not + let defaultBulkSelect = null; + if (selection?.onSelectAll && !bulkSelect) { + const totalCount = filteredData.length; + const pageCount = dataViewRows.length; + // Count only selected items that are in the current filtered dataset + const selectedCount = filteredData.filter((item) => + selection.selectedItems.has(selection.getItemId(item)), + ).length; + + const handleBulkSelect = (value: BulkSelectValue) => { + if (value === BulkSelectValue.all || value === BulkSelectValue.page) { + selection.onSelectAll(true, filteredData); + } else if (value === BulkSelectValue.none || value === BulkSelectValue.nonePage) { + selection.onSelectAll(false, filteredData); + } + }; + + defaultBulkSelect = ( + + `${t('public~Select page')}${itemCount ? ` (${itemCount})` : ''}` + } + selectAllLabel={(itemCount) => + `${t('public~Select all')}${itemCount ? ` (${itemCount})` : ''}` + } + selectedLabel={(itemCount) => t('public~{{itemCount}} selected', { itemCount })} + /> + ); + } + const dataViewFilterNodes = useMemo(() => { const basicFilters: ReactNode[] = []; @@ -177,6 +236,7 @@ export const ConsoleDataView = < className={css(dataViewFilterNodes.length === 1 && 'co-console-data-view-single-filter')} > 0 && ( onSetFilters(values)}> @@ -186,7 +246,8 @@ export const ConsoleDataView = < } clearAllFilters={clearAllFilters} actions={ - + + {bulkActions} {!hideColumnManagement && ( { - return { - ...nameCellProps, - 'data-test': `data-view-cell-${name}-name`, - }; -}; +/** + * Returns name column props with appropriate offset based on whether bulk select is enabled. + * Use this for column definitions. + * @param hasRightBorder - Whether to include hasRightBorder (default: true) + * @param withBulkSelect - Whether the table has bulk selection enabled (default: false) + */ +export const getNameColumnProps = (hasRightBorder = true, withBulkSelect = false) => ({ + ...cellIsStickyProps, + ...(hasRightBorder && { hasRightBorder: true }), + ...(withBulkSelect && { stickyLeftOffset: SELECTION_COLUMN_WIDTH }), +}); + +/** + * Returns name cell props with appropriate offset based on whether bulk select is enabled. + * Use this for row cell definitions. + * @param name - The name to use in the data-test attribute + * @param withBulkSelect - Whether the table has bulk selection enabled (default: false) + */ +export const getNameCellProps = (name: string, withBulkSelect = false) => ({ + ...getNameColumnProps(true, withBulkSelect), + 'data-test': `data-view-cell-${name}-name`, +}); export const actionsCellProps = { ...cellIsStickyProps, diff --git a/frontend/packages/console-app/src/components/data-view/dataViewSelectionHelpers.ts b/frontend/packages/console-app/src/components/data-view/dataViewSelectionHelpers.ts new file mode 100644 index 00000000000..bd667a893a9 --- /dev/null +++ b/frontend/packages/console-app/src/components/data-view/dataViewSelectionHelpers.ts @@ -0,0 +1,78 @@ +import type { TableColumn } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; + +const selectionColumnProps = { + isStickyColumn: true, + stickyMinWidth: '0', + stickyLeftOffset: '0', +} as const; + +/** + * Creates a selection column definition for DataView tables. + * This column displays checkboxes for row selection. + * + * @example + * ```typescript + * const columns = [ + * createSelectionColumn(), + * { title: 'Name', id: 'name', ... }, + * ... + * ]; + * ``` + */ +export const createSelectionColumn = (): TableColumn => ({ + title: '', + id: 'select', + props: selectionColumnProps, +}); + +type CreateSelectionCellOptions = { + /** Row index in the table */ + rowIndex: number; + /** Unique ID for the item being selected */ + itemId: string; + /** Whether the item is currently selected */ + isSelected: boolean; + /** Callback when selection state changes */ + onSelect: (itemId: string, isSelecting: boolean) => void; + /** Whether the checkbox should be disabled */ + disabled?: boolean; +}; + +/** + * Creates a selection cell object for a DataView row. + * This cell contains the checkbox for row selection. + * + * @example + * ```typescript + * const rowCells = { + * select: createSelectionCell({ + * rowIndex: 0, + * itemId: getUID(node), + * isSelected: selectedIds.has(getUID(node)), + * onSelect: onSelectItem, + * }), + * name: { cell: }, + * ... + * }; + * ``` + */ +export const createSelectionCell = ({ + rowIndex, + itemId, + isSelected, + onSelect, + disabled = false, +}: CreateSelectionCellOptions) => ({ + cell: '', // Checkbox is rendered via props, no content needed + props: { + ...selectionColumnProps, + select: { + rowIndex, + onSelect: (_event: any, isSelecting: boolean) => { + onSelect(itemId, isSelecting); + }, + isSelected, + isDisabled: disabled, + }, + }, +}); diff --git a/frontend/packages/console-app/src/components/data-view/useDataViewSelection.ts b/frontend/packages/console-app/src/components/data-view/useDataViewSelection.ts new file mode 100644 index 00000000000..da9840b493a --- /dev/null +++ b/frontend/packages/console-app/src/components/data-view/useDataViewSelection.ts @@ -0,0 +1,106 @@ +import { useState, useCallback, useMemo, useEffect } from 'react'; + +type UseDataViewSelectionOptions = { + /** All data items */ + data: T[]; + /** Function to extract unique ID from an item */ + getItemId: (item: T) => string; + /** Optional filter to exclude certain items from selection (e.g., filter out CSRs) */ + filterSelectable?: (item: T) => boolean; +}; + +type UseDataViewSelectionResult = { + /** Set of selected item IDs */ + selectedIds: Set; + /** Array of selected item objects */ + selectedItems: T[]; + /** Callback to select/deselect a single item */ + onSelectItem: (itemId: string, isSelecting: boolean) => void; + /** Callback to select/deselect all filtered items */ + onSelectAll: (isSelecting: boolean, filteredItems: T[]) => void; + /** Clear all selections */ + clearSelection: () => void; +}; + +/** + * Custom hook for managing selection state in DataView components. + * Provides selection state, callbacks, and selected item objects. + * + * @example + * ```typescript + * const { selectedIds, selectedItems, onSelectItem, onSelectAll, clearSelection } = + * useDataViewSelection({ + * data, + * getItemId: (node) => getUID(node), + * filterSelectable: (item) => !isCSRResource(item), + * }); + * ``` + */ +export const useDataViewSelection = ({ + data, + getItemId, + filterSelectable, +}: UseDataViewSelectionOptions): UseDataViewSelectionResult => { + const [selectedIds, setSelectedIds] = useState>(new Set()); + + // Update selection to only include items that still exist in the current data + useEffect(() => { + const selectableData = filterSelectable ? data.filter(filterSelectable) : data; + const currentValidIds = new Set(selectableData.map(getItemId)); + + setSelectedIds((prev) => { + const filtered = new Set(); + prev.forEach((id) => { + if (currentValidIds.has(id)) { + filtered.add(id); + } + }); + // Only update if the selection actually changed + return filtered.size === prev.size ? prev : filtered; + }); + }, [data, getItemId, filterSelectable]); + + const onSelectItem = useCallback((itemId: string, isSelecting: boolean) => { + setSelectedIds((prev) => { + const newSet = new Set(prev); + if (isSelecting) { + newSet.add(itemId); + } else { + newSet.delete(itemId); + } + return newSet; + }); + }, []); + + const onSelectAll = useCallback( + (isSelecting: boolean, filteredItems: T[]) => { + if (isSelecting) { + const selectableItems = filterSelectable + ? filteredItems.filter(filterSelectable) + : filteredItems; + const itemIds = selectableItems.map(getItemId); + setSelectedIds(new Set(itemIds)); + } else { + setSelectedIds(new Set()); + } + }, + [getItemId, filterSelectable], + ); + + const clearSelection = useCallback(() => { + setSelectedIds(new Set()); + }, []); + + const selectedItems = useMemo(() => { + const selectableData = filterSelectable ? data.filter(filterSelectable) : data; + return selectableData.filter((item) => selectedIds.has(getItemId(item))); + }, [data, selectedIds, getItemId, filterSelectable]); + + return { + selectedIds, + selectedItems, + onSelectItem, + onSelectAll, + clearSelection, + }; +}; diff --git a/frontend/packages/console-app/src/components/nodes/NodesPage.tsx b/frontend/packages/console-app/src/components/nodes/NodesPage.tsx index d822e13716d..1690dd5770b 100644 --- a/frontend/packages/console-app/src/components/nodes/NodesPage.tsx +++ b/frontend/packages/console-app/src/components/nodes/NodesPage.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react'; -import { useMemo, useCallback, useEffect, Suspense } from 'react'; +import { useMemo, useCallback, useEffect, useState, Suspense } from 'react'; import { Button, ButtonVariant } from '@patternfly/react-core'; import { DataViewCheckboxFilter } from '@patternfly/react-data-view'; import type { DataViewFilterOption } from '@patternfly/react-data-view/dist/esm/DataViewFilters'; @@ -8,16 +8,21 @@ import { useTranslation } from 'react-i18next'; import { actionsCellProps, getNameCellProps, + getNameColumnProps, initialFiltersDefault, ConsoleDataView, - nameCellProps, getLabelsColumnWidthStyleProp, } from '@console/app/src/components/data-view/ConsoleDataView'; +import { + createSelectionColumn, + createSelectionCell, +} from '@console/app/src/components/data-view/dataViewSelectionHelpers'; import type { ConsoleDataViewColumn, ConsoleDataViewRow, ResourceFilters, } from '@console/app/src/components/data-view/types'; +import { useDataViewSelection } from '@console/app/src/components/data-view/useDataViewSelection'; import { useColumnWidthSettings } from '@console/app/src/components/data-view/useResizableColumnProps'; import { FLAG_NODE_MGMT_V1 } from '@console/app/src/consts'; import type { K8sModel } from '@console/dynamic-plugin-sdk/src/api/dynamic-core-api'; @@ -105,6 +110,7 @@ import { useWatchVirtualMachineInstances, } from './NodeVmUtils'; import ClientCSRStatus from './status/CSRStatus'; +import { useBulkNodeActions as useBulkNodeActionsHook } from './useBulkNodeActions'; import type { GetNodeStatusExtensions } from './useNodeStatusExtensions'; import { useNodeStatusExtensions } from './useNodeStatusExtensions'; @@ -179,13 +185,14 @@ const useNodesColumns = ( const columns = useMemo(() => { return [ + createSelectionColumn(), { title: t('console-app~Name'), id: nodeColumnInfo.name.id, sort: 'metadata.name', resizableProps: getResizableProps(nodeColumnInfo.name.id), props: { - ...nameCellProps, + ...getNameColumnProps(true, true), modifier: 'nowrap', }, }, @@ -401,8 +408,12 @@ const getNodeDataViewRows = ( tableColumns: ConsoleDataViewColumn[], nodeMetrics: NodeMetrics, statusExtensions: GetNodeStatusExtensions, + selection?: { + selectedItems: Set; + onSelect: (itemId: string, isSelecting: boolean) => void; + }, ): ConsoleDataViewRow[] => { - return rowData.map(({ obj }) => { + return rowData.map(({ obj }, rowIndex) => { const isCSR = isCSRResource(obj); const node = isCSR ? null : (obj as NodeKind); const csr = isCSR ? (obj as NodeCertificateSigningRequestKind) : null; @@ -434,6 +445,15 @@ const getNodeDataViewRows = ( const context = node ? { [resourceKind]: node } : {}; const rowCells = { + select: + selection && node + ? createSelectionCell({ + rowIndex, + itemId: nodeUID, + isSelected: selection.selectedItems.has(nodeUID), + onSelect: selection.onSelect, + }) + : undefined, [nodeColumnInfo.name.id]: { cell: node ? ( { - const cell = rowCells[id]?.cell || DASH; + const rowCell = rowCells[id]; + if (!rowCell) { + return { + id, + cell: DASH, + }; + } + // For select column, don't default to DASH - checkbox is rendered via props + const cellContent = id === 'select' ? rowCell.cell ?? '' : rowCell.cell ?? DASH; return { id, - props: rowCells[id]?.props, - cell, + props: rowCell.props, + cell: cellContent, }; }); }); @@ -643,15 +671,38 @@ const NodeList: FC = ({ const columnManagementID = referenceForModel(NodeModel); const statusExtensions = useNodeStatusExtensions(); + // Selection state + const { selectedIds, onSelectItem, onSelectAll, clearSelection } = useDataViewSelection({ + data, + getItemId: getUID, + filterSelectable: (item) => !isCSRResource(item), + }); + + // Track filtered selected nodes for bulk actions + const [filteredSelectedNodes, setFilteredSelectedNodes] = useState([]); + + const handleFilteredSelectionChange = useCallback((items: NodeRowItem[]) => { + // Filter out CSRs and cast to NodeKind + const nodes = items.filter((item) => !isCSRResource(item)) as NodeKind[]; + setFilteredSelectedNodes(nodes); + }, []); + + const bulkActions = useBulkNodeActionsHook({ + selectedNodes: filteredSelectedNodes, + onComplete: clearSelection, + }); + const columnLayout = useMemo( () => ({ id: columnManagementID, type: t('console-app~Node'), - columns: columns.map((col) => ({ - id: col.id, - title: col.title, - additional: col.additional, - })), + columns: columns + .filter((col) => col.id !== 'select' && col.id !== nodeColumnInfo.actions.id) + .map((col) => ({ + id: col.id, + title: col.title, + additional: col.additional, + })), selectedColumns: selectedColumns?.[columnManagementID]?.length > 0 ? new Set(selectedColumns[columnManagementID] as string[]) @@ -859,6 +910,10 @@ const NodeList: FC = ({ tableColumns, nodeMetrics, statusExtensions, + { + selectedItems: selectedIds, + onSelect: onSelectItem, + }, ) } hideNameLabelFilters={hideNameLabelFilters} @@ -866,6 +921,15 @@ const NodeList: FC = ({ hideColumnManagement={hideColumnManagement} isResizable resetAllColumnWidths={resetAllColumnWidths} + bulkActions={bulkActions} + selection={{ + selectedItems: selectedIds, + onSelect: onSelectItem, + onSelectAll, + getItemId: getUID, + onFilteredSelectionChange: handleFilteredSelectionChange, + }} + actionsBreakpoint="2xl" /> ); diff --git a/frontend/packages/console-app/src/components/nodes/useBulkNodeActions.tsx b/frontend/packages/console-app/src/components/nodes/useBulkNodeActions.tsx new file mode 100644 index 00000000000..764427bd1b9 --- /dev/null +++ b/frontend/packages/console-app/src/components/nodes/useBulkNodeActions.tsx @@ -0,0 +1,123 @@ +import { useMemo, useCallback } from 'react'; +import { ResponsiveAction } from '@patternfly/react-component-groups'; +import { useTranslation } from 'react-i18next'; +import type { NodeKind } from '@console/internal/module/k8s'; +import { usePromiseHandler } from '@console/shared/src/hooks/usePromiseHandler'; +import { isNodeUnschedulable } from '@console/shared/src/selectors/node'; +import { makeNodeSchedulable, makeNodeUnschedulable } from '../../k8s/requests/nodes'; + +type UseBulkNodeActionsOptions = { + selectedNodes: NodeKind[]; + onComplete: () => void; +}; + +export const useBulkNodeActions = ({ selectedNodes, onComplete }: UseBulkNodeActionsOptions) => { + const { t } = useTranslation(); + const [handlePromise, inProgress] = usePromiseHandler(); + + const { schedulableCount, unschedulableCount } = useMemo(() => { + let schedulable = 0; + let unschedulable = 0; + selectedNodes.forEach((node) => { + if (isNodeUnschedulable(node)) { + unschedulable++; + } else { + schedulable++; + } + }); + return { schedulableCount: schedulable, unschedulableCount: unschedulable }; + }, [selectedNodes]); + + const handleMarkSchedulable = useCallback(() => { + const promises = selectedNodes + .filter((node) => isNodeUnschedulable(node)) + .map((node) => makeNodeSchedulable(node)); + + handlePromise( + Promise.allSettled(promises).then((results) => { + const failures = results.filter((r) => r.status === 'rejected'); + if (failures.length > 0) { + throw new Error( + t( + 'console-app~Failed to mark {{failureCount}} of {{totalCount}} nodes as schedulable', + { failureCount: failures.length, totalCount: results.length }, + ), + ); + } + }), + ) + .then(() => { + onComplete(); + }) + .catch(() => { + // Errors are handled by usePromiseHandler + }); + }, [selectedNodes, handlePromise, t, onComplete]); + + const handleMarkUnschedulable = useCallback(() => { + const promises = selectedNodes + .filter((node) => !isNodeUnschedulable(node)) + .map((node) => makeNodeUnschedulable(node)); + + handlePromise( + Promise.allSettled(promises).then((results) => { + const failures = results.filter((r) => r.status === 'rejected'); + if (failures.length > 0) { + throw new Error( + t( + 'console-app~Failed to mark {{failureCount}} of {{totalCount}} nodes as unschedulable', + { failureCount: failures.length, totalCount: results.length }, + ), + ); + } + }), + ) + .then(() => { + onComplete(); + }) + .catch(() => { + // Errors are handled by usePromiseHandler + }); + }, [selectedNodes, handlePromise, t, onComplete]); + + return useMemo(() => { + const actions: JSX.Element[] = []; + + if (unschedulableCount > 0) { + actions.push( + + {t('console-app~Mark as schedulable ({{nodeCount}})', { nodeCount: unschedulableCount })} + , + ); + } + + if (schedulableCount > 0) { + actions.push( + + {t('console-app~Mark as unschedulable ({{nodeCount}})', { nodeCount: schedulableCount })} + , + ); + } + + return actions; + }, [ + unschedulableCount, + schedulableCount, + handleMarkSchedulable, + handleMarkUnschedulable, + inProgress, + t, + ]); +}; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-api.ts b/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-api.ts index ca4e6b53259..2481a495aec 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-api.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-api.ts @@ -98,6 +98,12 @@ export const ConsoleDataView: < export const cellIsStickyProps: CellIsStickyProps = require('@console/app/src/components/data-view/ConsoleDataView') .cellIsStickyProps; +export const getNameColumnProps: ( + hasRightBorder?: boolean, + withBulkSelect?: boolean, +) => CellIsStickyProps = require('@console/app/src/components/data-view/ConsoleDataView') + .getNameColumnProps; + export const getNameCellProps: GetNameCellProps = require('@console/app/src/components/data-view/ConsoleDataView') .getNameCellProps; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts index 7e344585bfc..23914104475 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts @@ -1,5 +1,6 @@ import type { ReactNode, ComponentType, SetStateAction, Dispatch } from 'react'; import type { QuickStart } from '@patternfly/quickstarts'; +import type { OverflowMenuProps } from '@patternfly/react-core'; import type { DataViewTh } from '@patternfly/react-data-view/dist/esm/DataViewTable/DataViewTable'; import type { SortByDirection, ThProps } from '@patternfly/react-table'; import type { Map as ImmutableMap } from 'immutable'; @@ -358,6 +359,25 @@ export type ConsoleDataViewProps< isResizable?: boolean; /** When provided and isResizable is true, a toolbar action is shown to reset all column widths. */ resetAllColumnWidths?: () => void; + /** Bulk select control to display in the toolbar (e.g., PatternFly BulkSelect component). */ + bulkSelect?: ReactNode; + /** Bulk actions to display in the toolbar when items are selected. */ + bulkActions?: ReactNode; + /** Selection configuration for enabling row selection. */ + selection?: { + /** Set of selected item IDs. */ + selectedItems: Set; + /** Callback when a single row is selected/deselected. */ + onSelect: (itemId: string, isSelecting: boolean) => void; + /** Callback when select all is toggled. Receives filtered items matching current filters. */ + onSelectAll?: (isSelecting: boolean, filteredItems: TData[]) => void; + /** Function to extract unique ID from an item for selection tracking. */ + getItemId: (item: TData) => string; + /** Callback to receive filtered selected items whenever filters or selection changes. */ + onFilteredSelectionChange?: (filteredSelectedItems: TData[]) => void; + }; + /** Breakpoint at which toolbar actions switch between horizontal and dropdown layout. Default is 'lg'. */ + actionsBreakpoint?: OverflowMenuProps['breakpoint']; }; // ConsoleDataView helper types @@ -368,6 +388,7 @@ export type CellIsStickyProps = { export type GetNameCellProps = ( name: string, + withBulkSelect?: boolean, ) => CellIsStickyProps & { hasRightBorder: true; 'data-test': string; diff --git a/frontend/public/components/droppable-edit-yaml.tsx b/frontend/public/components/droppable-edit-yaml.tsx index d3443046198..3f67e7f5189 100644 --- a/frontend/public/components/droppable-edit-yaml.tsx +++ b/frontend/public/components/droppable-edit-yaml.tsx @@ -141,7 +141,7 @@ export const DroppableEditYAML: FC = ({ onDropRejected(fileRejections, event); } if (acceptedFiles.length > 0) { - onFileDrop(event, acceptedFiles as File[]); + onFileDrop(event as DropEvent, acceptedFiles as File[]); } }, accept: { diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index d228b1d6057..14573f54fc3 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -1763,6 +1763,9 @@ "Admission Webhook Warning": "Admission Webhook Warning", "{{kind}} {{name}} violates policy {{warning}}": "{{kind}} {{name}} violates policy {{warning}}", "Learn more": "Learn more", + "Select none (0)": "Select none (0)", + "Select page": "Select page", + "{{itemCount}} selected": "{{itemCount}} selected", "Filter by name": "Filter by name", "Reset column widths": "Reset column widths", "{{label}} table": "{{label}} table", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 60fb4cfc057..9c7012f38f8 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3523,9 +3523,9 @@ __metadata: languageName: node linkType: hard -"@patternfly/react-component-groups@npm:^6.1.0, @patternfly/react-component-groups@npm:~6.4.0": - version: 6.4.0 - resolution: "@patternfly/react-component-groups@npm:6.4.0" +"@patternfly/react-component-groups@npm:6.4.0-prerelease.17": + version: 6.4.0-prerelease.17 + resolution: "@patternfly/react-component-groups@npm:6.4.0-prerelease.17" dependencies: "@patternfly/react-core": "npm:^6.0.0" "@patternfly/react-icons": "npm:^6.0.0" @@ -3536,7 +3536,7 @@ __metadata: "@patternfly/react-drag-drop": ^6.0.0 react: ^17 || ^18 || ^19 react-dom: ^17 || ^18 || ^19 - checksum: 10c0/97e6e910206b6c4c0e04a44b1b62fb7dbf24538aa367e01948abe0b27ae3f58a0b2316d150a22a885a566c10e065093388111710dd45a7a5d85817aabfb05b50 + checksum: 10c0/6fbaf722bc6a3e9e101043cb0749cef8fce89b1f054b1ce447538bbf413ceeec5e777bcfbcdae4f6d4e9a1d4969f922566f045bfd9b76a43ad45064b86b5f3ff languageName: node linkType: hard @@ -17330,7 +17330,7 @@ __metadata: "@patternfly/react-catalog-view-extension": "npm:~6.3.0" "@patternfly/react-charts": "npm:~8.4.1" "@patternfly/react-code-editor": "npm:~6.4.2" - "@patternfly/react-component-groups": "npm:~6.4.0" + "@patternfly/react-component-groups": "npm:6.4.0-prerelease.17" "@patternfly/react-core": "npm:~6.4.2" "@patternfly/react-data-view": "npm:~6.4.0-prerelease.12" "@patternfly/react-drag-drop": "npm:~6.5.0-prerelease.38"