import { DataTable } from '@uhg-abyss/web/ui/DataTable';import { useDataTable } from '@uhg-abyss/web/hooks/useDataTable';Editable cells
To seamlessly integrate editable cells into your DataTable, use the EditableTableCell sub-component.
import { EditableTableCell } from '@uhg-abyss/web/ui/DataTable';{ header: 'Column 1', accessorKey: 'col1', cell: (props) => { return <EditableTableCell {...props} inputType="text" />; // Pass in all the props from cell into EditableTableCell }, footer: 'Footer 1',}To display an Abyss-managed column containing buttons for editing data, set editCellConfig.enableColumnEdit to true.
const dataTableProps = useDataTable({ //... editCellConfig: { enableColumnEdit: true, }, //...});Here is a basic example with no configuration:
This example shows how to use the value of the cell to determine if the cell should be editable or not.
{ header: 'Column 4', accessorKey: 'col4', cell: (props) => { if (props.getValue() !== 'Completed') { return <EditableTableCell {...props} />; } return props.renderValue(); }, footer: 'Footer 4',}By default, when a row is not being edited, the cell value is the return value of the renderValue method. This example combines editable data with custom cell rendering.
{ header: 'Column 4', accessorKey: 'col4', cell: (props) => { const value = props.getValue(); const table = props.table; const row = props.row;
if (table.options.meta.editActions.rowsInEditMode[row.id]) { return <EditableTableCell {...props} />; } // Custom formatting when not being edited return ( <React.Fragment> <IconSymbol icon="home" size="24px" /> {value} </React.Fragment> ); }, footer: 'Footer 4',}Here is an advanced example of editable data with additional configuration.
- Columns 1 and 2 are editable.
- Column 3 uses a custom cell renderer when not in edit mode.
- Column 4 does not allow the value to be edited if the value is
'Completed'.
Inputs
Input types
To change the input type of an editable cell, use the inputType prop of the EditableTableCell sub-component. The available input types are:
text, which renders aTextInput(this is the default)date, which renders aDateInputselect, which renders aSelectInput
{ header: 'Column 4', accessorKey: 'col4', cell: (props) => { return <EditableTableCell {...props} inputType="date" />; },}For type 'select', you must also provide the options prop, which is an array of objects with value and label properties.
{ header: 'Column 4', accessorKey: 'col4', cell: (props) => { const options= [ { value: 'Completed', label: 'Completed' }, { value: 'Not Completed', label: 'Not Completed' }, { value: 'In Progress', label: 'In Progress' } ]
return <EditableTableCell {...props} inputType="select" options={options} />; },}Customizing inputs
You can customize the behavior and appearance of each input type using their respective configuration props: textConfig, selectConfig, and dateConfig.
// TextInput<EditableTableCell {...props} inputType="text" textConfig={{}} />
// DateInput<EditableTableCell {...props} inputType="date" dateConfig={{}} />
// SelectInput<EditableTableCell {...props} inputType="select" options={options} selectConfig={{}} />TextInput
Use textConfig to customize the TextInput behavior and appearance. The available props are:
typemaskmaskConfigmaxLengthplaceholderautoCompleteinputLeftElementleftAddOnrightAddOnleftAddOnDescriptionrightAddOnDescriptionprefixsuffixpreserveWhitespacereturnUnmaskedValueemptyMaskChar
To learn about prop usage, refer to TextInput.
Note: When using mask / maskConfig with EditableTableCell, most times you will set returnUnmaskedValue to true to maintain consistent data structure for filtering, sorting, and database storage.
DateInput
Use dateConfig to customize the DateInput behavior. The available props are:
enableOutsideScrollhidePlaceholderinputLeftElementinputOnlymaxDateminDateexcludeDate
To learn about prop usage, refer to DateInput.
SelectInput
Use selectConfig to customize the SelectInput behavior. The available props are:
isSearchableinputLeftElementenableOutsideScrollvirtualmaxListHeight
To learn about prop usage, refer to SelectInput.
Row Editing
In the Abyss-managed Edit column, there are three buttons associated with each row:
- The edit button toggles editing mode for the row
- The cancel button reverts any changes to the previous unedited data
- The confirm button updates the table data state to reflect the changes
By default, when editCellConfig.enableColumnEdit is true, all rows will be editable. To disable editing for specific rows, use the editCellConfig.canEditRow function. This function receives the row data as an argument and should return a boolean value indicating whether the row is editable. The example below disables editing for rows where the value of col4 is 'Completed'.
const dataTableProps = useDataTable({ // ... editCellConfig: { enableColumnEdit: true, canEditRow: (row) => { return row.col4 !== 'Completed'; }, }, // ...});Use the editCellConfig.onEditCompleted callback to perform additional actions, such as synchronizing changes with a back-end server, when editing is completed. This callback receives two arguments: previousRow and updatedRow, which represent the row data before and after editing, respectively.
Note: If the data is unchanged, the callback will not be executed.
const dataTableProps = useDataTable({ // ... editCellConfig: { enableColumnEdit: true, onEditCompleted: (previousRow, updatedRow) => { console.log('Previous row data:', previousRow); console.log('Updated row data:', updatedRow); // Additional logic to handle changes }, }, // ...});Validation
Input validation ensures data quality and consistency before saving changes. The EditableTableCell component provides flexible validation by accepting a custom validate function that receives both the cell value and the entire row data, allowing for contextual and cross-field validation.
Note: inputType="select" does not allow usage of validate since an item will always be selected and the options provided will always be valid.
Custom Validation
To add validation to an editable cell, use the validate prop. This function is called on every value change and should return an error message, as a string, if validation fails, or undefined if the value is valid. The function receives two arguments:
value: The current cell value being validatedrow: The entire row data, enabling cross-field validation logic
{ header: 'Status', accessorKey: 'status', cell: (props) => { return ( <EditableTableCell {...props} inputType="text" validate={(value, row) => { // Basic required validation if (!value || (typeof value === 'string' && value.trim() === '')) { return 'This field is required'; } // Minimum length validation if (typeof value === 'string' && value.length < 3) { return 'Must be at least 3 characters'; } return undefined; // Validation passes }} /> ); },}Common Validation Patterns
Pattern validation: Enforce specific formats using regular expressions.
validate={(value, row) => { if (typeof value === 'string' && value && !/^[A-Z0-9]+$/.test(value)) { return 'Only uppercase letters and numbers allowed'; } return undefined;}}Cross-field validation: Validate based on the values of other cells in the same row.
validate={(value, row) => { if (!value) return 'End date is required';
const startDate = new Date(row.startDate); const endDate = new Date(value as string);
if (endDate <= startDate) { return 'End date must be after start date'; }
return undefined;}}The save button is automatically disabled when any cell has validation errors. Clicking cancel reverts changes and clears all validation errors.
Interactive Example
Custom Action
DataTable provides a way to perform actions on individual rows. This is useful for operations like deleting or programmatically modifying a row's data.
The configuration for editCellConfig.customAction the same pattern / usage as Individual Actions
- Single Action: editCellConfig.customAction.actionMode = 'button'
- Multiple Actions: editCellConfig.customAction.actionMode = 'dropdown'
Note: Please refer to the Individual Row Actions documentation for detailed usage examples and configuration options.
Single Action
Multiple Actions
Programmatically edit cells
To default certain rows to edit mode on initial render, use the initialStateConfig.initialRowsInEditMode property. This property accepts an object where the keys are the unique IDs of the rows and the values are booleans indicating whether that row should be in edit mode.
const dataTableProps = useDataTable({ //... initialStateConfig: { initialRowsInEditMode: { '0': true, '1': true, }, //... },});To programmatically update which rows are in edit mode, use setRowsInEditMode method, which accepts the same object format as initialStateConfig.initialRowsInEditMode.
const dataTableProps = useDataTable({ //...});
const openEditToggle = () => { dataTableProps.editRowState.setRowsInEditMode({ '2': true, });};
//...
return <Button onClick={openEditToggle}>Edit Row 3</Button>;Component Tokens
Note: Click on the token row to copy the token to your clipboard.
DataTable Tokens
| Token Name | Value | |
|---|---|---|
| data-table.color.border.column-header.drag | #002677 | |
| data-table.color.border.root | #CBCCCD | |
| data-table.color.border.row.drag | #002677 | |
| data-table.color.border.table | #CBCCCD | |
| data-table.color.icon.column-header-menus.grouping.active | #002677 | |
| data-table.color.icon.column-header-menus.grouping.hover | #004BA0 | |
| data-table.color.icon.column-header-menus.grouping.rest | #196ECF | |
| data-table.color.icon.column-header-menus.sorting.active | #002677 | |
| data-table.color.icon.column-header-menus.sorting.hover | #004BA0 | |
| data-table.color.icon.column-header-menus.sorting.rest | #196ECF | |
| data-table.color.icon.drag-handle.active | #002677 | |
| data-table.color.icon.drag-handle.hover | #004BA0 | |
| data-table.color.icon.drag-handle.rest | #196ECF | |
| data-table.color.icon.expander.active | #002677 | |
| data-table.color.icon.expander.disabled | #7D7F81 | |
| data-table.color.icon.expander.hover | #004BA0 | |
| data-table.color.icon.expander.rest | #196ECF | |
| data-table.color.icon.utility.drag-alternative.active | #000000 | |
| data-table.color.icon.utility.drag-alternative.disabled | #7D7F81 | |
| data-table.color.icon.utility.drag-alternative.hover | #323334 | |
| data-table.color.icon.utility.drag-alternative.rest | #4B4D4F | |
| data-table.color.icon.utility.filter.active | #002677 | |
| data-table.color.icon.utility.filter.hover | #004BA0 | |
| data-table.color.icon.utility.filter.rest | #196ECF | |
| data-table.color.surface.column-header.active | #E5F8FB | |
| data-table.color.surface.column-header.default | #F3F3F3 | |
| data-table.color.surface.column-header.drag | #E5F8FB | |
| data-table.color.surface.footer | #F3F3F3 | |
| data-table.color.surface.header | #FFFFFF | |
| data-table.color.surface.root | #FFFFFF | |
| data-table.color.surface.row.drag | #E5F8FB | |
| data-table.color.surface.row.even | #FAFCFF | |
| data-table.color.surface.row.highlighted | #E5F8FB | |
| data-table.color.surface.row.hover | #F3F3F3 | |
| data-table.color.surface.row.odd | #FFFFFF | |
| data-table.color.surface.table | #FFFFFF | |
| data-table.color.text.cell | #4B4D4F | |
| data-table.color.text.column-header | #4B4D4F | |
| data-table.color.text.header.heading | #002677 | |
| data-table.color.text.header.paragraph | #4B4D4F | |
| data-table.border-radius.all.container | 8px | |
| data-table.border-width.all.column-header.drag | 2px | |
| data-table.border-width.all.root | 1px | |
| data-table.border-width.all.row.drag | 2px | |
| data-table.border-width.all.table | 1px | |
| data-table.sizing.all.icon.column-header-menus | 20px | |
| data-table.sizing.all.icon.drag-handle-row | 24px | |
| data-table.sizing.all.icon.expander-column | 24px | |
| data-table.sizing.all.icon.utility.drag-alternative | 20px | |
| data-table.sizing.all.icon.utility.filter | 20px | |
| data-table.sizing.height.cell.comfortable | 48px | |
| data-table.sizing.height.cell.compact | 32px | |
| data-table.sizing.height.cell.cozy | 40px | |
| data-table.spacing.gap.horizontal.button-group | 8px | |
| data-table.spacing.gap.horizontal.cell | 4px | |
| data-table.spacing.gap.horizontal.drag-alternative | 8px | |
| data-table.spacing.gap.horizontal.input-container | 8px | |
| data-table.spacing.gap.horizontal.slot-wrapper | 24px | |
| data-table.spacing.gap.vertical.column-header | 2px | |
| data-table.spacing.gap.vertical.header | 4px | |
| data-table.spacing.gap.filter-two-inputs | 16px | |
| data-table.spacing.padding.all.column-header | 8px | |
| data-table.spacing.padding.all.column-header-menus | 2px | |
| data-table.spacing.padding.all.header | 16px | |
| data-table.spacing.padding.all.result-text | 16px | |
| data-table.spacing.padding.all.slot-wrapper | 16px | |
| data-table.spacing.padding.horizontal.cell | 8px | |
| data-table.spacing.padding.vertical.button-group | 8px | |
| data-table.spacing.padding.vertical.cell | 4px | |
| data-table.elevation.column.pinned.left | 6px 0px 8px -2px rgba(0,0,0,0.16) | |
| data-table.elevation.column.pinned.right | -6px 0px 8px -2px rgba(0,0,0,0.16) | |
| data-table.elevation.column-header | 0px 6px 8px -2px rgba(0,0,0,0.16) | |
| data-table.elevation.table-settings-dropdown.section-header | 0px 2px 4px -2px rgba(0,0,0,0.16) |