CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-patternfly--react-table

React table components for PatternFly design system with sorting, selection, expansion, and editing capabilities

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

editing.mddocs/

Editing Capabilities

Inline editing components for text and select inputs with validation support and row-level editing controls.

Capabilities

EditableTextCell

Inline editable text cell component with validation support.

/**
 * Inline editable text cell component
 * @param props - EditableTextCell configuration props
 * @returns EditableTextCell component
 */
function EditableTextCell(props: IEditableTextCell): React.FunctionComponent<IEditableTextCell>;

interface IEditableTextCell extends React.HTMLProps<HTMLDivElement> {
  /** The current value of the text input */
  value: string;
  /** Row index of this text cell */
  rowIndex: number;
  /** Cell index of this text cell */
  cellIndex: number;
  /** Props to build the input */
  props: EditableTextCellProps;
  /** Event handler which fires when user changes the text in this cell */
  handleTextInputChange: (
    newValue: string,
    event: React.FormEvent<HTMLInputElement>,
    rowIndex: number,
    cellIndex: number
  ) => void;
  /** Accessible label of the text input */
  inputAriaLabel: string;
  /** Flag indicating if the text input is disabled */
  isDisabled?: boolean;
}

interface EditableTextCellProps {
  /** Name of the input */
  name: string;
  /** Value to display in the cell */
  value: string;
  /** Editable value (can differ from display value) */
  editableValue?: string;
  /** Whether the cell is valid */
  isValid?: boolean;
  /** Error text to display */
  errorText?: string;
  /** Arbitrary data to pass to the internal text input */
  [key: string]: any;
}

Usage Examples:

import { EditableTextCell } from "@patternfly/react-table";

// Basic editable text cell
<Td>
  <EditableTextCell
    value={rowData.name}
    rowIndex={rowIndex}
    cellIndex={0}
    props={{
      name: 'name',
      value: rowData.name,
      editableValue: editValues[`${rowIndex}-0`],
      isValid: validationResults[`${rowIndex}-0`]?.isValid !== false
    }}
    handleTextInputChange={(newValue, event, rowIndex, cellIndex) => {
      setEditValues(prev => ({
        ...prev,
        [`${rowIndex}-${cellIndex}`]: newValue
      }));
      validateCell(rowIndex, cellIndex, newValue);
    }}
    inputAriaLabel={`Edit name for row ${rowIndex}`}
  />
</Td>

// Editable text cell with validation error
<Td>
  <EditableTextCell
    value={rowData.email}
    rowIndex={rowIndex}
    cellIndex={1}
    props={{
      name: 'email',
      value: rowData.email,
      editableValue: editValues[`${rowIndex}-1`],
      isValid: false,
      errorText: 'Please enter a valid email address',
    }}
    handleTextInputChange={handleEmailChange}
    inputAriaLabel={`Edit email for row ${rowIndex}`}
    isDisabled={!isEditing}
  />
</Td>

EditableSelectInputCell

Inline editable select cell component with support for single and multi-select.

/**
 * Inline editable select cell component
 * @param props - EditableSelectInputCell configuration props
 * @returns EditableSelectInputCell component
 */
function EditableSelectInputCell(props: IEditableSelectInputCell): React.FunctionComponent<IEditableSelectInputCell>;

interface IEditableSelectInputCell extends Omit<React.HTMLProps<HTMLElement | HTMLDivElement>, 'onSelect' | 'onToggle'> {
  /** Row index of this select input cell */
  rowIndex: number;
  /** Cell index of this select input cell */
  cellIndex: number;
  /** Props to build the select component */
  props: EditableSelectInputProps;
  /** Event handler which fires when user selects an option in this cell */
  onSelect: (
    event: React.MouseEvent | React.ChangeEvent,
    newValue: any | any[],
    rowIndex: number,
    cellIndex: number,
    isPlaceholder?: boolean
  ) => void;
  /** Options to display in the expandable select menu */
  options?: React.ReactElement<any>[];
  /** Flag indicating the select input is disabled */
  isDisabled?: boolean;
  /** Flag indicating the toggle gets placeholder styles */
  isPlaceholder?: boolean;
  /** Current selected options to display as the read only value of the table cell */
  selections?: any | any[];
  /** Flag indicating the select menu is open */
  isOpen?: boolean;
  /** Event handler which fires when the select toggle is toggled */
  onToggle?: (event: React.MouseEvent | undefined) => void;
  /** Event handler which fires when the user clears the selections */
  clearSelection?: (event: React.MouseEvent, rowIndex: number, cellIndex: number) => void;
}

