import { isValidElement, useRef, useState } from 'react'; import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; import { Button, Checkbox, Radio, TextInput, KeyTypes, getUniqueId } from '@patternfly/react-core'; import RhUiEditFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon'; import RhMicronsCloseIcon from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import inlineEditStyles from '@patternfly/react-styles/css/components/InlineEdit/inline-edit'; import { css } from '@patternfly/react-styles'; interface EditButtonsCellProps { onClick: (type: 'save' | 'cancel' | 'edit') => void; elementToFocusOnEditRef?: React.MutableRefObject; rowAriaLabel: string; } const EditButtonsCell: React.FunctionComponent = ({ onClick, elementToFocusOnEditRef, rowAriaLabel = 'row' }) => { const editButtonRef = useRef(undefined); const onKeyDown = (event: React.KeyboardEvent, button: 'edit' | 'stopEditing') => { const focusRef = button === 'edit' ? elementToFocusOnEditRef : editButtonRef; if (event.key === KeyTypes.Enter || event.key === KeyTypes.Space) { // because space key triggers click event before keyDown, we have to prevent default behaviour and trigger click manually event.preventDefault(); (event.target as HTMLButtonElement).click(); setTimeout(() => { focusRef?.current?.focus(); }, 0); } }; return ( <>
); }; interface EditableCellProps { dataLabel: string; staticValue: React.ReactNode; editingValue: React.ReactNode; role?: string; ariaLabel?: string; } const EditableCell: React.FunctionComponent = ({ dataLabel, staticValue, editingValue, role, ariaLabel }) => { const hasMultipleInputs = Array.isArray(editingValue) && editingValue.every((elem) => isValidElement(elem)); return (
{staticValue}
{hasMultipleInputs ? (
{(editingValue as React.ReactElement[]).map((elem, index) => (
{elem}
))}
) : (
{editingValue}
)} ); }; interface EditableRow { data: CustomData; columnNames: ColumnNames; dataOptions?: CustomDataOptions; saveChanges: (editedData: CustomData) => void; ariaLabel: string; rowIndex?: number; } const EditableRow: React.FunctionComponent = ({ data, columnNames, dataOptions, saveChanges, ariaLabel, rowIndex }) => { const [editable, setEditable] = useState(false); const [editedData, setEditedData] = useState(data); const inputRef = useRef(undefined); return ( setEditedData((data) => ({ ...data, textInput: (e.target as HTMLInputElement).value }))} /> } /> } /> { const id = getUniqueId('checkbox'); return ( setEditedData((data) => ({ ...data, checkboxes: checked ? [...data.checkboxes, option] : data.checkboxes.filter((item) => item !== option) })) } /> ); })} /> { const id = getUniqueId('radio'); return ( setEditedData((data) => ({ ...data, radios: option }))} /> ); })} /> { type === 'edit' ? setEditable(true) : setEditable(false); type === 'save' && saveChanges(editedData); type === 'cancel' && setEditedData(data); }} rowAriaLabel={ariaLabel} elementToFocusOnEditRef={inputRef} /> ); }; interface CustomData { textInput: string; textInputDisabled: string | null; checkboxes: string[]; radios: string; } interface CustomDataOptions { checkboxes: string[]; radios: string[]; } type ColumnNames = { [K in keyof T]: string }; export const TableEditable: React.FunctionComponent = () => { // In real usage, this data would come from some external source like an API via props. const initialRows: CustomData[] = [ { textInput: 'Editable text 1', textInputDisabled: 'Non-editable text 1', checkboxes: ['Option A'], radios: 'Option A' }, { textInput: 'Editable text 2', textInputDisabled: null, checkboxes: [], radios: 'Option B' }, { textInput: 'Editable text 3', textInputDisabled: 'Non-editable text 3', checkboxes: ['Option A', 'Option B'], radios: 'Option A' } ]; // List of all selectable options for some cells of initialRows const initialRowsOptions: CustomDataOptions[] = [ { checkboxes: ['Option A', 'Option B', 'Option C'], radios: ['Option A', 'Option B'] }, { checkboxes: ['Option A', 'Option B'], radios: ['Option A', 'Option B', 'Option C'] }, { checkboxes: ['Option A', 'Option B'], radios: ['Option A', 'Option B'] } ]; const [rows, setRows] = useState(initialRows); const columnNames: ColumnNames = { textInput: 'Text input', textInputDisabled: 'Disabled text input', checkboxes: 'Checkboxes', radios: 'Radios' }; return ( {rows.map((data, index) => ( { setRows((rows) => rows.map((row, i) => (i === index ? editedRow : row))); }} ariaLabel={`row ${index + 1}`} > ))}
{columnNames.textInput} {columnNames.textInputDisabled} {columnNames.checkboxes} {columnNames.radios}
); };