Skip to content

Commit aca3f70

Browse files
authored
IBX-10469: Stateful Inputs HOC (#13)
1 parent 270cc10 commit aca3f70

File tree

5 files changed

+53
-35
lines changed

5 files changed

+53
-35
lines changed

packages/components/src/inputs/InputText/InputText.stories.tsx

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import React, { useState } from 'react';
2-
31
import type { Meta, StoryObj } from '@storybook/react';
42
import { action } from 'storybook/actions';
53

64
import { INPUT_SIZE_VALUES, INPUT_TYPE_VALUES } from './InputText.types';
7-
import InputText from './InputText';
5+
import { InputTextStateful } from './InputText';
86

9-
const meta: Meta<typeof InputText> = {
10-
component: InputText,
7+
const meta: Meta<typeof InputTextStateful> = {
8+
component: InputTextStateful,
119
parameters: {
1210
layout: 'centered',
1311
},
@@ -37,33 +35,11 @@ const meta: Meta<typeof InputText> = {
3735
onFocus: action('on-focus'),
3836
onInput: action('on-input'),
3937
},
40-
decorators: [
41-
(Story, { args }) => {
42-
const [value, setValue] = useState(args.value ?? '');
43-
const onChange = (changedValue: string, event?: React.ChangeEvent<HTMLInputElement>) => {
44-
setValue(changedValue);
45-
46-
if (args.onChange) {
47-
args.onChange(changedValue, event);
48-
}
49-
};
50-
51-
return (
52-
<Story
53-
args={{
54-
...args,
55-
onChange,
56-
value,
57-
}}
58-
/>
59-
);
60-
},
61-
],
6238
};
6339

6440
export default meta;
6541

66-
type Story = StoryObj<typeof InputText>;
42+
type Story = StoryObj<typeof InputTextStateful>;
6743

6844
export const Empty: Story = {
6945
name: 'Empty',

packages/components/src/inputs/InputText/InputText.test.stories.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Meta, StoryObj } from '@storybook/react';
22
import { expect, fn, userEvent, within } from 'storybook/test';
33

4-
import InputText from './InputText';
4+
import { InputTextStateful } from './InputText';
55

6-
const meta: Meta<typeof InputText> = {
7-
component: InputText,
6+
const meta: Meta<typeof InputTextStateful> = {
7+
component: InputTextStateful,
88
parameters: {
99
layout: 'centered',
1010
},
@@ -20,7 +20,7 @@ const meta: Meta<typeof InputText> = {
2020

2121
export default meta;
2222

23-
type Story = StoryObj<typeof InputText>;
23+
type Story = StoryObj<typeof InputTextStateful>;
2424

2525
export const Default: Story = {
2626
name: 'Default',
@@ -59,5 +59,14 @@ export const Default: Story = {
5959
await expect(args.onChange).toHaveBeenCalledTimes(insertTextLength);
6060
await expect(args.onInput).toHaveBeenCalledTimes(insertTextLength);
6161
});
62+
63+
const clearBtn = canvas.getByRole('button');
64+
65+
await step('InputText handles clear event', async () => {
66+
await userEvent.click(clearBtn);
67+
68+
await expect(args.onChange).toHaveBeenLastCalledWith('');
69+
await expect(input).toHaveValue('');
70+
});
6271
},
6372
};

packages/components/src/inputs/InputText/InputText.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
33
import BaseInput from '@ids-internal/partials/BaseInput';
44
import ClearBtn from '../../ui/ClearBtn';
55
import { createCssClassNames } from '@ids-internal/shared/css.class.names';
6+
import withStateValue from '@ids-internal/hoc/withStateValue';
67

78
import { ComponentEntryDataType } from '@ids-types/general';
89
import { InputTextProps } from './InputText.types';
910

10-
const Input = ({
11+
const InputText = ({
1112
name,
1213
onBlur = () => undefined,
1314
onChange = () => undefined,
@@ -116,4 +117,6 @@ const Input = ({
116117
);
117118
};
118119

119-
export default Input;
120+
export default InputText;
121+
122+
export const InputTextStateful = withStateValue<string | number>(InputText);
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import InputText from './InputText';
1+
import InputText, { InputTextStateful } from './InputText';
22
import { InputTextProps } from './InputText.types';
33

44
export default InputText;
5+
export { InputTextStateful };
56
export type { InputTextProps };
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { FC, useState } from 'react';
2+
3+
type OnChangeFn<T> = (value: T, ...args: any[]) => any; // eslint-disable-line @typescript-eslint/no-explicit-any
4+
interface WrappedComponentProps<T> {
5+
onChange?: OnChangeFn<T>;
6+
value: T;
7+
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
8+
}
9+
10+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11+
export default <T,>(WrappedComponent: FC<any>) => {
12+
const WrapperComponent = ({ value, onChange, ...restProps }: WrappedComponentProps<T>) => {
13+
const [componentValue, setComponentValue] = useState(value);
14+
15+
const handleChange = (...args: Parameters<OnChangeFn<T>>): ReturnType<OnChangeFn<T>> => {
16+
setComponentValue(args[0]);
17+
18+
if (onChange) {
19+
onChange(...args);
20+
}
21+
};
22+
23+
return <WrappedComponent {...restProps} onChange={handleChange} value={componentValue} />;
24+
};
25+
26+
WrapperComponent.displayName = `withStateValue(${WrappedComponent.displayName ?? WrappedComponent.name})`;
27+
28+
return WrapperComponent;
29+
};

0 commit comments

Comments
 (0)