interface EditableSelectInputProps {
  /** Name of the select input */
  name: string;
  /** Value to display in the cell */
  value: string | string[];
  /** Flag controlling isOpen state of select */
  isSelectOpen: boolean;
  /** Single select option value for single select menus, or array for multi select */
  selected: any | any[];
  /** Array of react elements to display in the select menu */
  options: React.ReactElement<any>[];
  /** Props to be passed down to the select component */
  editableSelectProps?: SelectProps;
  /** Error text to display */
  errorText?: string;
  /** Arbitrary data to pass to the internal select component */
  [key: string]: any;
}

Usage Examples:

import { EditableSelectInputCell, SelectOption } from "@patternfly/react-table";

// Basic editable select cell
const statusOptions = [
  <SelectOption key="active" value="active">Active</SelectOption>,
  <SelectOption key="inactive" value="inactive">Inactive</SelectOption>,
  <SelectOption key="pending" value="pending">Pending</SelectOption>
];

<Td>
  <EditableSelectInputCell
    rowIndex={rowIndex}
    cellIndex={2}
    props={{
      name: 'status',
      value: rowData.status,
      isSelectOpen: openSelects[`${rowIndex}-2`] || false,
      selected: editValues[`${rowIndex}-2`] || rowData.status,
      options: statusOptions
    }}
    options={statusOptions}
    selections={editValues[`${rowIndex}-2`] || rowData.status}
    isOpen={openSelects[`${rowIndex}-2`] || false}
    onSelect={(event, value, rowIndex, cellIndex) => {
      setEditValues(prev => ({
        ...prev,
        [`${rowIndex}-${cellIndex}`]: value
      }));
      setOpenSelects(prev => ({
        ...prev,
        [`${rowIndex}-${cellIndex}`]: false
      }));
    }}
    onToggle={(event) => {
      setOpenSelects(prev => ({
        ...prev,
        [`${rowIndex}-2`]: !prev[`${rowIndex}-2`]
      }));
    }}
  />
</Td>

// Multi-select editable cell
<Td>
  <EditableSelectInputCell
    rowIndex={rowIndex}
    cellIndex={3}
    props={{
      name: 'tags',
      value: rowData.tags,
      isSelectOpen: openSelects[`${rowIndex}-3`] || false,
      selected: editValues[`${rowIndex}-3`] || rowData.tags,
      options: tagOptions,
      editableSelectProps: { variant: 'checkbox' }
    }}
    options={tagOptions}
    selections={editValues[`${rowIndex}-3`] || rowData.tags}
    isOpen={openSelects[`${rowIndex}-3`] || false}
    onSelect={handleMultiSelect}
    onToggle={toggleMultiSelect}
    clearSelection={(event, rowIndex, cellIndex) => {
      setEditValues(prev => ({
        ...prev,
        [`${rowIndex}-${cellIndex}`]: []
      }));
    }}
  />
</Td>

Row-Level Editing

Row Edit Configuration

// Row editing event handler
type OnRowEdit = (
  event: React.MouseEvent<HTMLButtonElement>,
  type: RowEditType,
  isEditable?: boolean,
  rowIndex?: number,
  validationErrors?: RowErrors
) => void;

// Row edit action types
type RowEditType = 'save' | 'cancel' | 'edit';

// Row validation errors
interface RowErrors {
  [name: string]: string[];
}

// Row validation definition
interface IValidatorDef {
  validator: (value: string) => boolean;
  errorText: string;
  name: string;
}

Row Edit Properties

// IRow interface includes editing properties
interface IRow extends RowType {
  /** Whether the row is editable */
  isEditable?: boolean;
  /** Whether the row is valid */
  isValid?: boolean;
  /** Array of validation functions to run against every cell for a given row */
  rowEditValidationRules?: IValidatorDef[];
  /** Aria label for edit button in inline edit */
  rowEditBtnAriaLabel?: (idx: number) => string;
  /** Aria label for save button in inline edit */
  rowSaveBtnAriaLabel?: (idx: number) => string;
  /** Aria label for cancel button in inline edit */
  rowCancelBtnAriaLabel?: (idx: number) => string;
}

Usage Examples:

