Skip to content

Commit

Permalink
[WIP] feat(table): add getRowDescriptor property (#1250)
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanVor authored Jan 19, 2024
1 parent 6a9e189 commit a447acc
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 19 deletions.
9 changes: 9 additions & 0 deletions src/components/Table/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Additional functionality is enabled via HOCs:
| data | Data | `any[]` | |
| columns | Column parameters | `TableColumnConfig[]` | |
| verticalAlign | Vertical alignment of contents | `"top"` `"middle"` | |
| getRowDescriptor | Row mouseleave handler | `(item: any, index: number) => DescriptorType` | |
| getRowId | The row ID, used when selecting and sorting rows. If you skip a row, its ID will be the value of the field in the row data with the same name as the column ID | `string` `((item: any, index: number) => string)` | |
| getRowClassNames | Row CSS classes | `(item: any, index: number) => string[]` | |
| isRowDisabled | Condition for disabling columns | `(item: any, index: number) => boolean` | |
Expand All @@ -41,6 +42,14 @@ Additional functionality is enabled via HOCs:
| stickyHorizontalScroll | A horizontal sticky scroll in a table. NB: A table cannot have a fixed height and a sticky scroll at the same time. A sticky scroll will not work if the table has an overflow. | `boolean` | `false` |
| stickyHorizontalScrollBreakpoint | The threshold that the parent block should reach before making a scroll sticky. This is useful in the console, for example, when the groupActions bar closes the scroll. | `number` | `0` |

### DescriptorType

| Name | Description | Type | Default |
| :--------- | :----------------------------------------------- | :---------------------: | :---------: |
| id | The row ID, used when selecting and sorting rows | `string` `undefined` | `undefined` |
| disabled | Column ID | `boolean` `undefined` | `undefined` |
| classNames | Row CSS classes | ` string[]` `undefined` | `undefined` |

### TableColumnConfig

| Name | Description | Type | Default |
Expand Down
57 changes: 52 additions & 5 deletions src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ export interface TableColumnConfig<I> {
meta?: Record<string, any>;
}

export interface DescriptorType {
/**
* Row ID.
* Used when selecting and sorting rows.
*/
id?: string;

/**
* Row CSS classes.
*/
classNames?: string[];

/**
* Condition for disabling columns.
*/
disabled?: boolean;
}

// TODO: Replace @default in props description with defaultProps in order to work with Storybook.
export interface TableProps<I> extends QAProps {
/** Data */
Expand All @@ -75,15 +93,32 @@ export interface TableProps<I> extends QAProps {
*/
stickyHorizontalScrollBreakpoint?: number;
/**
* @deprecated Use getRowDescriptor instead
*
* Row ID.
* Used when selecting and sorting rows. If you pass a row,
* its ID will be the value of the field in the row data named the same as the column ID.
*/
getRowId?: string | ((item: I, index: number) => string);
/** Row CSS classes. */
/**
* @deprecated Use getRowDescriptor instead
*
* Row CSS classes.
* */
getRowClassNames?: (item: I, index: number) => string[];
/** Condition for disabling columns. */
/**
* @deprecated Use getRowDescriptor instead
*
* Condition for disabling columns.
* */
isRowDisabled?: (item: I, index: number) => boolean;

/**
*
* @returns {DescriptorType} {@link DescriptorType}
*/
getRowDescriptor?: (item: I, index: number) => DescriptorType | undefined;

/** Row click handler. When passed row's hover is visible. */
onRowClick?: (item: I, index: number, event: React.MouseEvent<HTMLTableRowElement>) => void;
/** Row mouseenter handler. */
Expand Down Expand Up @@ -124,9 +159,15 @@ export class Table<I extends TableDataItem = Record<string, string>> extends Rea

// Static methods may be used by HOCs
static getRowId<I extends TableDataItem>(props: TableProps<I>, item: I, rowIndex?: number) {
const {data, getRowId} = props;
const {data, getRowId, getRowDescriptor} = props;
const index = rowIndex ?? data.indexOf(item);

const descriptor = getRowDescriptor?.(item, index);

if (descriptor?.id !== undefined) {
return descriptor.id;
}

if (typeof getRowId === 'function') {
return getRowId(item, index);
}
Expand Down Expand Up @@ -404,12 +445,18 @@ export class Table<I extends TableDataItem = Record<string, string>> extends Rea
verticalAlign,
edgePadding,
wordWrap,
getRowDescriptor,
} = this.props;
const {columnsStyles} = this.state;

const disabled = isRowDisabled ? isRowDisabled(item, rowIndex) : false;
const descriptor = getRowDescriptor?.(item, rowIndex);

const disabled = descriptor?.disabled || isRowDisabled?.(item, rowIndex) || false;

const additionalClassNames =
descriptor?.classNames || getRowClassNames?.(item, rowIndex) || [];

const interactive = Boolean(!disabled && onRowClick);
const additionalClassNames = getRowClassNames ? getRowClassNames(item, rowIndex) : [];

return (
<tr
Expand Down
4 changes: 3 additions & 1 deletion src/components/Table/__stories__/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export default {
},
} as Meta<TableProps<DataItem>>;

const DefaultTemplate: StoryFn<TableProps<DataItem>> = (args) => <Table {...args} />;
const DefaultTemplate: StoryFn<TableProps<DataItem>> = (args) => {
return <Table {...args} />;
};
export const Default = DefaultTemplate.bind({});

const EmptyDefaultTemplate: StoryFn<TableProps<DataItem>> = (args) => <Table {...args} />;
Expand Down
35 changes: 34 additions & 1 deletion src/components/Table/__tests__/Table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import {render, screen, within} from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import {Table} from '../Table';
import {Table, TableProps} from '../Table';

import {columns, data} from './utils';

Expand Down Expand Up @@ -152,4 +152,37 @@ describe('Table', () => {

expect(table).toHaveStyle(style);
});

test('getRowDescriptor property case', () => {
const EXPTECTED_CLASS_NAME = 'EXPTECTED_CLASS_NAME';
const EXPECTED_ID = 'EXPECTED_ID';

const ROW_ID = 1;

const getRowDescriptor: TableProps<unknown>['getRowDescriptor'] = (_, index) => {
if (index === ROW_ID) {
return {classNames: [EXPTECTED_CLASS_NAME], disabled: true, id: EXPECTED_ID};
}

return undefined;
};

render(
<Table data={data} columns={columns} getRowDescriptor={getRowDescriptor} qa={qaId} />,
);

const table = screen.getByTestId(qaId);
const tbody = within(table).getAllByRole('rowgroup');
const rows = within(tbody[1]).getAllByRole('row');

expect(rows.length).toBe(data.length);
expect(rows.length > 1).toBe(true);

rows.forEach((row, i) => {
const expectedFlag = ROW_ID === i;

expect(row.className.includes(EXPTECTED_CLASS_NAME)).toBe(expectedFlag);
expect(row.className.includes('yc-table__row_disabled')).toBe(expectedFlag);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,15 @@ export function withTableActions<I extends TableDataItem, E extends {} = {}>(
}

private renderBodyCell = (item: I, index: number) => {
const {isRowDisabled, getRowActions, rowActionsSize} = this.props;
const {isRowDisabled, getRowActions, rowActionsSize, getRowDescriptor} = this.props;
const actions = getRowActions(item, index);

if (actions.length === 0) {
return null;
}

const disabled = isRowDisabled ? isRowDisabled(item, index) : false;
const disabled =
getRowDescriptor?.(item, index)?.disabled || isRowDisabled?.(item, index) || false;

return (
<div className={b('actions')}>
Expand Down
24 changes: 14 additions & 10 deletions src/components/Table/hoc/withTableSelection/withTableSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function withTableSelection<I extends TableDataItem, E extends {} = {}>(
onSelectionChange, // eslint-disable-line @typescript-eslint/no-unused-vars
columns,
onRowClick,
getRowClassNames,
getRowDescriptor,
...restTableProps
} = this.props;

Expand All @@ -49,7 +49,7 @@ export function withTableSelection<I extends TableDataItem, E extends {} = {}>(
{...(restTableProps as Omit<TableProps<I>, 'columns'> & E)}
columns={this.enhanceColumns(columns)}
onRowClick={this.enhanceOnRowClick(onRowClick)}
getRowClassNames={this.enhanceGetRowClassNames(getRowClassNames)}
getRowDescriptor={this.enhanceGetRowDescriptor(getRowDescriptor)}
/>
);
}
Expand Down Expand Up @@ -201,13 +201,15 @@ export function withTableSelection<I extends TableDataItem, E extends {} = {}>(
);

// eslint-disable-next-line @typescript-eslint/member-ordering
private enhanceGetRowClassNames = _memoize(
(getRowClassNames?: (item: I, index: number) => string[]) => {
private enhanceGetRowDescriptor = _memoize(
(getRowDescriptor?: TableProps<I>['getRowDescriptor']) => {
return (item: I, index: number) => {
const {selectedIds} = this.props;
const classNames = getRowClassNames
? getRowClassNames(item, index).slice()
: [];
const {selectedIds, getRowClassNames} = this.props;
const classNames =
getRowDescriptor?.(item, index)?.classNames?.slice() ||
getRowClassNames?.(item, index) ||
[];

const id = Table.getRowId(this.props, item, index);
const selected = selectedIds.includes(id);

Expand All @@ -219,11 +221,13 @@ export function withTableSelection<I extends TableDataItem, E extends {} = {}>(
);

private isDisabled = (item: I, index: number) => {
const {isRowDisabled, isRowSelectionDisabled} = this.props;
const {isRowDisabled, isRowSelectionDisabled, getRowDescriptor} = this.props;
if (isRowSelectionDisabled && isRowSelectionDisabled(item, index)) {
return true;
}
return isRowDisabled ? isRowDisabled(item, index) : false;
return (
getRowDescriptor?.(item, index)?.disabled || isRowDisabled?.(item, index) || false
);
};
};
}

0 comments on commit a447acc

Please sign in to comment.