Skip to content

Commit

Permalink
fix: redo cell selection method
Browse files Browse the repository at this point in the history
  • Loading branch information
shayan-shojaei committed Feb 22, 2022
1 parent 5672a87 commit c2e819e
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 128 deletions.
39 changes: 24 additions & 15 deletions src/components/Cell/Cell.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { CSSProperties } from 'react';
import { CellData, DataTypes } from '../../utils/data.types';
import { CellData, DataTypes, ModifierKey } from '../../utils/data.types';
import './Cell.scss';
import cn from 'classnames';
import { isValidNumber } from '../../utils';
Expand All @@ -22,10 +22,8 @@ export interface IProps {
style?: CSSProperties;
/** HTML role attribute */
role?: string;
/** Tab index */
tabIndex?: number;
/** On Click */
onClick?: () => void;
onClick?: (modifier: ModifierKey) => void;
/** On Double Click */
onDoubleClick?: () => void;
}
Expand All @@ -38,7 +36,6 @@ function Cell({
highlighted,
editing,
selected,
tabIndex,
onClick,
onDoubleClick,
role
Expand All @@ -63,6 +60,18 @@ function Cell({
!!onChange && onChange({ ...data, value: newValue });
};

const handleClick = (e: React.MouseEvent<HTMLElement>) => {
if (onClick === undefined) return;

let modifer: ModifierKey = undefined;
if (e.ctrlKey) {
modifer = 'ctrl';
} else if (e.shiftKey) {
modifer = 'shift';
}
onClick(modifer);
};

return (
<div
className={cn({
Expand All @@ -73,8 +82,9 @@ function Cell({
[className]: !!className
})}
style={style}
tabIndex={tabIndex}
role={role}
onClick={handleClick}
onDoubleClick={onDoubleClick}
>
{editing ? (
<input
Expand All @@ -85,11 +95,7 @@ function Cell({
onChange={handleInputChange}
/>
) : (
<span
onClick={onClick}
onDoubleClick={onDoubleClick}
className="cell--display"
>
<span className="cell--display">
{data.value !== undefined ? `${data.value}` : ''}
</span>
)}
Expand All @@ -100,22 +106,25 @@ function Cell({
/**
* Cell Component
* @param {DataTypes} data - Cell data object.
* @param onChange - fires when the cell value **or** state changes, passes in the new cell data object.
* @param onValueChange - fires when the cell value changes, passes in the new cell value data.
* @param {function} onChange - fires when the cell value **or** state changes, passes in the new cell data object.
* @param {function} onValueChange - fires when the cell value changes, passes in the new cell value data.
* @param {function} onClick - fires when the cell is clicked; *will not fire while the cell is being edited*.
* passes in the modifer key if any was pressed.
* @param {function} onDoubleClick - fires when the cell is double clicked; *will not fire while the cell is being edited*.
* @param className - appends to the element class list.
* @param style - appends to the element style.
* @param {boolean} highlighted - whether the cell is highlighted or not which indicates the cursor is on the cell.
* only **one** component should be highlighted at a time but multiple components can be selected.
* @param {boolean} editing - whether the cell is being edited or not and would render an input if so.
* only **one** component should be editing at a time but multiple components can be selected.
* @param {number} tabIndex - tab index in the corresponding spreadsheet.
*/
export default React.memo(Cell, (prevProps, nextProps) => {
return (
prevProps.style === nextProps.style &&
prevProps.className === nextProps.className &&
prevProps.onChange === nextProps.onChange &&
prevProps.tabIndex === nextProps.tabIndex &&
prevProps.onClick === nextProps.onClick &&
prevProps.onDoubleClick === nextProps.onDoubleClick &&
prevProps.highlighted === nextProps.highlighted &&
prevProps.selected === nextProps.selected &&
prevProps.editing === nextProps.editing &&
Expand Down
9 changes: 5 additions & 4 deletions src/components/ColumnHeads/ColumnHeads.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React from 'react';
import { createRangeArray, getAlphabetCharAtIndex } from '../../utils';
import { ModifierKey } from '../../utils/data.types';
import Cell from '../Cell/Cell';
import './ColumnHeads.scss';

interface IProps {
totalColumns: number;
selectedColumns?: number[];
onClick?: (column: number) => void;
onClick?: (column: number, modifier: ModifierKey) => void;
}

function ColumnHeads({ totalColumns, onClick, selectedColumns }: IProps) {
const handleClick = (column: number) => !!onClick && onClick(column);
const handleClick = (column: number, modifier: ModifierKey) =>
!!onClick && onClick(column, modifier);

// a column is added to account for the row indices column
return (
Expand All @@ -24,8 +26,7 @@ function ColumnHeads({ totalColumns, onClick, selectedColumns }: IProps) {
value: column === 0 ? '' : getAlphabetCharAtIndex(column - 1)
}}
className={'column-head--cell'}
tabIndex={column}
onClick={() => handleClick(column - 1)}
onClick={(modifier) => handleClick(column - 1, modifier)}
selected={selectedColumns && selectedColumns.includes(column - 1)}
/>
);
Expand Down
14 changes: 4 additions & 10 deletions src/components/RowIndexCell/RowIndexCell.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import React from 'react';
import { ModifierKey } from '../../utils/data.types';
import Cell from '../Cell/Cell';
import './RowIndexCell.scss';

interface IProps {
rowIndex: number;
tabIndex?: number;
onClick?: (row: number) => void;
onClick?: (row: number, modifier: ModifierKey) => void;
selected?: boolean;
}

export default function RowIndexCell({
rowIndex,
tabIndex,
onClick,
selected
}: IProps) {
export default function RowIndexCell({ rowIndex, onClick, selected }: IProps) {
return (
<Cell
role="row-index"
data={{ value: rowIndex + 1 }}
className={'row-index--cell'}
tabIndex={tabIndex}
onClick={() => !!onClick && onClick(rowIndex)}
onClick={(modifier) => !!onClick && onClick(rowIndex, modifier)}
selected={selected}
/>
);
Expand Down
83 changes: 70 additions & 13 deletions src/components/Spreadsheet/Spreadsheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { Data } from '../../utils/data.types';
import { getAlphabetCharAtIndex } from '../../utils';

const SAMPLE_DATA: Data = [
[{ value: 'John' }, { value: 'Doe' }],
[{ value: 'Mark' }, { value: 'Rober' }],
[{ value: 'Alan' }, { value: 'Turing' }]
[{ value: 'John' }, { value: 'Doe' }, { value: 34 }],
[{ value: 'Mark' }, { value: 'Rober' }, { value: 34 }],
[{ value: 'Alan' }, { value: 'Turing' }, { value: 34 }]
];

describe('Spreadsheet', () => {
Expand All @@ -35,53 +35,110 @@ describe('Spreadsheet', () => {
}
});

test('should select cell on click', () => {
test('should select cell on click', async () => {
const onSelectionChange = jest.fn();

const { container } = render(
const component = render(
<Spreadsheet data={SAMPLE_DATA} onSelectionChange={onSelectionChange} />
);

const firstDataCell = container.querySelector('[tabindex="4"] span');
const firstDataCell = await component.findByText('John');
const secondDataCell = await component.findByText('Rober');

userEvent.click(firstDataCell);

expect(onSelectionChange).toBeCalledTimes(2);
expect(onSelectionChange).toHaveBeenLastCalledWith([{ row: 0, column: 0 }]);

userEvent.click(secondDataCell);

expect(onSelectionChange).toBeCalledTimes(3);
expect(onSelectionChange).toHaveBeenLastCalledWith([{ row: 1, column: 1 }]);
});

test('should select row when index is clicked', () => {
test('should select row when index is clicked', async () => {
const onSelectionChange = jest.fn();

const { container } = render(
const component = render(
<Spreadsheet data={SAMPLE_DATA} onSelectionChange={onSelectionChange} />
);

const firstRowCell = container.querySelector('[tabindex="3"] span');
userEvent.click(firstRowCell);
const firstRowCell = await component.findByText('1');
const secondRowCell = await component.findByText('2');

userEvent.click(firstRowCell);
expect(onSelectionChange).toBeCalledTimes(2);

expect(onSelectionChange).toHaveBeenLastCalledWith([
{ row: 0, column: 0 },
{ row: 0, column: 1 },
{ row: 0, column: 2 }
]);
userEvent.click(secondRowCell);
expect(onSelectionChange).toBeCalledTimes(3);
expect(onSelectionChange).toHaveBeenLastCalledWith([
{ row: 1, column: 0 },
{ row: 1, column: 1 },
{ row: 1, column: 2 }
]);
});

test('should select column when title is clicked', () => {
test('should select column when title is clicked', async () => {
const onSelectionChange = jest.fn();

const { container } = render(
const component = render(
<Spreadsheet data={SAMPLE_DATA} onSelectionChange={onSelectionChange} />
);

const firstColumnCell = container.querySelector('[tabindex="1"] span');
const firstColumnCell = await component.findByText('A');
const secondColumnCell = await component.findByText('B');

userEvent.click(firstColumnCell);
expect(onSelectionChange).toBeCalledTimes(2);
expect(onSelectionChange).toHaveBeenLastCalledWith([
{ row: 0, column: 0 },
{ row: 1, column: 0 },
{ row: 2, column: 0 }
]);
userEvent.click(secondColumnCell);
expect(onSelectionChange).toBeCalledTimes(3);
expect(onSelectionChange).toHaveBeenLastCalledWith([
{ row: 0, column: 1 },
{ row: 1, column: 1 },
{ row: 2, column: 1 }
]);
});

test('should select the data between two columns when the second column is shift-clicked', async () => {
const onSelectionChange = jest.fn();

const component = render(
<Spreadsheet data={SAMPLE_DATA} onSelectionChange={onSelectionChange} />
);

const firstColumnCell = await component.findByText('A');
const thirdColumnCell = await component.findByText('C');

userEvent.click(firstColumnCell);
expect(onSelectionChange).toBeCalledTimes(2);
expect(onSelectionChange).toHaveBeenLastCalledWith([
{ row: 0, column: 0 },
{ row: 1, column: 0 },
{ row: 2, column: 0 }
]);

userEvent.click(thirdColumnCell, { shiftKey: true });
expect(onSelectionChange).toBeCalledTimes(3);
expect(onSelectionChange).toHaveBeenLastCalledWith([
{ row: 0, column: 0 },
{ row: 1, column: 0 },
{ row: 2, column: 0 },
{ row: 0, column: 1 },
{ row: 1, column: 1 },
{ row: 2, column: 1 },
{ row: 0, column: 2 },
{ row: 1, column: 2 },
{ row: 2, column: 2 }
]);
});
});
Loading

0 comments on commit c2e819e

Please sign in to comment.