Skip to content

Commit

Permalink
feat(ui-component): Add event listeners on table level (#13923)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasEng authored Nov 12, 2024
1 parent 1fc22c5 commit bf7450c
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,9 @@ describe('StudioCodeListEditor', () => {
const user = userEvent.setup();
renderCodeListEditor();
const labelInput = screen.getByRole('textbox', { name: texts.itemLabel(1) });
const additionalText = 'new text';
const newValue = codeList[0].label + additionalText;
await user.type(labelInput, additionalText);
expect(onChange).toHaveBeenCalledTimes(additionalText.length);
const newValue = 'new text';
await user.type(labelInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], label: newValue },
codeList[1],
Expand All @@ -130,10 +129,9 @@ describe('StudioCodeListEditor', () => {
const user = userEvent.setup();
renderCodeListEditor();
const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) });
const additionalText = 'new text';
const newValue = codeList[0].value + additionalText;
await user.type(valueInput, additionalText);
expect(onChange).toHaveBeenCalledTimes(additionalText.length);
const newValue = 'new text';
await user.type(valueInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], value: newValue },
codeList[1],
Expand All @@ -145,10 +143,9 @@ describe('StudioCodeListEditor', () => {
const user = userEvent.setup();
renderCodeListEditor();
const descriptionInput = screen.getByRole('textbox', { name: texts.itemDescription(1) });
const additionalText = 'new text';
const newValue = codeList[0].description + additionalText;
await user.type(descriptionInput, additionalText);
expect(onChange).toHaveBeenCalledTimes(additionalText.length);
const newValue = 'new text';
await user.type(descriptionInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], description: newValue },
codeList[1],
Expand All @@ -160,10 +157,9 @@ describe('StudioCodeListEditor', () => {
const user = userEvent.setup();
renderCodeListEditor();
const helpTextInput = screen.getByRole('textbox', { name: texts.itemHelpText(1) });
const additionalText = 'new text';
const newValue = codeList[0].helpText + additionalText;
await user.type(helpTextInput, additionalText);
expect(onChange).toHaveBeenCalledTimes(additionalText.length);
const newValue = 'new text';
await user.type(helpTextInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], helpText: newValue },
codeList[1],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { StudioButtonProps } from '../../StudioButton';
import { StudioButton } from '../../StudioButton';
import { BaseInputCell } from './BaseInputCell';
import cn from 'classnames';
import { useEventProps } from './useEventProps';

export type CellButtonProps = StudioButtonProps;

Expand All @@ -14,10 +15,16 @@ export class CellButton extends BaseInputCell<HTMLButtonElement, CellButtonProps
{ className: givenClass, ...rest }: StudioButtonProps,
ref: ForwardedRef<HTMLButtonElement>,
): 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 eventProps = useEventProps<HTMLButtonElement>(rest);

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

return (
<StudioTable.Cell className={className}>
<StudioButton ref={ref} variant='secondary' {...rest} />
<StudioButton ref={ref} variant='secondary' {...rest} {...eventProps} />
</StudioTable.Cell>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import type { StudioCheckboxProps } from '../../StudioCheckbox';
import { StudioCheckbox } from '../../StudioCheckbox';
import { BaseInputCell } from './BaseInputCell';
import { useEventProps } from './useEventProps';

export type CellCheckboxProps = StudioCheckboxProps;

Expand All @@ -12,9 +13,14 @@ export class CellCheckbox extends BaseInputCell<HTMLInputElement, CellCheckboxPr
{ className, ...rest }: CellCheckboxProps,
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 eventProps = useEventProps<HTMLInputElement>(rest);

return (
<StudioTable.Cell className={className}>
<StudioCheckbox ref={ref} {...rest} />
<StudioCheckbox ref={ref} {...rest} {...eventProps} />
</StudioTable.Cell>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
import { StudioTable } from '../../StudioTable';
import type { ForwardedRef, ReactElement } from 'react';
import React from 'react';
import type { FocusEvent, ForwardedRef, ReactElement } from 'react';
import React, { useCallback } from 'react';
import classes from './Cell.module.css';
import type { StudioTextareaProps } from '../../StudioTextarea';
import { StudioTextarea } from '../../StudioTextarea';
import { BaseInputCell } from './BaseInputCell';
import cn from 'classnames';
import { isCaretAtEnd, isCaretAtStart, isSomethingSelected } from '../dom-utils/caretUtils';
import { useEventProps } from './useEventProps';

export type CellTextareaProps = StudioTextareaProps;

export class CellTextarea extends BaseInputCell<HTMLTextAreaElement, CellTextareaProps> {
render(
{ className: givenClass, ...rest }: CellTextareaProps,
{ className: givenClass, onFocus, ...rest }: CellTextareaProps,
ref: ForwardedRef<HTMLTextAreaElement>,
): 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<HTMLTextAreaElement>): void => {
onFocus?.(event);
event.currentTarget.select();
},
[onFocus],
);

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

const className = cn(classes.textareaCell, givenClass);
return (
<StudioTable.Cell className={className}>
<StudioTextarea
hideLabel
onFocus={(event) => event.currentTarget.select()}
ref={ref}
size='small'
{...rest}
/>
<StudioTextarea hideLabel ref={ref} size='small' {...rest} {...eventProps} />
</StudioTable.Cell>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
import { StudioTable } from '../../StudioTable';
import type { ForwardedRef, ReactElement } from 'react';
import React from 'react';
import type { FocusEvent, ForwardedRef, ReactElement } from 'react';
import React, { useCallback } from 'react';

import type { StudioTextfieldProps } from '../../StudioTextfield';
import { StudioTextfield } from '../../StudioTextfield';
import classes from './Cell.module.css';
import { BaseInputCell } from './BaseInputCell';
import cn from 'classnames';
import { isCaretAtEnd, isCaretAtStart, isSomethingSelected } from '../dom-utils/caretUtils';
import { useEventProps } from './useEventProps';

export type CellTextfieldProps = StudioTextfieldProps;

export class CellTextfield extends BaseInputCell<HTMLInputElement, CellTextfieldProps> {
render(
{ className: givenClass, ...rest }: CellTextfieldProps,
{ className: givenClass, onFocus, ...rest }: CellTextfieldProps,
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<HTMLInputElement>({ onFocus: handleFocus, ...rest });

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

return (
<StudioTable.Cell className={className}>
<StudioTextfield
hideLabel
onFocus={(event) => event.currentTarget.select()}
ref={ref}
size='small'
{...rest}
/>
<StudioTextfield hideLabel ref={ref} size='small' {...rest} {...eventProps} />
</StudioTable.Cell>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { HTMLAttributes } from 'react';
import { useMemo } from 'react';
import { useStudioInputTableContext } from '../StudioInputTableContext';
import type { HTMLCellInputElement } from '../types/HTMLCellInputElement';
import type { EventProps } from '../types/EventProps';

export function useEventProps<Element extends HTMLCellInputElement>({
onBlur,
onFocus,
onChange,
}: Partial<HTMLAttributes<Element>>): EventProps<Element> {
const { onChangeAny, onBlurAny, onFocusAny } = useStudioInputTableContext<Element>();

return useMemo<EventProps<Element>>(
() => ({
onChange: (event) => {
onChange?.(event);
onChangeAny?.(event);
},
onFocus: (event) => {
onFocus?.(event);
onFocusAny?.(event);
},
onBlur: (event) => {
onBlur?.(event);
onBlurAny?.(event);
},
}),
[onChange, onFocus, onBlur, onChangeAny, onBlurAny, onFocusAny],
);
}
Loading

0 comments on commit bf7450c

Please sign in to comment.