react-restyle-components 0.1.58 → 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/package.json +4 -5
- package/package.json +5 -6
- package/src/App.css +38 -0
- package/src/App.test.tsx +9 -0
- package/src/App.tsx +26 -0
- package/src/core-components/atoms/buttons/button.stories.tsx +42 -0
- package/src/core-components/atoms/buttons/buttons.component.tsx +33 -0
- package/src/core-components/atoms/buttons/buttons.test.tsx +13 -0
- package/src/core-components/atoms/check-box/checkBox.component.tsx +54 -0
- package/src/core-components/atoms/check-box/checkBox.stories.tsx +29 -0
- package/src/core-components/atoms/check-box/checkBox.test.tsx +11 -0
- package/src/core-components/atoms/date-picker/date-picker.component.tsx +60 -0
- package/src/core-components/atoms/date-picker/date-picker.stories.tsx +23 -0
- package/src/core-components/atoms/date-picker/date-picker.test.tsx +17 -0
- package/src/core-components/atoms/form/form.component.tsx +604 -0
- package/src/core-components/atoms/form/form.test.tsx +120 -0
- package/src/core-components/atoms/icons/icons.component.tsx +65 -0
- package/src/core-components/atoms/icons/icons.stories.tsx +24 -0
- package/src/core-components/atoms/icons/icons.test.tsx +15 -0
- package/src/core-components/atoms/input/input-otp.component.tsx +107 -0
- package/src/core-components/atoms/input/input-otp.styles.css +35 -0
- package/src/core-components/atoms/input/input-pin.component.tsx +144 -0
- package/src/core-components/atoms/input/input-pin.stories.tsx +25 -0
- package/src/core-components/atoms/input/input-pin.test.tsx +30 -0
- package/src/core-components/atoms/input/input.component.tsx +74 -0
- package/src/core-components/atoms/input/input.stories.tsx +26 -0
- package/src/core-components/atoms/input/input.styles.css +35 -0
- package/src/core-components/atoms/input/input.test.tsx +32 -0
- package/src/core-components/atoms/input-dropdown/input-dropdown.component.tsx +91 -0
- package/src/core-components/atoms/input-dropdown/input-dropdown.stories.tsx +22 -0
- package/src/core-components/atoms/input-dropdown/input-dropdown.test.tsx +16 -0
- package/src/core-components/atoms/loader/loader.component.tsx +78 -0
- package/src/core-components/atoms/loader/loader.stories.tsx +18 -0
- package/src/core-components/atoms/loader/loader.test.tsx +9 -0
- package/src/core-components/atoms/radio/radio.component.tsx +48 -0
- package/src/core-components/atoms/radio/radio.stories.tsx +27 -0
- package/src/core-components/atoms/radio/radio.test.tsx +11 -0
- package/src/core-components/atoms/stepper/stepper.component.tsx +72 -0
- package/src/core-components/atoms/stepper/stepper.stories.tsx +23 -0
- package/src/core-components/atoms/stepper/stepper.test.tsx +14 -0
- package/src/core-components/atoms/tabs/tabs.component.tsx +41 -0
- package/src/core-components/atoms/tabs/tabs.stories.tsx +27 -0
- package/src/core-components/atoms/tabs/tabs.test.tsx +19 -0
- package/src/core-components/atoms/timer/timer.component.tsx +89 -0
- package/src/core-components/atoms/timer/timer.test.tsx +16 -0
- package/src/core-components/atoms/tooltip/tooltip.component.test.tsx +10 -0
- package/src/core-components/atoms/tooltip/tooltip.component.tsx +54 -0
- package/src/core-components/atoms/tooltip/tooltip.stories.tsx +18 -0
- package/src/core-components/index.ts +21 -0
- package/src/core-components/molecules/auto-complete-filter-multiple-select-multiple-fields-display/auto-complete-filter-multiple-select-multiple-fields-display.component.test.tsx +18 -0
- package/src/core-components/molecules/auto-complete-filter-multiple-select-multiple-fields-display/auto-complete-filter-multiple-select-multiple-fields-display.component.tsx +182 -0
- package/src/core-components/molecules/auto-complete-filter-multiple-select-multiple-fields-display/auto-complete-filter-multiple-select-multiple-fields-display.stories.tsx +55 -0
- package/src/core-components/molecules/auto-complete-filter-single-select-multiple-fields-display/auto-complete-filter-single-select-multiple-fields-display.component.test.tsx +23 -0
- package/src/core-components/molecules/auto-complete-filter-single-select-multiple-fields-display/auto-complete-filter-single-select-multiple-fields-display.component.tsx +190 -0
- package/src/core-components/molecules/auto-complete-filter-single-select-multiple-fields-display/auto-complete-filter-single-select-multiple-fields-display.stories.tsx +35 -0
- package/src/core-components/molecules/css-multiline-input/css-multiline-input.component.tsx +135 -0
- package/src/core-components/molecules/css-multiline-input/css-multiline-input.stories.tsx +18 -0
- package/src/core-components/molecules/css-multiline-input/css-multiline-input.test.tsx +11 -0
- package/src/core-components/molecules/css-multiline-input/css-properties.ts +177 -0
- package/src/core-utils/index.ts +1 -0
- package/src/core-utils/unit-test.utils.tsx +12 -0
- package/src/custom.d.ts +4 -0
- package/src/index.css +17 -0
- package/src/index.ts +1 -0
- package/src/index.tsx +18 -0
- package/src/library/assets/svg/DownArrow.svg +14 -0
- package/src/library/assets/svg/UpArrow.svg +14 -0
- package/src/library/assets/svg/checkedBox.svg +14 -0
- package/src/library/assets/svg/checkedRadio.svg +13 -0
- package/src/library/assets/svg/datePicker.svg +3 -0
- package/src/library/assets/svg/index.ts +38 -0
- package/src/library/assets/svg/timer copy.svg +3 -0
- package/src/library/assets/svg/timer.svg +3 -0
- package/src/library/assets/svg/unCheckbox.svg +3 -0
- package/src/library/assets/svg/uncheckRadio.svg +3 -0
- package/src/logo.svg +1 -0
- package/src/react-app-env.d.ts +1 -0
- package/src/reportWebVitals.ts +16 -0
- package/src/setupTests.ts +5 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {render, fireEvent} from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import {
|
|
5
|
+
CheckBox,
|
|
6
|
+
Input,
|
|
7
|
+
Toggle,
|
|
8
|
+
MultilineInput,
|
|
9
|
+
Label,
|
|
10
|
+
InputDate,
|
|
11
|
+
InputFile,
|
|
12
|
+
InputRadio,
|
|
13
|
+
InputWrapper,
|
|
14
|
+
} from './form.component';
|
|
15
|
+
|
|
16
|
+
describe('Checkbox component', () => {
|
|
17
|
+
it('render checkbox correctly', () => {
|
|
18
|
+
const checkbox = render(<CheckBox />);
|
|
19
|
+
expect(checkbox).toMatchSnapshot();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Input component', () => {
|
|
24
|
+
it('render input correctly', () => {
|
|
25
|
+
const inputComp = render(
|
|
26
|
+
<Input
|
|
27
|
+
type="text"
|
|
28
|
+
value="test"
|
|
29
|
+
onChange={(val) => jest.fn()}
|
|
30
|
+
onBlur={(value) => jest.fn()}
|
|
31
|
+
onKeyDown={(val) => jest.fn()}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
const input = inputComp.getByTestId('INPT');
|
|
35
|
+
fireEvent.change(input, {target: {value: 'check'}});
|
|
36
|
+
userEvent.type(input, 'value');
|
|
37
|
+
fireEvent.blur(input);
|
|
38
|
+
fireEvent.keyDown(input, {
|
|
39
|
+
key: 'Escape',
|
|
40
|
+
code: 'Escape',
|
|
41
|
+
keyCode: 27,
|
|
42
|
+
charCode: 27,
|
|
43
|
+
});
|
|
44
|
+
fireEvent.keyPress(input, {key: 'Enter', keyCode: 13});
|
|
45
|
+
|
|
46
|
+
expect(input).toMatchSnapshot();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('Toggle component', () => {
|
|
50
|
+
it('render toggle correctly', () => {
|
|
51
|
+
const toggle = render(<Toggle />);
|
|
52
|
+
expect(toggle).toMatchSnapshot();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// describe('SelectOption component', () => {
|
|
57
|
+
// it('render selectOption correctly', () => {
|
|
58
|
+
// const select = render(
|
|
59
|
+
// <SelectOption key='test' onChange={value => jest.fn()} value='test' />,
|
|
60
|
+
// );
|
|
61
|
+
// const selectOption = select.getByTestId('SELECT');
|
|
62
|
+
// fireEvent.change(selectOption, {target: {value: 'check'}});
|
|
63
|
+
// userEvent.type(selectOption, 'value');
|
|
64
|
+
// expect(select).toMatchSnapshot();
|
|
65
|
+
// });
|
|
66
|
+
// });
|
|
67
|
+
describe('MultilineInput component', () => {
|
|
68
|
+
it('render multilineInput correctly', () => {
|
|
69
|
+
const multi = render(<MultilineInput />);
|
|
70
|
+
expect(multi).toMatchSnapshot();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// describe('Clock component', () => {
|
|
75
|
+
// it('render clock correctly', () => {
|
|
76
|
+
// const clock = render(
|
|
77
|
+
// <Clock value='2022-02-20' onChange={value => jest.fn()} />,
|
|
78
|
+
// );
|
|
79
|
+
// expect(clock).toMatchSnapshot();
|
|
80
|
+
// });
|
|
81
|
+
// });
|
|
82
|
+
describe('Label component', () => {
|
|
83
|
+
it('render label correctly', () => {
|
|
84
|
+
const label = render(<Label htmlFor="vsdvrf" />);
|
|
85
|
+
expect(label).toMatchSnapshot();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('InputDate component', () => {
|
|
90
|
+
it('render inputDate correctly', () => {
|
|
91
|
+
const inputDate = render(<InputDate />);
|
|
92
|
+
expect(inputDate).toMatchSnapshot();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
// describe('InputDateTime component', () => {
|
|
96
|
+
// it('render inputDateTime correctly', () => {
|
|
97
|
+
// const inputDateTime = render(<InputDateTime />);
|
|
98
|
+
// expect(inputDateTime).toMatchSnapshot();
|
|
99
|
+
// });
|
|
100
|
+
// });
|
|
101
|
+
|
|
102
|
+
describe('InputFile component', () => {
|
|
103
|
+
it('render inputFile correctly', () => {
|
|
104
|
+
const inputFile = render(<InputFile />);
|
|
105
|
+
expect(inputFile).toMatchSnapshot();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('InputRadio component', () => {
|
|
109
|
+
it('render inputRadio correctly', () => {
|
|
110
|
+
const inputRadio = render(<InputRadio />);
|
|
111
|
+
expect(inputRadio).toMatchSnapshot();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('InputWrapper component', () => {
|
|
116
|
+
it('render inputWrapper correctly', () => {
|
|
117
|
+
const inputWrapper = render(<InputWrapper />);
|
|
118
|
+
expect(inputWrapper).toMatchSnapshot();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, {Suspense} from 'react';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import {Tooltip} from '../..';
|
|
4
|
+
import loadable from '@loadable/component';
|
|
5
|
+
import {IconBaseProps} from 'react-icons/lib';
|
|
6
|
+
|
|
7
|
+
interface IconProps {
|
|
8
|
+
nameIcon: string;
|
|
9
|
+
propsIcon?: IconBaseProps;
|
|
10
|
+
tooltip?: string;
|
|
11
|
+
isDisable?: boolean;
|
|
12
|
+
onClick?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let lib = 'md';
|
|
16
|
+
export const Icon = ({
|
|
17
|
+
nameIcon,
|
|
18
|
+
propsIcon,
|
|
19
|
+
tooltip = '',
|
|
20
|
+
isDisable = false,
|
|
21
|
+
onClick,
|
|
22
|
+
}: IconProps): JSX.Element => {
|
|
23
|
+
try {
|
|
24
|
+
const iconProps = {
|
|
25
|
+
...propsIcon,
|
|
26
|
+
color: isDisable ? '#808080' : propsIcon?.color || '#ffffff',
|
|
27
|
+
};
|
|
28
|
+
const lib = nameIcon
|
|
29
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
30
|
+
.split(' ')[0]
|
|
31
|
+
.toLocaleLowerCase();
|
|
32
|
+
|
|
33
|
+
let ElementIcon: any;
|
|
34
|
+
if (lib == 'md')
|
|
35
|
+
ElementIcon = loadable(() => import('react-icons/md'), {
|
|
36
|
+
resolveComponent: (el) =>
|
|
37
|
+
el[nameIcon] != null ? el[nameIcon] : el[Object.keys(el.default)[0]],
|
|
38
|
+
});
|
|
39
|
+
else
|
|
40
|
+
ElementIcon = loadable(() => import('react-icons/fa'), {
|
|
41
|
+
resolveComponent: (el) =>
|
|
42
|
+
el[nameIcon] != null ? el[nameIcon] : el[Object.keys(el.default)[0]],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
onClick={() => {
|
|
48
|
+
if (!isDisable) onClick && onClick();
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{!_.isEmpty(tooltip) ? (
|
|
52
|
+
<Tooltip tooltipText={tooltip}>
|
|
53
|
+
<ElementIcon {...iconProps} />
|
|
54
|
+
</Tooltip>
|
|
55
|
+
) : (
|
|
56
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
57
|
+
<ElementIcon {...iconProps} />
|
|
58
|
+
</Suspense>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return <></>;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
+
import {Icon} from './icons.component';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Icon> = {
|
|
6
|
+
title: 'Design System/Atoms/Icon',
|
|
7
|
+
component: Icon,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
componentSubtitle: `import { Icon } from 'react-restyle-components'`,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof Icon>;
|
|
15
|
+
|
|
16
|
+
export const Primary: Story = {
|
|
17
|
+
args: {
|
|
18
|
+
nameIcon: 'FaHistory',
|
|
19
|
+
propsIcon: {
|
|
20
|
+
color: '#000000',
|
|
21
|
+
size: 24,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {render} from '@core-utils';
|
|
3
|
+
import {Icon} from './icons.component';
|
|
4
|
+
it('render Icon correctly', () => {
|
|
5
|
+
const icon = render(
|
|
6
|
+
<Icon
|
|
7
|
+
nameIcon="FaHistory"
|
|
8
|
+
propsIcon={{
|
|
9
|
+
color: '#000000',
|
|
10
|
+
size: 24,
|
|
11
|
+
}}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
expect(icon).toMatchSnapshot();
|
|
15
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import React, {useEffect, useRef} from 'react';
|
|
3
|
+
import './input-otp.styles.css';
|
|
4
|
+
interface PinInputGridProps {
|
|
5
|
+
title?: string;
|
|
6
|
+
hasError?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
disable?: boolean;
|
|
9
|
+
blur?: any;
|
|
10
|
+
name?: string;
|
|
11
|
+
pin: Array<number | undefined>;
|
|
12
|
+
|
|
13
|
+
onPinChanged: (pinEntry: number | undefined, index: number) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const InputOtp: React.FC<PinInputGridProps> = ({
|
|
17
|
+
className,
|
|
18
|
+
pin,
|
|
19
|
+
onPinChanged,
|
|
20
|
+
}) => {
|
|
21
|
+
const pinLength = 6;
|
|
22
|
+
const Pin_Min_Value = 0;
|
|
23
|
+
const Pin_Max_Value = 9;
|
|
24
|
+
const BACKSPACE_Key = 'Backspace';
|
|
25
|
+
const inputRefs = useRef<HTMLInputElement[]>([]);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const ref = inputRefs.current[0];
|
|
29
|
+
if (ref) {
|
|
30
|
+
ref.focus();
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const changePinFocus = (pinIndex: number) => {
|
|
35
|
+
const ref = inputRefs.current[pinIndex];
|
|
36
|
+
if (ref) {
|
|
37
|
+
ref.focus();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const onChange = (
|
|
42
|
+
event: React.ChangeEvent<HTMLInputElement>,
|
|
43
|
+
index: number
|
|
44
|
+
) => {
|
|
45
|
+
const value = event.target.value;
|
|
46
|
+
const pinNumber = Number(value.trim());
|
|
47
|
+
|
|
48
|
+
if (isNaN(pinNumber) || value.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (pinNumber >= Pin_Min_Value && pinNumber <= Pin_Max_Value) {
|
|
53
|
+
onPinChanged(pinNumber, index);
|
|
54
|
+
if (index < pinLength - 1) {
|
|
55
|
+
changePinFocus(index + 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const onKeyDown = (
|
|
61
|
+
event: React.KeyboardEvent<HTMLInputElement>,
|
|
62
|
+
index: number
|
|
63
|
+
) => {
|
|
64
|
+
console.log({event, index});
|
|
65
|
+
|
|
66
|
+
const keyboardKeyCode = event.nativeEvent.code;
|
|
67
|
+
if (keyboardKeyCode != BACKSPACE_Key) return;
|
|
68
|
+
|
|
69
|
+
if (pin[index] === undefined) {
|
|
70
|
+
changePinFocus(index - 1);
|
|
71
|
+
} else {
|
|
72
|
+
onPinChanged(undefined, index);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className={`${className} relative`}>
|
|
78
|
+
<div className="flex space-x-1 bg-background-secondary rounded-lg outline-offset-8">
|
|
79
|
+
{Array.from({length: pinLength}, (_, index) => (
|
|
80
|
+
<input
|
|
81
|
+
data-testid="onPinChanged"
|
|
82
|
+
className="pt-3 pb-2 block w-full px-0 text-5xl font-extrabold border-b-0 text-orange mt-0 bg-transparent text-center z-20 appearance-none focus:outline-none font-nunitoSansRegular"
|
|
83
|
+
onKeyDown={(event) => onKeyDown(event, index)}
|
|
84
|
+
key={index}
|
|
85
|
+
onMouseUp={() => {
|
|
86
|
+
if (pin.join('').length <= 0) {
|
|
87
|
+
changePinFocus(0);
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
90
|
+
onClick={() => {
|
|
91
|
+
if (pin.join('').length <= 0) {
|
|
92
|
+
onPinChanged(undefined, 0);
|
|
93
|
+
}
|
|
94
|
+
}}
|
|
95
|
+
ref={(el) => {
|
|
96
|
+
if (el) {
|
|
97
|
+
inputRefs.current[index] = el;
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
onChange={(event) => onChange(event, index)}
|
|
101
|
+
value={pin[index] || ''}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.-z-1 {
|
|
2
|
+
z-index: -1;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.origin-0 {
|
|
6
|
+
transform-origin: 0%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
input:focus ~ label,
|
|
10
|
+
input:not(:placeholder-shown) ~ label,
|
|
11
|
+
textarea:focus ~ label,
|
|
12
|
+
textarea:not(:placeholder-shown) ~ label,
|
|
13
|
+
select:focus ~ label,
|
|
14
|
+
select:not([value=""]):valid ~ label {
|
|
15
|
+
/* @apply transform; scale-75; -translate-y-6; */
|
|
16
|
+
--tw-translate-x: 0;
|
|
17
|
+
--tw-translate-y: 0;
|
|
18
|
+
--tw-rotate: 0;
|
|
19
|
+
--tw-skew-x: 0;
|
|
20
|
+
--tw-skew-y: 0;
|
|
21
|
+
transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y))
|
|
22
|
+
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))
|
|
23
|
+
scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
24
|
+
--tw-scale-x: 0.75;
|
|
25
|
+
--tw-scale-y: 0.75;
|
|
26
|
+
--tw-translate-y: -1.5rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
input:focus ~ label,
|
|
30
|
+
select:focus ~ label {
|
|
31
|
+
/* @apply text-black; left-0; */
|
|
32
|
+
--tw-text-opacity: 1;
|
|
33
|
+
color: "#696969";
|
|
34
|
+
left: 0px;
|
|
35
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import React, {useRef, useState} from 'react';
|
|
3
|
+
import './input.styles.css';
|
|
4
|
+
|
|
5
|
+
interface PinInputGridProps {
|
|
6
|
+
title: string;
|
|
7
|
+
hasError?: any;
|
|
8
|
+
errorMsg?: string;
|
|
9
|
+
defaultMsg?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
disable?: boolean;
|
|
12
|
+
name?: string;
|
|
13
|
+
defaultPin: Array<number | undefined>;
|
|
14
|
+
onPinChanged: (item: any, index: number) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const InputPin: React.FC<PinInputGridProps> = ({
|
|
18
|
+
title,
|
|
19
|
+
className,
|
|
20
|
+
defaultPin,
|
|
21
|
+
errorMsg,
|
|
22
|
+
defaultMsg,
|
|
23
|
+
hasError,
|
|
24
|
+
onPinChanged,
|
|
25
|
+
}) => {
|
|
26
|
+
const pin = useRef<any>([]);
|
|
27
|
+
const [reload, setReload] = useState<boolean>(false);
|
|
28
|
+
const pinLength = 12;
|
|
29
|
+
const Pin_Min_Value = 0;
|
|
30
|
+
const Pin_Max_Value = 9;
|
|
31
|
+
const BACKSPACE_Key = 'Backspace';
|
|
32
|
+
const inputRefs = useRef<HTMLInputElement[]>([]);
|
|
33
|
+
|
|
34
|
+
const removeValuesFromArray = (valuesArray: string[], value: string) => {
|
|
35
|
+
const valueIndex = valuesArray.findIndex((entry) => entry === value);
|
|
36
|
+
if (valueIndex === -1) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
valuesArray.splice(valueIndex, 1);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const changePinFocus = (pinIndex: number) => {
|
|
43
|
+
const ref = inputRefs.current[pinIndex];
|
|
44
|
+
if (ref) {
|
|
45
|
+
ref.focus();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const onChange = (
|
|
50
|
+
event: React.ChangeEvent<HTMLInputElement>,
|
|
51
|
+
index: number
|
|
52
|
+
) => {
|
|
53
|
+
const previousValue = event.target.defaultValue;
|
|
54
|
+
const valueArray = event.target.value.split('');
|
|
55
|
+
const output = removeValuesFromArray(valueArray, previousValue);
|
|
56
|
+
const value = valueArray.pop();
|
|
57
|
+
|
|
58
|
+
if (!value) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const pinNumber = Number(value);
|
|
62
|
+
|
|
63
|
+
if (isNaN(pinNumber) || value.length === 0) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
pin.current[pin.current?.length] = value;
|
|
67
|
+
setReload(!reload);
|
|
68
|
+
if (pinNumber >= Pin_Min_Value && pinNumber <= Pin_Max_Value) {
|
|
69
|
+
onPinChanged(pin.current, index);
|
|
70
|
+
if (index < pinLength - 1) {
|
|
71
|
+
changePinFocus(index + 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const onKeyDown = (
|
|
76
|
+
event: React.KeyboardEvent<HTMLInputElement>,
|
|
77
|
+
index: number
|
|
78
|
+
) => {
|
|
79
|
+
const keyboardKeyCode = event.nativeEvent.code;
|
|
80
|
+
if (keyboardKeyCode != BACKSPACE_Key) return;
|
|
81
|
+
if (pin.current[index] !== undefined && keyboardKeyCode === BACKSPACE_Key) {
|
|
82
|
+
changePinFocus(index - 1);
|
|
83
|
+
}
|
|
84
|
+
if (pin.current[index] === undefined) {
|
|
85
|
+
changePinFocus(index - 1);
|
|
86
|
+
} else {
|
|
87
|
+
onPinChanged(undefined, index);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className={`${className} relative`}>
|
|
93
|
+
<label className="duration-300 -z-1 origin-0 text-gray-dark-secondary font-nunitoSansRegular text-md">
|
|
94
|
+
{title}
|
|
95
|
+
</label>
|
|
96
|
+
<div className="flex space-x-1">
|
|
97
|
+
{Array.from({length: pinLength}, (_, index) => (
|
|
98
|
+
<div className="flex space-x-1 max-w-full items-stretch" key={index}>
|
|
99
|
+
<input
|
|
100
|
+
data-testid="inputElementPin"
|
|
101
|
+
type="tel"
|
|
102
|
+
className="pb-2 block w-4 px-0 mt-0 bg-transparent text-center border-0 border-b z-20 appearance-none rounded-none focus:outline-none focus:ring-0 focus:border-gray-secondary border-gray-secondary font-nunitoSansRegular "
|
|
103
|
+
onKeyUp={(event) => onKeyDown(event, index)}
|
|
104
|
+
key={index}
|
|
105
|
+
onMouseUp={() => {
|
|
106
|
+
if (pin.current.join('').length <= 0) {
|
|
107
|
+
changePinFocus(0);
|
|
108
|
+
}
|
|
109
|
+
}}
|
|
110
|
+
onClick={() => {
|
|
111
|
+
if (pin.current.join('').length <= 0) {
|
|
112
|
+
onPinChanged(undefined, 0);
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
115
|
+
ref={(el) => {
|
|
116
|
+
if (el) {
|
|
117
|
+
inputRefs.current[index] = el;
|
|
118
|
+
}
|
|
119
|
+
}}
|
|
120
|
+
onChange={(event) => onChange(event, index)}
|
|
121
|
+
value={pin.current[index]?.toString() || ''}
|
|
122
|
+
/>
|
|
123
|
+
<div className="invisible">
|
|
124
|
+
{(index + 1) % 4 === 0 ? 'ss' : null}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
{hasError && hasError?.type !== 'required' && (
|
|
130
|
+
<span
|
|
131
|
+
className="text-sm text-red text-4xs font-nunitoSansRegular"
|
|
132
|
+
id="error"
|
|
133
|
+
>
|
|
134
|
+
{`${errorMsg || ''}`}
|
|
135
|
+
</span>
|
|
136
|
+
)}
|
|
137
|
+
{(!hasError || hasError?.type === 'required') && (
|
|
138
|
+
<span className="text-sm text-4xs font-nunitoSansRegular" id="error">
|
|
139
|
+
{`${defaultMsg || ''}`}
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
+
import {InputPin} from './input-pin.component';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof InputPin> = {
|
|
6
|
+
title: 'Design System/Atoms/InputPin',
|
|
7
|
+
component: InputPin,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
componentSubtitle: `import { InputPin } from 'react-restyle-components'`,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof InputPin>;
|
|
15
|
+
|
|
16
|
+
export const Primary: Story = {
|
|
17
|
+
args: {
|
|
18
|
+
title: 'Enter your Aadhaar Number',
|
|
19
|
+
hasError: true,
|
|
20
|
+
defaultPin: [],
|
|
21
|
+
onPinChanged: (item) => {
|
|
22
|
+
console.log({item});
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable testing-library/render-result-naming-convention */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import {render} from '@core-utils';
|
|
4
|
+
import {InputPin} from './input-pin.component';
|
|
5
|
+
|
|
6
|
+
it('render InputOtp correctly without error', () => {
|
|
7
|
+
const pinInput = render(
|
|
8
|
+
<InputPin
|
|
9
|
+
title="Enter your Aadhaar Number"
|
|
10
|
+
hasError={false}
|
|
11
|
+
errorMsg="Uh oh! You’ve entered an invalid aadhaar number"
|
|
12
|
+
onPinChanged={() => jest.fn()}
|
|
13
|
+
defaultPin={[]}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
expect(pinInput).toMatchSnapshot();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('render InputOtp correctly with error', () => {
|
|
20
|
+
const pinInput = render(
|
|
21
|
+
<InputPin
|
|
22
|
+
title="Enter your Aadhaar Number"
|
|
23
|
+
hasError={true}
|
|
24
|
+
errorMsg="Uh oh! You’ve entered an invalid aadhaar number"
|
|
25
|
+
onPinChanged={() => jest.fn()}
|
|
26
|
+
defaultPin={[]}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
expect(pinInput).toMatchSnapshot();
|
|
30
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import React, {useState} from 'react';
|
|
3
|
+
import './input.styles.css';
|
|
4
|
+
|
|
5
|
+
interface InputProps {
|
|
6
|
+
title: string;
|
|
7
|
+
defaultValue: string;
|
|
8
|
+
hasError?: any;
|
|
9
|
+
errorMsg?: string;
|
|
10
|
+
defaultMsg?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
disable?: boolean;
|
|
13
|
+
inputStyle?: string;
|
|
14
|
+
autoComplete?: 'off' | 'on';
|
|
15
|
+
maxlength?: number;
|
|
16
|
+
onChange: (value: any) => void;
|
|
17
|
+
onBlur?: (value: any) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Input = ({
|
|
21
|
+
title,
|
|
22
|
+
defaultValue,
|
|
23
|
+
className,
|
|
24
|
+
disable,
|
|
25
|
+
hasError,
|
|
26
|
+
errorMsg,
|
|
27
|
+
defaultMsg,
|
|
28
|
+
inputStyle,
|
|
29
|
+
autoComplete,
|
|
30
|
+
maxlength = 40,
|
|
31
|
+
onChange,
|
|
32
|
+
onBlur,
|
|
33
|
+
}: InputProps) => {
|
|
34
|
+
const [value, setValue] = useState(defaultValue);
|
|
35
|
+
|
|
36
|
+
const onKeyUpValue = (event) => {
|
|
37
|
+
onChange(event.target.value);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className={`${className} relative`}>
|
|
42
|
+
<input
|
|
43
|
+
data-testid="inputElement"
|
|
44
|
+
type="text"
|
|
45
|
+
name="name"
|
|
46
|
+
value={value}
|
|
47
|
+
placeholder=" "
|
|
48
|
+
disabled={disable}
|
|
49
|
+
className={`pt-3 pb-2 bg-transparent block w-full px-0 mt-0 rounded-none border-0 border-b appearance-none focus:outline-none focus:ring-0 focus:border-gray-secondary border-gray-secondary font-nunitoSansRegular ${inputStyle}`}
|
|
50
|
+
onChange={(e) => {
|
|
51
|
+
setValue(e.target.value);
|
|
52
|
+
onChange && onChange(e.target.value);
|
|
53
|
+
}}
|
|
54
|
+
onKeyUp={onKeyUpValue}
|
|
55
|
+
onBlur={(e) => onBlur && onBlur(e.target.value)}
|
|
56
|
+
autoComplete={autoComplete}
|
|
57
|
+
maxLength={maxlength}
|
|
58
|
+
/>
|
|
59
|
+
<label className="absolute duration-300 top-3 -z-1 origin-0 text-gray-dark-secondary font-nunitoSansRegular text-md">
|
|
60
|
+
{title}
|
|
61
|
+
</label>
|
|
62
|
+
{hasError && hasError?.type !== 'required' && (
|
|
63
|
+
<span className="text-sm text-red text-4xs" id="error">
|
|
64
|
+
{`${errorMsg || ''}`}
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
{(!hasError || hasError?.type === 'required') && (
|
|
68
|
+
<span className="text-sm text-4xs" id="error">
|
|
69
|
+
{`${defaultMsg || ''}`}
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
+
import {Input} from './input.component';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Input> = {
|
|
6
|
+
title: 'Design System/Atoms/Input',
|
|
7
|
+
component: Input,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
};
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof Input>;
|
|
12
|
+
|
|
13
|
+
export const Primary: Story = {
|
|
14
|
+
args: {
|
|
15
|
+
className: 'mt-4',
|
|
16
|
+
title: 'Enter your Pan Number',
|
|
17
|
+
hasError: true,
|
|
18
|
+
defaultValue: '',
|
|
19
|
+
onChange: (item) => {
|
|
20
|
+
console.log({item});
|
|
21
|
+
},
|
|
22
|
+
onBlur: (item) => {
|
|
23
|
+
console.log({item});
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|