From 667131b9c1f819ec78c1cf7c88ec86833f707913 Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Thu, 18 Sep 2025 16:22:18 -0400 Subject: [PATCH 1/4] fix(columnManagementModal): refactor to use ListManager in modal --- package-lock.json | 45 +++--- package.json | 2 +- packages/module/package.json | 1 + .../ColumnManagementModal.md | 8 + .../ColumnManagementModalDragDropExample.tsx | 131 ++++++++++++++++ .../ColumnManagementModal.test.tsx | 57 +++++-- .../ColumnManagementModal.tsx | 144 +++++++++--------- .../src/ListManager/ListManager.test.tsx | 23 +++ .../module/src/ListManager/ListManager.tsx | 103 ++++++++----- packages/module/src/index.ts | 3 + 10 files changed, 368 insertions(+), 149 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModalDragDropExample.tsx diff --git a/package-lock.json b/package-lock.json index c6f92b7a..25615533 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "packages/*" ], "dependencies": { - "@patternfly/react-drag-drop": "^6.3.0", + "@patternfly/react-drag-drop": "^6.0.0", "@patternfly/react-tokens": "^6.0.0", "sharp": "^0.34.0" }, @@ -4665,14 +4665,14 @@ "link": true }, "node_modules/@patternfly/react-core": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.3.0.tgz", - "integrity": "sha512-TM+pLwLd5DzaDlOQhqeju9H9QUFQypQiNwXQLNIxOV5r3fmKh4NTp2Av/8WmFkpCj8mejDOfp4TNxoU1zdjCkQ==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.3.1.tgz", + "integrity": "sha512-1qV20nU4M6PA28qnikH9fPLQlkteaZZToFlATjBNBw7aUI6zIvj7U0akkHz8raWcfHAI+tAzGV7dfKjiv035/g==", "license": "MIT", "dependencies": { - "@patternfly/react-icons": "^6.3.0", - "@patternfly/react-styles": "^6.3.0", - "@patternfly/react-tokens": "^6.3.0", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", + "@patternfly/react-tokens": "^6.3.1", "focus-trap": "7.6.4", "react-dropzone": "^14.3.5", "tslib": "^2.8.1" @@ -4683,17 +4683,17 @@ } }, "node_modules/@patternfly/react-drag-drop": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-drag-drop/-/react-drag-drop-6.3.0.tgz", - "integrity": "sha512-6MgH1ZoMmugw9ESWO8D2z2Xc9v9kQTCfoQJRifH3PoC7IW0047yw/6vHnLonLxfeBkx5QR/zVmYnKyWNd+Q5OQ==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-drag-drop/-/react-drag-drop-6.3.1.tgz", + "integrity": "sha512-lTPTSCtScYm+5NPCbr8hmSMOggOEhvIIzsyoVWF/G+iJBR97u0fdvsBqRvTg95hv2R/bKTXHigSCgixqnE9XdQ==", "license": "MIT", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", - "@patternfly/react-core": "^6.3.0", - "@patternfly/react-icons": "^6.3.0", - "@patternfly/react-styles": "^6.3.0", + "@patternfly/react-core": "^6.3.1", + "@patternfly/react-icons": "^6.3.1", + "@patternfly/react-styles": "^6.3.1", "resize-observer-polyfill": "^1.5.1" }, "peerDependencies": { @@ -4702,9 +4702,9 @@ } }, "node_modules/@patternfly/react-icons": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.3.0.tgz", - "integrity": "sha512-W39JyqKW1UL6/YGuinDnpjbhmmLAfuxVrgDcdFBaK4D7D1iqkkqrDMV8zIzmV/RkodJ79xRnucYhYb2RukG4RA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.3.1.tgz", + "integrity": "sha512-uiMounSIww1iZLM4pq+X8c3upzwl9iowXRPjR5CA8entb70lwgAXg3PqvypnuTAcilTq1Y3k5sFTqkhz7rgKcQ==", "license": "MIT", "peerDependencies": { "react": "^17 || ^18 || ^19", @@ -4712,9 +4712,9 @@ } }, "node_modules/@patternfly/react-styles": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.3.0.tgz", - "integrity": "sha512-FvuyNsY2oN8f2dvCl4Hx8CxBWCIF3BC9JE3Ay1lCuVqY1WYkvW4AQn3/0WVRINCxB9FkQxVNkSjARdwHNCEulw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.3.1.tgz", + "integrity": "sha512-hyb+PlO8YITjKh2wBvjdeZhX6FyB3hlf4r6yG4rPOHk4SgneXHjNSdGwQ3szAxgGqtbENCYtOqwD/8ai72GrxQ==", "license": "MIT" }, "node_modules/@patternfly/react-table": { @@ -4736,9 +4736,9 @@ } }, "node_modules/@patternfly/react-tokens": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.3.0.tgz", - "integrity": "sha512-yWStfkbxg4RWAExFKS/JRGScyadOy35yr4DFispNeHrkZWMp4pwKf0VdwlQZ7+ZtSgEWtzzy1KFxMLmWh3mEqA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.3.1.tgz", + "integrity": "sha512-wt/xKU1tGCDXUueFb+8/Cwxlm4vUD/Xl26O8MxbSLm6NZAHOUPwytJ7gugloGSPvc/zcsXxEgKANL8UZNO6DTw==", "license": "MIT" }, "node_modules/@pkgjs/parseargs": { @@ -26329,6 +26329,7 @@ "typescript": "^5.8.3" }, "peerDependencies": { + "@patternfly/react-drag-drop": "^6.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" } diff --git a/package.json b/package.json index 4ef6cb73..c4c7551c 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "whatwg-fetch": "^3.6.20" }, "dependencies": { - "@patternfly/react-drag-drop": "^6.3.0", + "@patternfly/react-drag-drop": "^6.0.0", "@patternfly/react-tokens": "^6.0.0", "sharp": "^0.34.0" } diff --git a/packages/module/package.json b/packages/module/package.json index 5f54edb3..e251230c 100644 --- a/packages/module/package.json +++ b/packages/module/package.json @@ -38,6 +38,7 @@ "react-jss": "^10.10.0" }, "peerDependencies": { + "@patternfly/react-drag-drop": "^6.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModal.md b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModal.md index 27e960b3..11ff99a9 100644 --- a/packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModal.md +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModal.md @@ -29,3 +29,11 @@ Clicking the "Manage columns" button will open the column management modal. The ```js file="./ColumnManagementModalExample.tsx" ``` + +### With drag and drop reordering + +When `enableDragDrop` is set to `true`, users can drag and drop columns to reorder them. The order changes are reflected both in the modal and in the table when applied. This is useful when column order matters for the user experience. + +```js file="./ColumnManagementModalDragDropExample.tsx" + +``` diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModalDragDropExample.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModalDragDropExample.tsx new file mode 100644 index 00000000..5c45ef61 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ColumnManagementModal/ColumnManagementModalDragDropExample.tsx @@ -0,0 +1,131 @@ +import { FunctionComponent, useState } from 'react'; +import { Button, ButtonVariant } from '@patternfly/react-core'; +import { Table, Tbody, Td, Th, Tr, Thead } from '@patternfly/react-table'; +import { ColumnsIcon } from '@patternfly/react-icons'; +import ColumnManagementModal, { + ColumnManagementModalColumn +} from '@patternfly/react-component-groups/dist/dynamic/ColumnManagementModal'; + +const DEFAULT_COLUMNS: ColumnManagementModalColumn[] = [ + { + title: 'ID', + key: 'id', + isShownByDefault: true, + isShown: true, + isUntoggleable: true + }, + { + title: 'Publish date', + key: 'publishDate', + isShownByDefault: true, + isShown: true + }, + { + title: 'Impact', + key: 'impact', + isShownByDefault: true, + isShown: true + }, + { + title: 'Score', + key: 'score', + isShownByDefault: false, + isShown: false + }, + { + title: 'CVSS Vector', + key: 'cvssVector', + isShownByDefault: false, + isShown: false + }, + { + title: 'Severity', + key: 'severity', + isShownByDefault: true, + isShown: true + } +]; + +const ROWS = [ + { + id: 'CVE-2024-1546', + publishDate: '20 Feb 2024', + impact: 'Important', + score: '7.5', + cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N', + severity: 'High' + }, + { + id: 'CVE-2024-1547', + publishDate: '20 Feb 2024', + impact: 'Important', + score: '7.5', + cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N', + severity: 'High' + }, + { + id: 'CVE-2024-1548', + publishDate: '20 Feb 2024', + impact: 'Moderate', + score: '6.1', + cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N', + severity: 'Medium' + }, + { + id: 'CVE-2024-1549', + publishDate: '20 Feb 2024', + impact: 'Moderate', + score: '6.1', + cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N', + severity: 'Medium' + } +]; + +export const ColumnManagementModalDragDropExample: FunctionComponent = () => { + const [ columns, setColumns ] = useState(DEFAULT_COLUMNS); + const [ isOpen, setOpen ] = useState(false); + + return ( + <> + setColumns(newColumns)} + isOpen={isOpen} + onClose={() => setOpen(false)} + enableDragDrop={true} + title="Manage and reorder columns" + description="Selected categories will be displayed in the table. Drag and drop to reorder columns." + /> + + + + + {columns + .filter((column) => column.isShown) + .map((column) => ( + + ))} + + + + {ROWS.map((row, rowIndex) => ( + + {columns + .filter((column) => column.isShown) + .map((column, columnIndex) => ( + + ))} + + ))} + +
{column.title}
{row[column.key]}
+ + ); +}; \ No newline at end of file diff --git a/packages/module/src/ColumnManagementModal/ColumnManagementModal.test.tsx b/packages/module/src/ColumnManagementModal/ColumnManagementModal.test.tsx index efe0554b..6c4b7ae5 100644 --- a/packages/module/src/ColumnManagementModal/ColumnManagementModal.test.tsx +++ b/packages/module/src/ColumnManagementModal/ColumnManagementModal.test.tsx @@ -1,5 +1,6 @@ import '@testing-library/jest-dom' import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import ColumnManagementModal, { ColumnManagementModalColumn } from './ColumnManagementModal'; const DEFAULT_COLUMNS : ColumnManagementModalColumn[] = [ @@ -33,18 +34,29 @@ const DEFAULT_COLUMNS : ColumnManagementModalColumn[] = [ const onClose = jest.fn(); const setColumns = jest.fn(); +// Simple mock to track when DragDropSort is used +jest.mock('@patternfly/react-drag-drop', () => ({ + DragDropSort: ({ children }) =>
{children}
, + Droppable: ({ wrapper }) => wrapper, +})); + +const renderColumnManagementModal = (props = {}) => render( setColumns(newColumns)} + isOpen + onClose={onClose} + data-testid="column-mgmt-modal" + {...props} +/>); + beforeEach(() => { - render( setColumns(newColumns)} - isOpen - onClose={onClose} - data-testid="column-mgmt-modal" - />); + jest.clearAllMocks(); + renderColumnManagementModal(); }); const getCheckboxesState = () => { - const checkboxes = screen.getByTestId('column-mgmt-modal').querySelectorAll('input[type="checkbox"]'); + // Get only the column checkboxes (exclude the BulkSelect checkbox) + const checkboxes = screen.getByTestId('column-mgmt-modal').querySelectorAll('input[type="checkbox"][data-testid^="column-check-"]'); return (Array.from(checkboxes) as HTMLInputElement[]).map(c => c.checked); } @@ -54,7 +66,7 @@ describe('ColumnManagementModal component', () => { }); it('should have checkbox checked if column is shown by default', () => { - const idCheckbox = screen.getByTestId('column-mgmt-modal').querySelector('input[type="checkbox"][data-ouia-component-id="ColumnManagementModal-column0-checkbox"]'); + const idCheckbox = screen.getByTestId('column-mgmt-modal').querySelector('input[type="checkbox"][data-testid="column-check-id"]'); expect(idCheckbox).toHaveAttribute('disabled'); expect(idCheckbox).toHaveAttribute('checked'); @@ -72,11 +84,15 @@ describe('ColumnManagementModal component', () => { expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map(c => c.isShownByDefault)); }); - it('should set all columns to show upon clicking on "Select all"', () => { + it('should set all columns to show upon clicking on "Select all"', async () => { // disable Impact column which is enabled by default fireEvent.click(screen.getByText('Impact')); - fireEvent.click(screen.getByText('Select all')); + // Use the BulkSelect to select all + const menuToggle = screen.getByLabelText('Bulk select toggle'); + await userEvent.click(menuToggle); + const selectAllButton = screen.getByText('Select all (4)'); + await userEvent.click(selectAllButton); expect(getCheckboxesState()).toEqual(DEFAULT_COLUMNS.map(_ => true)); }); @@ -103,6 +119,23 @@ describe('ColumnManagementModal component', () => { fireEvent.click(screen.getByText('Cancel')); expect(onClose).toHaveBeenCalled(); - expect(setColumns).toHaveBeenCalledWith(DEFAULT_COLUMNS); + // applyColumns should NOT be called on cancel + expect(setColumns).not.toHaveBeenCalled(); + }); + + describe('enableDragDrop prop', () => { + it('should default enableDragDrop to false', () => { + // Default behavior should not enable drag and drop + expect(screen.queryByTestId('drag-drop-sort')).not.toBeInTheDocument(); + }); + + it('should pass enableDragDrop prop to ListManager', () => { + // Test that the prop is properly passed through to ListManager + jest.clearAllMocks(); + renderColumnManagementModal({ enableDragDrop: true }); + + // When enableDragDrop is true, DragDropSort should be rendered + expect(screen.getByTestId('drag-drop-sort')).toBeInTheDocument(); + }); }); }); diff --git a/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx b/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx index 398d74ff..f97bf3f2 100644 --- a/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx +++ b/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx @@ -1,20 +1,13 @@ import type { FunctionComponent } from 'react'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Button, Content, ContentVariants, - DataListItem, - DataList, - DataListItemRow, - DataListCheck, - DataListCell, - DataListItemCells, - Split, - SplitItem, - ButtonVariant + ButtonVariant, } from '@patternfly/react-core'; import { ModalProps, Modal, ModalVariant } from '@patternfly/react-core/deprecated'; +import ListManager, { ListManagerItem } from '../ListManager/ListManager'; export interface ColumnManagementModalColumn { /** Internal identifier of a column by which table displayed columns are filtered. */ @@ -45,6 +38,8 @@ export interface ColumnManagementModalProps extends Omit = ( @@ -55,41 +50,76 @@ const ColumnManagementModal: FunctionComponent = ( appliedColumns, applyColumns, ouiaId = 'ColumnManagementModal', + enableDragDrop = false, ...props }: ColumnManagementModalProps) => { - const [ currentColumns, setCurrentColumns ] = useState( + const [ currentColumns, setCurrentColumns ] = useState(() => appliedColumns.map(column => ({ ...column, isShown: column.isShown ?? column.isShownByDefault })) ); - const handleChange = index => { - const newColumns = [ ...currentColumns ]; - const changedColumn = { ...newColumns[index] }; + // Sync with appliedColumns when they change + useEffect(() => { + setCurrentColumns(appliedColumns.map(column => ({ ...column, isShown: column.isShown ?? column.isShownByDefault }))); + }, [ appliedColumns ]); - changedColumn.isShown = !changedColumn.isShown; - newColumns[index] = changedColumn; + // Convert ColumnManagementModalColumn to ListManagerItem + const listManagerItems: ListManagerItem[] = currentColumns.map(column => ({ + key: column.key, + title: column.title, + isSelected: column.isShown, + isShownByDefault: column.isShownByDefault, + isUntoggleable: column.isUntoggleable + })); - setCurrentColumns(newColumns); + const resetToDefault = () => { + setCurrentColumns(currentColumns.map(column => ({ ...column, isShown: column.isShownByDefault ?? false }))); }; - const selectAll = () => { - let newColumns = [ ...currentColumns ]; - newColumns = newColumns.map(column => ({ ...column, isShown: true })); + const handleSelect = (item: ListManagerItem) => { + const newColumns = currentColumns.map(column => + column.key === item.key + ? { ...column, isShown: item.isSelected ?? column.isShownByDefault } + : column + ); + setCurrentColumns(newColumns); + }; + const handleSelectAll = (items: ListManagerItem[]) => { + const newColumns = currentColumns.map(column => { + const matchingItem = items.find(item => item.key === column.key); + return matchingItem + ? { ...column, isShown: matchingItem.isSelected ?? column.isShownByDefault } + : column; + }); setCurrentColumns(newColumns); }; - const resetToDefault = () => { - setCurrentColumns(currentColumns.map(column => ({ ...column, isShown: column.isShownByDefault ?? false }))); + const handleOrderChange = (items: ListManagerItem[]) => { + // Update the order of currentColumns based on the new order from ListManager + const newColumns = items.map(item => { + const originalColumn = currentColumns.find(col => col.key === item.key); + if (!originalColumn) { + throw new Error(`Column with key ${item.key} not found`); + } + return { ...originalColumn, isShown: item.isSelected ?? originalColumn.isShownByDefault }; + }); + setCurrentColumns(newColumns); }; - const handleSave = event => { - applyColumns(currentColumns); - onClose(event); + const handleSave = (items: ListManagerItem[]) => { + const updatedColumns = items.map(item => ({ + key: item.key, + title: item.title, + isShown: item.isSelected, + isShownByDefault: item.isShownByDefault, + isUntoggleable: item.isUntoggleable + })); + applyColumns(updatedColumns); + onClose({} as KeyboardEvent); }; - const handleCancel = event => { - setCurrentColumns(appliedColumns.map(column => ({ ...column, isShown: column.isShown ?? column.isShownByDefault }))); - onClose(event); + const handleCancel = () => { + onClose({} as KeyboardEvent); }; return ( @@ -101,56 +131,24 @@ const ColumnManagementModal: FunctionComponent = ( description={ <> {description} - - - - - - - - + } - actions={[ - , - - ]} ouiaId={ouiaId} {...props} > - - {currentColumns.map((column, index) => - - - handleChange(index)} - isDisabled={column.isUntoggleable} - aria-labelledby={`${ouiaId}-column${index}-label`} - ouiaId={`${ouiaId}-column${index}-checkbox`} - id={`${ouiaId}-column${index}-checkbox`} - /> - - - - ]} - /> - - - )} - + ); } diff --git a/packages/module/src/ListManager/ListManager.test.tsx b/packages/module/src/ListManager/ListManager.test.tsx index 8f23cf28..4dd7a3fb 100644 --- a/packages/module/src/ListManager/ListManager.test.tsx +++ b/packages/module/src/ListManager/ListManager.test.tsx @@ -82,4 +82,27 @@ describe('ListManager', () => { await userEvent.click(saveButton); expect(onSave).toHaveBeenCalledWith(expect.any(Array)); }); + + describe('enableDragDrop prop', () => { + it('should sync columns when props change', async () => { + const { rerender } = render(); + + // Initial state + expect(screen.getByTestId('column-check-name')).toBeChecked(); + expect(screen.getByTestId('column-check-version')).not.toBeChecked(); + + // Update columns + const updatedColumns = [ + { key: 'name', title: 'Name', isSelected: false, isShownByDefault: true }, + { key: 'status', title: 'Status', isSelected: true, isShownByDefault: true }, + { key: 'version', title: 'Version', isSelected: true, isShownByDefault: false }, + ]; + + rerender(); + + // State should sync with new props + expect(screen.getByTestId('column-check-name')).not.toBeChecked(); + expect(screen.getByTestId('column-check-version')).toBeChecked(); + }); + }); }); diff --git a/packages/module/src/ListManager/ListManager.tsx b/packages/module/src/ListManager/ListManager.tsx index 2650ee67..e5b11f17 100644 --- a/packages/module/src/ListManager/ListManager.tsx +++ b/packages/module/src/ListManager/ListManager.tsx @@ -1,7 +1,8 @@ import type { FunctionComponent } from 'react'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { DataList, + DataListItem, DataListItemRow, DataListCheck, DataListCell, @@ -12,7 +13,7 @@ import { ActionListItem, ActionListGroup, } from '@patternfly/react-core'; -import { DragDropSort, Droppable } from '@patternfly/react-drag-drop'; +import { DragDropSort, Droppable, DraggableObject } from '@patternfly/react-drag-drop'; import BulkSelect, { BulkSelectValue } from '../BulkSelect'; export interface ListManagerItem { @@ -43,6 +44,8 @@ export interface ListManagerProps { onSave?: (columns: ListManagerItem[]) => void; /** Callback to close the modal */ onCancel?: () => void; + /** Enable drag and drop functionality for reordering items */ + enableDragDrop?: boolean; } const ListManager: FunctionComponent = ( @@ -52,12 +55,18 @@ const ListManager: FunctionComponent = ( onSelectAll, onOrderChange, onSave, - onCancel }: ListManagerProps) => { + onCancel, + enableDragDrop = true }: ListManagerProps) => { const [ currentColumns, setCurrentColumns ] = useState( () => columns.map(column => ({ ...column, isSelected: column.isSelected ?? column.isShownByDefault, id: column.key })) ); + // Sync with columns prop when it changes + useEffect(() => { + setCurrentColumns(columns.map(column => ({ ...column, isSelected: column.isSelected ?? column.isShownByDefault, id: column.key }))); + }, [ columns ]); + const handleChange = (columnKey: string) => { const newColumns = [ ...currentColumns ]; @@ -72,9 +81,9 @@ const ListManager: FunctionComponent = ( onSelect?.(changedColumn); }; - const onDrag = (_event: unknown, newOrder: any[]) => { - const newColumns = newOrder.map((item: any) => { - const found = currentColumns.find(c => c.key === item.id); + const onDrag = (_event: unknown, newOrder: DraggableObject[]) => { + const newColumns = newOrder.map((item: DraggableObject) => { + const found = currentColumns.find(c => c.key === String(item.id)); if (!found) { throw new Error(`Column with key ${item.id} not found`); } @@ -99,6 +108,29 @@ const ListManager: FunctionComponent = ( onSelectAll?.(newColumns); }; + const renderDataListItem = (column: ListManagerItem & { id: string }, index: number) => ( + + handleChange(column.key)} + isDisabled={column.isUntoggleable} + aria-labelledby={`${ouiaId}-column-${index}-label`} + ouiaId={`${ouiaId}-column-${index}-checkbox`} + id={`${ouiaId}-column-${index}-checkbox`} + /> + + + + ]} + /> + + ); + return ( <>
@@ -114,41 +146,30 @@ const ListManager: FunctionComponent = ( } />
- ({ id: column.key, content: - - handleChange(column.key)} - isDisabled={column.isUntoggleable} - aria-labelledby={`${ouiaId}-column-${index}-label`} - ouiaId={`${ouiaId}-column-${index}-checkbox`} - id={`${ouiaId}-column-${index}-checkbox`} - /> - - - - ]} - /> - - }))} - onDrop={onDrag} - overlayProps={{ isCompact: true }} - > - - // eslint-disable-next-line no-console - ({ id: column.key, content: column.title }) - )} - wrapper={} - /> - + {enableDragDrop ? ( + ({ id: column.key, content: renderDataListItem(column, index) }))} + onDrop={onDrag} + overlayProps={{ isCompact: true }} + > + + // eslint-disable-next-line no-console + ({ id: column.key, content: column.title }) + )} + wrapper={} + /> + + ) : ( + + {currentColumns.map((column, index) => ( + + {renderDataListItem(column, index)} + + ))} + + )} diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index 82af8858..0b16baee 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -63,6 +63,9 @@ export * from './LogSnippet'; export { default as ListManager } from './ListManager'; export * from './ListManager'; +export { default as FieldBuilder } from './FieldBuilder'; +export * from './FieldBuilder'; + export { default as ExternalLinkButton } from './ExternalLinkButton'; export * from './ExternalLinkButton'; From e058a31a7a873370b596ca960f96dc7466469c5a Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Wed, 1 Oct 2025 08:33:53 -0400 Subject: [PATCH 2/4] address review comments --- .../ColumnManagementModal.tsx | 19 +++++++++---------- .../module/src/ListManager/ListManager.tsx | 12 +++++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx b/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx index f97bf3f2..6b2e2ed7 100644 --- a/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx +++ b/packages/module/src/ColumnManagementModal/ColumnManagementModal.tsx @@ -75,16 +75,7 @@ const ColumnManagementModal: FunctionComponent = ( setCurrentColumns(currentColumns.map(column => ({ ...column, isShown: column.isShownByDefault ?? false }))); }; - const handleSelect = (item: ListManagerItem) => { - const newColumns = currentColumns.map(column => - column.key === item.key - ? { ...column, isShown: item.isSelected ?? column.isShownByDefault } - : column - ); - setCurrentColumns(newColumns); - }; - - const handleSelectAll = (items: ListManagerItem[]) => { + const updateColumns = (items: ListManagerItem[]) => { const newColumns = currentColumns.map(column => { const matchingItem = items.find(item => item.key === column.key); return matchingItem @@ -94,6 +85,14 @@ const ColumnManagementModal: FunctionComponent = ( setCurrentColumns(newColumns); }; + const handleSelect = (item: ListManagerItem) => { + updateColumns([item]); + }; + + const handleSelectAll = (items: ListManagerItem[]) => { + updateColumns(items); + }; + const handleOrderChange = (items: ListManagerItem[]) => { // Update the order of currentColumns based on the new order from ListManager const newColumns = items.map(item => { diff --git a/packages/module/src/ListManager/ListManager.tsx b/packages/module/src/ListManager/ListManager.tsx index e5b11f17..5f3bc13d 100644 --- a/packages/module/src/ListManager/ListManager.tsx +++ b/packages/module/src/ListManager/ListManager.tsx @@ -46,6 +46,8 @@ export interface ListManagerProps { onCancel?: () => void; /** Enable drag and drop functionality for reordering items */ enableDragDrop?: boolean; + /** Custom aria-label for the DataList */ + dataListAriaLabel?: string; } const ListManager: FunctionComponent = ( @@ -56,7 +58,8 @@ const ListManager: FunctionComponent = ( onOrderChange, onSave, onCancel, - enableDragDrop = true }: ListManagerProps) => { + enableDragDrop = true, + dataListAriaLabel = 'Selected columns' }: ListManagerProps) => { const [ currentColumns, setCurrentColumns ] = useState( () => columns.map(column => ({ ...column, isSelected: column.isSelected ?? column.isShownByDefault, id: column.key })) @@ -115,14 +118,13 @@ const ListManager: FunctionComponent = ( isChecked={column.isSelected} onChange={() => handleChange(column.key)} isDisabled={column.isUntoggleable} - aria-labelledby={`${ouiaId}-column-${index}-label`} ouiaId={`${ouiaId}-column-${index}-checkbox`} id={`${ouiaId}-column-${index}-checkbox`} /> -