Skip to content

Commit

Permalink
feat: add CellNumberfield to StudioInputTable (#14345)
Browse files Browse the repository at this point in the history
Co-authored-by: andreastanderen <71079896+standeren@users.noreply.github.com>
  • Loading branch information
ErlingHauan and standeren authored Jan 8, 2025
1 parent 7b805ee commit 2e6da20
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type StudioDecimalInputProps = Override<
description?: string;
onChange: (value: number) => void;
value?: number;
validationErrorMessage: string;
validationErrorMessage?: string;
},
StudioTextfieldProps
>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.textfieldCell,
.textareaCell,
.textResourceCell {
.textResourceCell,
.numberCell {
padding-block: var(--fds-spacing-1);
padding-inline-end: var(--fds-spacing-2);
padding-inline-start: 0;
Expand All @@ -17,6 +18,7 @@

:is(
.textfieldCell:not(:hover) input,
.numberfieldCell:not(:hover) input,
.textareaCell:not(:hover) textarea,
.textResourceCell:not(:hover) input
):not(:hover):not(:active):not(:focus),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { StudioTable } from '../../StudioTable';
import type { FocusEvent, ForwardedRef, ReactElement } from 'react';
import React, { useCallback } from 'react';
import classes from './Cell.module.css';
import { BaseInputCell } from './BaseInputCell';
import cn from 'classnames';
import { isCaretAtEnd, isCaretAtStart, isSomethingSelected } from '../dom-utils/caretUtils';
import type { StudioDecimalInputProps } from '../../StudioDecimalInput';
import { StudioDecimalInput } from '../../StudioDecimalInput';
import { useEventProps } from './useEventProps';

export type CellNumberfieldProps = StudioDecimalInputProps;

export class CellNumberfield extends BaseInputCell<HTMLInputElement, CellNumberfieldProps> {
render(
{ className: givenClass, onFocus, ...rest }: CellNumberfieldProps,
ref: ForwardedRef<HTMLInputElement>,
): ReactElement {
/* eslint-disable react-hooks/rules-of-hooks */
/* Eslint misinterprets this as a class component, while it's really just a functional component within a class */

const handleFocus = useCallback(
(event: FocusEvent<HTMLInputElement>): void => {
onFocus?.(event);
event.currentTarget.select();
},
[onFocus],
);

const eventProps = useEventProps<FocusEvent, FocusEvent, number>({
onFocus: handleFocus,
...rest,
});

const className = cn(classes.numberfieldCell, givenClass);

return (
<StudioTable.Cell className={className}>
<StudioDecimalInput hideLabel ref={ref} size='small' {...rest} {...eventProps} />
</StudioTable.Cell>
);
}

shouldMoveFocusOnArrowKey({ key, currentTarget }) {
if (isSomethingSelected(currentTarget)) return false;
switch (key) {
case 'ArrowUp':
return isCaretAtStart(currentTarget);
case 'ArrowDown':
return isCaretAtEnd(currentTarget);
case 'ArrowLeft':
return isCaretAtStart(currentTarget);
case 'ArrowRight':
return isCaretAtEnd(currentTarget);
}
}

shouldMoveFocusOnEnterKey = () => true;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { CellTextfieldProps } from './CellTextfield';
import { CellTextfield } from './CellTextfield';
import type { CellNumberfieldProps } from './CellNumberfield';
import { CellNumberfield } from './CellNumberfield';
import type { CellTextareaProps } from './CellTextarea';
import { CellTextarea } from './CellTextarea';
import type { CellButtonProps } from './CellButton';
Expand All @@ -13,6 +15,7 @@ import { CellTextResource } from './CellTextResource';

type CellComponent = typeof Cell & {
Textfield: InputCellComponent<CellTextfieldProps, HTMLInputElement>;
Numberfield: InputCellComponent<CellNumberfieldProps, HTMLInputElement>;
Textarea: InputCellComponent<CellTextareaProps, HTMLTextAreaElement>;
Button: InputCellComponent<CellButtonProps, HTMLButtonElement>;
Checkbox: InputCellComponent<CellCheckboxProps, HTMLInputElement>;
Expand All @@ -22,6 +25,9 @@ type CellComponent = typeof Cell & {
export const StudioInputTableCell = Cell as CellComponent;

StudioInputTableCell.Textfield = new CellTextfield('StudioInputTable.Cell.Textfield').component();
StudioInputTableCell.Numberfield = new CellNumberfield(
'StudioInputTable.Cell.Numberfield',
).component();
StudioInputTableCell.Textarea = new CellTextarea('StudioInputTable.Cell.Textarea').component();
StudioInputTableCell.Button = new CellButton('StudioInputTable.Cell.Button').component();
StudioInputTableCell.Checkbox = new CellCheckbox('StudioInputTable.Cell.Checkbox').component();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,33 @@ import {
textResourceProps,
textResourceSearchLabel,
textResourceValueLabel,
numberfieldLabel,
} from './test-data/testTableData';
import type { UserEvent } from '@testing-library/user-event';
import userEvent from '@testing-library/user-event';
import type { CellTextfieldProps } from './Cell/CellTextfield';
import type { CellTextareaProps } from './Cell/CellTextarea';
import type { CellCheckboxProps } from './Cell/CellCheckbox';
import type { CellButtonProps } from './Cell/CellButton';
import type { CellNumberfieldProps } from './Cell/CellNumberfield';
import type { HTMLCellInputElement } from './types/HTMLCellInputElement';
import type { EventName } from './types/EventName';
import type { FormEventProps } from './types/FormEventProps';
import type { EventPropName } from './types/EventPropName';
import { StringUtils } from '@studio/pure-functions';
import type { CellTextResourceInputProps } from './Cell/CellTextResource';

type ElementName = 'checkbox' | 'textfield' | 'textarea' | 'button' | 'textResource';
type ElementName =
| 'checkbox'
| 'textfield'
| 'numberfield'
| 'textarea'
| 'button'
| 'textResource';
type NativeElement<Name extends ElementName> = {
checkbox: HTMLInputElement;
textfield: HTMLInputElement;
numberfield: HTMLInputElement;
textarea: HTMLTextAreaElement;
button: HTMLButtonElement;
textResource: HTMLInputElement;
Expand Down Expand Up @@ -112,6 +121,8 @@ describe('StudioInputTable', () => {
expect(getTextfieldInRow(1)).toHaveFocus();
await user.keyboard('{Enter}'); // Move down to textfield 2
expect(getTextfieldInRow(2)).toHaveFocus();
await user.keyboard('{ArrowRight}'); // Move right to numberfield 2
expect(getNumberfieldInRow(2)).toHaveFocus();
await user.keyboard('{ArrowRight}'); // Move right to textarea 2
expect(getTextareaInRow(2)).toHaveFocus();
await user.keyboard('{ArrowRight}'); // Move right to text resource 2
Expand All @@ -129,6 +140,7 @@ describe('StudioInputTable', () => {
type TextboxTestCase = () => HTMLInputElement | HTMLTextAreaElement;
const textboxTestCases: { [key: string]: TextboxTestCase } = {
textfield: () => getTextfieldInRow(2),
numberfield: () => getNumberfieldInRow(2),
textarea: () => getTextareaInRow(2),
textResource: () => getTextResourceValueInRow(2),
};
Expand Down Expand Up @@ -276,6 +288,11 @@ describe('StudioInputTable', () => {
render: (ref) => renderSingleTextResourceCell(textResourceProps(0), ref),
getElement: () => getTextbox(textResourceValueLabel(0)) as HTMLInputElement,
},
numberfield: {
render: (ref) =>
renderSingleNumberfieldCell({ label: testLabel, onChange: jest.fn() }, ref),
getElement: () => getTextbox(testLabel) as HTMLInputElement,
},
};

test.each(Object.keys(testCases))('%s', (key) => {
Expand Down Expand Up @@ -312,6 +329,37 @@ describe('StudioInputTable', () => {
},
},
},
numberfield: {
change: {
render: (onChange) =>
renderSingleNumberfieldCell({
label: 'test',
onChange: (value: number) => onChange({ target: { value } } as any),
}),
action: (user) => user.type(screen.getByRole('textbox'), '1'),
},
focus: {
render: (onFocus) =>
renderSingleNumberfieldCell({
label: 'test',
onChange: jest.fn(),
onFocus,
}),
action: (user) => user.click(screen.getByRole('textbox')),
},
blur: {
render: (onBlur) =>
renderSingleNumberfieldCell({
label: 'test',
onChange: jest.fn(),
onBlur,
}),
action: async (user) => {
await user.click(screen.getByRole('textbox'));
await user.tab();
},
},
},
textarea: {
change: {
render: (onChange) => renderSingleTextareaCell({ label: 'test', onChange }),
Expand Down Expand Up @@ -432,6 +480,16 @@ const renderSingleTextfieldCell = (
</SingleRow>,
);

const renderSingleNumberfieldCell = (
props: CellNumberfieldProps,
ref?: ForwardedRef<HTMLInputElement>,
): RenderResult =>
render(
<SingleRow>
<StudioInputTable.Cell.Numberfield {...props} ref={ref} />
</SingleRow>,
);

const renderSingleTextareaCell = (
props: CellTextareaProps,
ref?: ForwardedRef<HTMLTextAreaElement>,
Expand Down Expand Up @@ -480,6 +538,8 @@ const getCheckboxInRow = (rowNumber: number): HTMLInputElement =>
const getTextbox = (name: string) => screen.getByRole('textbox', { name });
const getTextfieldInRow = (rowNumber: number): HTMLInputElement =>
getTextbox(textfieldLabel(rowNumber)) as HTMLInputElement;
const getNumberfieldInRow = (rowNumber: number): HTMLInputElement =>
getTextbox(numberfieldLabel(rowNumber)) as HTMLInputElement;
const getTextareaInRow = (rowNumber: number): HTMLTextAreaElement =>
getTextbox(textareaLabel(rowNumber)) as HTMLTextAreaElement;
const getButton = (name: string): HTMLButtonElement =>
Expand Down Expand Up @@ -515,7 +575,7 @@ const placeCaretAtPosition = (
position: number,
): void => element.setSelectionRange(position, position);

const expectedNumberOfColumns = 6;
const expectedNumberOfColumns = 7;
const expectedNumberOfHeaderRows = 1;
const expectedNumberOfBodyRows = 3;
const expectedNumberOfRows = expectedNumberOfBodyRows + expectedNumberOfHeaderRows;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import type { StudioInputTableProps } from '../StudioInputTable';
import {
buttonHeader,
headerCheckboxLabel,
textareaHeader,
textfieldHeader,
numberfieldHeader,
textareaHeader,
textHeader,
textResourceHeader,
textResourceTexts,
Expand All @@ -22,6 +23,7 @@ export function TestTable(props: StudioInputTableProps): ReactElement {
<StudioInputTable.HeaderCell.Checkbox aria-label={headerCheckboxLabel} />
<StudioInputTable.HeaderCell>{textHeader}</StudioInputTable.HeaderCell>
<StudioInputTable.HeaderCell>{textfieldHeader}</StudioInputTable.HeaderCell>
<StudioInputTable.HeaderCell>{numberfieldHeader}</StudioInputTable.HeaderCell>
<StudioInputTable.HeaderCell>{textareaHeader}</StudioInputTable.HeaderCell>
<StudioInputTable.HeaderCell>{textResourceHeader}</StudioInputTable.HeaderCell>
<StudioInputTable.HeaderCell>{buttonHeader}</StudioInputTable.HeaderCell>
Expand Down Expand Up @@ -53,6 +55,11 @@ function TestRow({ rowNumber: rn }: TestRowProps): ReactElement {
name={testData.textfieldName(rn)}
label={testData.textfieldLabel(rn)}
/>
<StudioInputTable.Cell.Numberfield
name={testData.numberfieldName(rn)}
label={testData.numberfieldLabel(rn)}
onChange={() => {}}
/>
<StudioInputTable.Cell.Textarea
name={testData.textareaName(rn)}
label={testData.textareaLabel(rn)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { textResourcesMock } from '../../../test-data/textResourcesMock';
export const headerCheckboxLabel = 'Select all';
export const textHeader = 'Text';
export const textfieldHeader = 'Textfield';
export const numberfieldHeader = 'Numberfield';
export const textareaHeader = 'Textarea';
export const buttonHeader = 'Button';
export const textResourceHeader = 'Text Resource';
Expand All @@ -14,6 +15,8 @@ export const checkboxLabel = (rowNumber: number) => `Checkbox ${rowNumber}`;
export const cleanText = (rowNumber: number) => `Text ${rowNumber}`;
export const textfieldName = (rowNumber: number) => `textfield${rowNumber}`;
export const textfieldLabel = (rowNumber: number) => `Textfield ${rowNumber}`;
export const numberfieldName = (rowNumber: number) => `numberfield${rowNumber}`;
export const numberfieldLabel = (rowNumber: number) => `Numberfield ${rowNumber}`;
export const textareaName = (rowNumber: number) => `textarea${rowNumber}`;
export const textareaLabel = (rowNumber: number) => `Textarea ${rowNumber}`;
export const buttonLabel = (rowNumber: number) => `Button ${rowNumber}`;
Expand Down

0 comments on commit 2e6da20

Please sign in to comment.