// Row with editing configuration
const editableRows = [
  {
    cells: ['John Doe', 'john@example.com', 'Active'],
    isEditable: editingRows.includes(0),
    isValid: rowValidation[0]?.isValid,
    rowEditValidationRules: [
      {
        name: 'email',
        validator: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
        errorText: 'Please enter a valid email address'
      },
      {
        name: 'name',
        validator: (value) => value.trim().length > 0,
        errorText: 'Name is required'
      }
    ],
    rowEditBtnAriaLabel: (idx) => `Edit row ${idx}`,
    rowSaveBtnAriaLabel: (idx) => `Save changes for row ${idx}`,
    rowCancelBtnAriaLabel: (idx) => `Cancel editing row ${idx}`
  }
];

// Row edit handlers
const handleRowEdit = (event, type, isEditable, rowIndex, validationErrors) => {
  switch (type) {
    case 'edit':
      setEditingRows(prev => [...prev, rowIndex]);
      break;
    case 'save':
      if (!validationErrors || Object.keys(validationErrors).length === 0) {
        // Save the changes
        saveRowChanges(rowIndex);
        setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
      }
      break;
    case 'cancel':
      // Discard changes
      discardRowChanges(rowIndex);
      setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
      break;
  }
};

Validation Support

Cell-Level Validation

// Validation function for individual cells
const validateCell = (rowIndex: number, cellIndex: number, value: string): boolean => {
  const row = rows[rowIndex];
  if (row.rowEditValidationRules) {
    const rule = row.rowEditValidationRules[cellIndex];
    if (rule && !rule.validator(value)) {
      setValidationErrors(prev => ({
        ...prev,
        [`${rowIndex}-${cellIndex}`]: rule.errorText
      }));
      return false;
    }
  }
  
  // Clear validation error if valid
  setValidationErrors(prev => {
    const newErrors = { ...prev };
    delete newErrors[`${rowIndex}-${cellIndex}`];
    return newErrors;
  });
  return true;
};

Row-Level Validation

// Validation function for entire rows
const validateRow = (rowIndex: number): RowErrors | null => {
  const row = rows[rowIndex];
  const errors: RowErrors = {};
  
  if (row.rowEditValidationRules) {
    row.rowEditValidationRules.forEach((rule, cellIndex) => {
      const cellValue = getCellValue(rowIndex, cellIndex);
      if (!rule.validator(cellValue)) {
        if (!errors[rule.name]) {
          errors[rule.name] = [];
        }
        errors[rule.name].push(rule.errorText);
      }
    });
  }
  
  return Object.keys(errors).length > 0 ? errors : null;
};

Edit State Management

Managing Edit State

// Example state management for table editing
const [editingRows, setEditingRows] = useState<number[]>([]);
const [editValues, setEditValues] = useState<Record<string, any>>({});
const [openSelects, setOpenSelects] = useState<Record<string, boolean>>({});
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});

// Helper functions
const startEditing = (rowIndex: number) => {
  setEditingRows(prev => [...prev, rowIndex]);
  // Initialize edit values with current row values
  const row = rows[rowIndex];
  row.cells.forEach((cell, cellIndex) => {
    setEditValues(prev => ({
      ...prev,
      [`${rowIndex}-${cellIndex}`]: cell
    }));
  });
};

const saveChanges = (rowIndex: number) => {
  const validationErrors = validateRow(rowIndex);
  if (!validationErrors) {
    // Apply changes to the row data
    const updatedRows = [...rows];
    updatedRows[rowIndex].cells = updatedRows[rowIndex].cells.map((_, cellIndex) => 
      editValues[`${rowIndex}-${cellIndex}`] || _
    );
    setRows(updatedRows);
    setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
    clearEditState(rowIndex);
  }
};

const cancelEditing = (rowIndex: number) => {
  setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
  clearEditState(rowIndex);
};

const clearEditState = (rowIndex: number) => {
  // Clear edit values for this row
  const keysToRemove = Object.keys(editValues).filter(key => 
    key.startsWith(`${rowIndex}-`)
  );
  setEditValues(prev => {
    const newState = { ...prev };
    keysToRemove.forEach(key => delete newState[key]);
    return newState;
  });
  
  // Clear validation errors for this row
  const errorKeysToRemove = Object.keys(validationErrors).filter(key => 
    key.startsWith(`${rowIndex}-`)
  );
  setValidationErrors(prev => {
    const newState = { ...prev };
    errorKeysToRemove.forEach(key => delete newState[key]);
    return newState;
  });
};

Install with Tessl CLI

npx tessl i tessl/npm-patternfly--react-table

docs

content-display.md

core-components.md

editing.md

index.md

interactive-features.md

layout-scrolling.md

utilities.md

tile.json