Skip to content

Commit

Permalink
feat(component): add MultiSelect column type to worksheet (#1484)
Browse files Browse the repository at this point in the history
  • Loading branch information
Meztimuro committed Aug 20, 2024
1 parent be66e1e commit d2f010d
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-dryers-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bigcommerce/big-design': minor
---

Added MultiSelect column type to Worksheet component
12 changes: 12 additions & 0 deletions packages/big-design/src/components/Worksheet/Cell/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { typedMemo } from '../../../utils';
import { Tooltip } from '../../Tooltip';
import { Small } from '../../Typography';
import { CheckboxEditor, ModalEditor, SelectEditor, TextEditor, ToggleEditor } from '../editors';
import { MultiSelectEditor } from '../editors/MultiSelectEditor';
import { useAutoFilling, useEditableCell, useWorksheetStore } from '../hooks';
import {
InternalWorksheetColumn,
Expand Down Expand Up @@ -186,6 +187,17 @@ const InternalCell = <T extends WorksheetItem>({
/>
);

case 'multiSelect':
return (
<MultiSelectEditor
cell={cell}
isEditing={isEditing}
onBlur={handleBlur}
onChange={handleChange}
options={options}
/>
);

case 'checkbox':
return (
<CheckboxEditor
Expand Down
10 changes: 9 additions & 1 deletion packages/big-design/src/components/Worksheet/Row/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WorksheetItem,
WorksheetModalColumn,
WorksheetNumberColumn,
WorksheetSelectableColumn,
WorksheetTextColumn,
} from '../types';

Expand Down Expand Up @@ -54,6 +55,13 @@ const InternalRow = <T extends WorksheetItem>({ columns, rowIndex }: RowProps<T>

const isChild = useMemo(() => parentId !== undefined, [parentId]);

const hasOptions = useCallback(
(column: InternalWorksheetColumn<T>): column is WorksheetSelectableColumn<T> => {
return column.type === 'select' || column.type === 'multiSelect';
},
[],
);

const hasFormatting = useCallback(
(
column: InternalWorksheetColumn<T>,
Expand Down Expand Up @@ -107,7 +115,7 @@ const InternalRow = <T extends WorksheetItem>({ columns, rowIndex }: RowProps<T>
key={`${rowIndex}-${columnIndex}`}
nextRowValue={(nextRow && nextRow[column.hash]) || ''}
notation={column.notation}
options={column.type === 'select' ? column.config.options : undefined}
options={hasOptions(column) ? column.config.options : undefined}
rowId={row.id}
rowIndex={rowIndex}
type={column.type ?? 'text'}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { createRef, useCallback, useEffect } from 'react';

import { typedMemo } from '../../../../utils';
import { MultiSelect } from '../../../MultiSelect';
import { useWorksheetStore } from '../../hooks';
import { Cell, WorksheetItem, WorksheetSelectableColumn } from '../../types';

import { MultiSelectWrapper } from './styled';

export interface MultiSelectEditorProps<Item> {
cell: Cell<Item>;
isEditing: boolean;
options?: WorksheetSelectableColumn<Item>['config']['options'];
onBlur(): void;
onChange(value: unknown): void;
}

const InternalMultiSelectEditor = <T extends WorksheetItem>({
cell,
isEditing,
onBlur,
onChange,
options = [],
}: MultiSelectEditorProps<T>) => {
const inputRef = createRef<HTMLInputElement>();
const { store, useStore } = useWorksheetStore();

const setEditingCell = useStore(store, (state) => state.setEditingCell);

const value = Array.isArray(cell.value) ? cell.value : [cell.value];

useEffect(() => {
if (isEditing && inputRef.current) {
inputRef.current.focus();
}
}, [inputRef, isEditing]);

const handleChange = useCallback(
(value: unknown[]) => {
onChange(value);
},
[onChange],
);

const handleOpen = useCallback(() => {
setEditingCell({ cell });
}, [cell, setEditingCell]);

const handleClose = useCallback(() => {
onBlur();
setEditingCell({ cell: null });
}, [onBlur, setEditingCell]);

return (
<MultiSelectWrapper>
<MultiSelect
disabled={cell.disabled}
filterable={false}
inputRef={inputRef}
onClose={handleClose}
onOpen={handleOpen}
onOptionsChange={handleChange}
options={options}
value={value}
/>
</MultiSelectWrapper>
);
};

export const MultiSelectEditor = typedMemo(InternalMultiSelectEditor);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MultiSelectEditor';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { theme as defaultTheme } from '@bigcommerce/big-design-theme';
import styled from 'styled-components';

export const MultiSelectWrapper = styled.div`
span {
background-color: ${({ theme }) => theme.colors.inherit};
border: none;
box-shadow: none;
&:hover:not([disabled]) {
border: none;
}
&[disabled] {
background-color: ${({ theme }) => theme.colors.white};
}
}
input {
font-size: ${({ theme }) => theme.typography.fontSize.small};
font-weight: ${({ theme }) => theme.typography.fontWeight.regular};
&[disabled] {
background-color: ${({ theme }) => theme.colors.inherit};
color: ${({ theme }) => theme.colors.secondary50};
cursor: default;
}
}
[role='option'] {
font-size: ${({ theme }) => theme.typography.fontSize.small};
}
`;

MultiSelectWrapper.defaultProps = { theme: defaultTheme };
115 changes: 115 additions & 0 deletions packages/big-design/src/components/Worksheet/spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Product {
otherField: string;
otherField2: number;
numberField: number;
multiField: number[];
}

const TreeComponent = (
Expand Down Expand Up @@ -118,6 +119,28 @@ const selectableColumns: Array<WorksheetColumn<Partial<Product>>> = [
},
];

const multiSelectColumns: Array<WorksheetColumn<Partial<Product>>> = [
{
hash: 'productName',
header: 'Product name',
validation: (value: string) => !!value,
disabled: true,
},
{
hash: 'multiField',
header: 'Multi field',
type: 'multiSelect',
config: {
options: [
{ value: 1, content: 'Multi 1' },
{ value: 2, content: 'Multi 2' },
{ value: 3, content: 'Multi 3' },
],
},
validation: (value: string) => !!value,
},
];

const disabledColumns: Array<WorksheetColumn<Product>> = [
{
hash: 'productName',
Expand Down Expand Up @@ -231,6 +254,19 @@ const selectableItems: Array<Partial<Product>> = [
},
];

const multiSelectItems: Array<Partial<Product>> = [
{
id: 1,
productName: 'Shoes Name One',
multiField: [1],
},
{
id: 2,
productName: 'Shoes Name Two',
multiField: [2],
},
];

let handleChange = jest.fn();
let handleErrors = jest.fn();

Expand Down Expand Up @@ -1067,6 +1103,85 @@ describe('SelectEditor', () => {
});
});

describe('MultiSelectEditor', () => {
test('renders MultiSelectEditor', async () => {
const { findAllByRole } = render(
<Worksheet columns={multiSelectColumns} items={multiSelectItems} onChange={handleChange} />,
);

const cells = await findAllByRole('combobox');

expect(cells).toHaveLength(2);
});

test('MultiSelectEditor is editable', async () => {
const { findAllByRole, findByLabelText } = render(
<Worksheet columns={multiSelectColumns} items={multiSelectItems} onChange={handleChange} />,
);

const cells = await findAllByRole('combobox');

await userEvent.click(cells[0]);

const options = await findAllByRole('option');

await userEvent.click(options[2]);

const deleteButtons = await findByLabelText('Remove Multi 2');

await userEvent.click(deleteButtons);

expect(handleChange).toHaveBeenCalledTimes(2);
expect(handleChange).toHaveBeenLastCalledWith([
{
id: 1,
productName: 'Shoes Name One',
multiField: [1, 3],
},
{
id: 2,
productName: 'Shoes Name Two',
multiField: [],
},
]);
});

test('renders in disabled state', async () => {
const { findAllByRole } = render(
<Worksheet
columns={[
{
hash: 'productName',
header: 'Product name',
validation: (value: string) => !!value,
disabled: true,
},
{
hash: 'multiField',
header: 'Multi field',
type: 'multiSelect',
config: {
options: [
{ value: 1, content: 'Multi 1' },
{ value: 2, content: 'Multi 2' },
{ value: 3, content: 'Multi 3' },
],
},
validation: (value: string) => !!value,
disabled: true,
},
]}
items={selectableItems}
onChange={handleChange}
/>,
);

const cells = await findAllByRole('combobox');

expect(cells[0]).toHaveAttribute('disabled');
});
});

describe('CheckboxEditor', () => {
test('renders CheckboxEditor', () => {
const { getAllByLabelText } = render(
Expand Down
2 changes: 1 addition & 1 deletion packages/big-design/src/components/Worksheet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export interface WorksheetSelectableColumn<Item> extends WorksheetBaseColumn<Ite
config: {
options: Array<SelectOption<unknown>>;
};
type: 'select';
type: 'select' | 'multiSelect';
}

export interface WorksheetModalColumn<Item> extends WorksheetBaseColumn<Item> {
Expand Down

0 comments on commit d2f010d

Please sign in to comment.