@mistertemp/libs-front-shared 1.1.1
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/CHANGELOG.md +15 -0
- package/README.md +18 -0
- package/index.ts +3 -0
- package/package.json +100 -0
- package/src/components/ControlledInputDate/ControlledInputDate.tsx +82 -0
- package/src/components/ControlledInputDate/__tests__/ControlledInputDate.unit.test.tsx +78 -0
- package/src/components/ControlledInputDate/index.ts +1 -0
- package/src/components/ControlledInputTime/ControlledInputTime.tsx +70 -0
- package/src/components/ControlledInputTime/__tests__/ControlledInputTime.unit.test.tsx +108 -0
- package/src/components/ControlledInputTime/index.ts +1 -0
- package/src/components/CopyToClipboard/CopyToClipboard.tsx +61 -0
- package/src/components/CopyToClipboard/__tests__/CopyToClipboard.unit.test.tsx +54 -0
- package/src/components/CopyToClipboard/index.ts +1 -0
- package/src/components/DateFilter/DateFilter.module.scss +4 -0
- package/src/components/DateFilter/DateFilter.tsx +117 -0
- package/src/components/DateFilter/DateFilter.utils.tsx +65 -0
- package/src/components/DateFilter/__tests__/DateFilter.unit.test.tsx +39 -0
- package/src/components/DateFilter/index.ts +1 -0
- package/src/components/EnvironmentFilter/EnvironmentFilter.tsx +74 -0
- package/src/components/EnvironmentFilter/__tests__/EnvironmentFilter.unit.test.tsx +99 -0
- package/src/components/EnvironmentFilter/index.ts +1 -0
- package/src/components/Filter/Filter.tsx +127 -0
- package/src/components/Filter/index.ts +1 -0
- package/src/components/ProfessionFilter/ProfessionFilter.module.scss +4 -0
- package/src/components/ProfessionFilter/ProfessionFilter.tsx +145 -0
- package/src/components/ProfessionFilter/__tests__/ProfessionFilter.unit.test.tsx +195 -0
- package/src/components/ProfessionFilter/index.ts +1 -0
- package/src/components/TextFilter/TextFilter.module.scss +3 -0
- package/src/components/TextFilter/TextFilter.tsx +112 -0
- package/src/components/TextFilter/__tests__/TextFilter.unit.test.tsx +75 -0
- package/src/components/TextFilter/index.ts +1 -0
- package/src/components/index.ts +8 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useBundledTranslation.ts +35 -0
- package/src/hooks/useDebouncedSearchInput.ts +36 -0
- package/src/hooks/usePrevious.ts +13 -0
- package/src/locales/fr/date-picker.json +11 -0
- package/src/locales/fr/scc-common-filters.json +22 -0
- package/src/locales/it/date-picker.json +11 -0
- package/src/locales/it/scc-common-filters.json +22 -0
- package/src/mocks/professions.ts +24 -0
- package/src/typings/filters.ts +7 -0
- package/src/typings/index.ts +1 -0
- package/tsconfig.json +25 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.1.1](https://github.com/mistertemp/shared-libs-front/compare/@mistertemp/libs-front-shared@1.1.0...@mistertemp/libs-front-shared@1.1.1) (2025-05-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* publish ([#450](https://github.com/mistertemp/shared-libs-front/issues/450)) ([caf27e3](https://github.com/mistertemp/shared-libs-front/commit/caf27e3519662eb4a66d55317ba0315b442b9aa2))
|
|
9
|
+
|
|
10
|
+
## [1.1.0](https://github.com/mistertemp/shared-core-components/compare/@mistertemp/libs-front-shared-v1.0.0...@mistertemp/libs-front-shared@1.1.0) (2025-05-05)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* deploy everything kaboom ([67c63d4](https://github.com/mistertemp/shared-core-components/commit/67c63d4e680a844966a8aff7d25aab4da0e97574))
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @mistertemp/common-core-components
|
|
2
|
+
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Components
|
|
6
|
+
- **CopyToClipboard** added
|
|
7
|
+
|
|
8
|
+
## 1.2.0
|
|
9
|
+
### Features
|
|
10
|
+
- **ControlledInputDate**: add min and max date handling
|
|
11
|
+
|
|
12
|
+
## 1.1.0
|
|
13
|
+
### Components
|
|
14
|
+
- **ControlledInputTime** for compatibility with react-hook-form
|
|
15
|
+
|
|
16
|
+
## 1.0.0
|
|
17
|
+
### Components
|
|
18
|
+
- **ControlledInputDate** for compatibility with react-hook-form
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mistertemp/libs-front-shared",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"main": "index.ts",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"demo": "vite --config ./demo/vite.config.ts",
|
|
8
|
+
"lint": "eslint --cache . --ext .ts --ext .tsx",
|
|
9
|
+
"lint:fix": "eslint . --ext .ts --ext .tsx --fix",
|
|
10
|
+
"lint-style": "stylelint '**/*.scss'",
|
|
11
|
+
"lint-style:fix": "stylelint '**/*.scss' --fix",
|
|
12
|
+
"type-check": "tsc --noEmit",
|
|
13
|
+
"test": "NODE_ENV=test ENV=local jest --config=./.jest/jest.config.json",
|
|
14
|
+
"test:watch": "NODE_ENV=test ENV=test jest --watch --config=./jest.config.json --rootDir=."
|
|
15
|
+
},
|
|
16
|
+
"eslintConfig": {
|
|
17
|
+
"extends": "./.eslintrc"
|
|
18
|
+
},
|
|
19
|
+
"prettier": "./.prettierrc.json",
|
|
20
|
+
"stylelint": {
|
|
21
|
+
"extends": "./.stylelintrc"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@axe-core/react": "4.10.0",
|
|
25
|
+
"@mistertemp/design-system": "12.0.0",
|
|
26
|
+
"@mistertemp/libs-work-environments": "0.8.1",
|
|
27
|
+
"@mistertemp/types-shared": "2.2.0",
|
|
28
|
+
"@testing-library/dom": "10.4.0",
|
|
29
|
+
"@testing-library/jest-dom": "6.6.2",
|
|
30
|
+
"@testing-library/react": "12.1.5",
|
|
31
|
+
"@testing-library/user-event": "14.6.1",
|
|
32
|
+
"@types/jest": "29.5.7",
|
|
33
|
+
"@types/lodash": "4.17.4",
|
|
34
|
+
"@types/react": "17.0.52",
|
|
35
|
+
"@types/react-dom": "17.0.18",
|
|
36
|
+
"@types/react-router-dom": "5.3.3",
|
|
37
|
+
"@types/react-test-renderer": "^18.0.7",
|
|
38
|
+
"@types/react-transition-group": "4.4.10",
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "7.18.0",
|
|
40
|
+
"@typescript-eslint/parser": "7.18.0",
|
|
41
|
+
"@vitejs/plugin-react-swc": "3.9.0",
|
|
42
|
+
"date-fns": "2.30.0",
|
|
43
|
+
"eslint": "8.57.0",
|
|
44
|
+
"eslint-config-prettier": "9.1.0",
|
|
45
|
+
"eslint-plugin-import": "2.31.0",
|
|
46
|
+
"eslint-plugin-jsx-a11y": "6.10.0",
|
|
47
|
+
"eslint-plugin-prettier": "5.2.1",
|
|
48
|
+
"eslint-plugin-react": "7.37.1",
|
|
49
|
+
"eslint-plugin-react-hooks": "4.6.0",
|
|
50
|
+
"eslint-plugin-simple-import-sort": "12.1.0",
|
|
51
|
+
"eslint-plugin-testing-library": "6.5.0",
|
|
52
|
+
"fuse.js": "7.0.0",
|
|
53
|
+
"i18next": "23.15.1",
|
|
54
|
+
"i18next-browser-languagedetector": "8.0.4",
|
|
55
|
+
"i18next-http-backend": "2.6.1",
|
|
56
|
+
"identity-obj-proxy": "3.0.0",
|
|
57
|
+
"jest-environment-jsdom": "29.7.0",
|
|
58
|
+
"lodash": "4.17.21",
|
|
59
|
+
"prettier": "3.4.1",
|
|
60
|
+
"react": "17.0.2",
|
|
61
|
+
"react-dom": "17.0.2",
|
|
62
|
+
"react-hook-form": "7.53.0",
|
|
63
|
+
"react-i18next": "14.1.0",
|
|
64
|
+
"react-router-dom": "6.28.0",
|
|
65
|
+
"react-transition-group": "4.4.5",
|
|
66
|
+
"sass": "1.87.0",
|
|
67
|
+
"stylelint": "16.19.1",
|
|
68
|
+
"stylelint-config-idiomatic-order": "10.0.0",
|
|
69
|
+
"stylelint-config-standard-scss": "14.0.0",
|
|
70
|
+
"stylelint-prettier": "5.0.3",
|
|
71
|
+
"ts-jest": "29.2.3",
|
|
72
|
+
"ts-node": "10.9.1",
|
|
73
|
+
"typescript": "5.7.2",
|
|
74
|
+
"vite": "6.3.4",
|
|
75
|
+
"vite-plugin-environment": "1.1.3",
|
|
76
|
+
"vite-plugin-svgr": "4.3.0"
|
|
77
|
+
},
|
|
78
|
+
"peerDependencies": {
|
|
79
|
+
"@mistertemp/design-system": ">=12.0.0",
|
|
80
|
+
"@mistertemp/libs-work-environments": "^0.8.1",
|
|
81
|
+
"i18next": "^23.5.0",
|
|
82
|
+
"i18next-browser-languagedetector": ">=8.0.0",
|
|
83
|
+
"i18next-http-backend": ">=2.2.1",
|
|
84
|
+
"react": "^17.0.2",
|
|
85
|
+
"react-dom": "^17.0.2",
|
|
86
|
+
"react-i18next": "^14.0.0 || ^15.0.0"
|
|
87
|
+
},
|
|
88
|
+
"browserslist": {
|
|
89
|
+
"production": [
|
|
90
|
+
">0.2%",
|
|
91
|
+
"not dead",
|
|
92
|
+
"not op_mini all"
|
|
93
|
+
],
|
|
94
|
+
"development": [
|
|
95
|
+
"last 1 chrome version",
|
|
96
|
+
"last 1 firefox version",
|
|
97
|
+
"last 1 safari version"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Input, InputProps } from '@mistertemp/design-system';
|
|
2
|
+
import { format, parse } from 'date-fns';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Controller, FieldPath, FieldValues, UseControllerProps } from 'react-hook-form';
|
|
5
|
+
|
|
6
|
+
const controlledInputDateDefaultFormat = 'dd/MM/yyyy';
|
|
7
|
+
|
|
8
|
+
const toDateInputValue = (formDateValue: string | null | undefined, dateFormat: string) =>
|
|
9
|
+
formDateValue
|
|
10
|
+
? format(parse(formDateValue, dateFormat, new Date()), controlledInputDateDefaultFormat)
|
|
11
|
+
: '';
|
|
12
|
+
|
|
13
|
+
const toDateFormValue = (valueAsDate: Date | null | undefined, dateFormat: string) =>
|
|
14
|
+
valueAsDate ? format(valueAsDate, dateFormat) : '';
|
|
15
|
+
|
|
16
|
+
export type ControlledInputDateProps<
|
|
17
|
+
TFieldValues extends FieldValues,
|
|
18
|
+
TName extends FieldPath<TFieldValues>,
|
|
19
|
+
> = UseControllerProps<TFieldValues, TName> & {
|
|
20
|
+
inputProps: Omit<InputProps, 'inputType' | 'value'>;
|
|
21
|
+
format?: string;
|
|
22
|
+
minErrorMessage?: string;
|
|
23
|
+
maxErrorMessage?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const ControlledInputDate = <
|
|
27
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
28
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
29
|
+
>({
|
|
30
|
+
inputProps: { onChange: externalOnChange, ...otherInputProps },
|
|
31
|
+
format = controlledInputDateDefaultFormat,
|
|
32
|
+
maxErrorMessage = 'max exceeded',
|
|
33
|
+
minErrorMessage = 'min exceeded',
|
|
34
|
+
...controllerProps
|
|
35
|
+
}: ControlledInputDateProps<TFieldValues, TName>) => (
|
|
36
|
+
<Controller
|
|
37
|
+
{...controllerProps}
|
|
38
|
+
rules={{
|
|
39
|
+
...controllerProps.rules,
|
|
40
|
+
max: otherInputProps.max
|
|
41
|
+
? {
|
|
42
|
+
value: otherInputProps.max,
|
|
43
|
+
message: maxErrorMessage,
|
|
44
|
+
}
|
|
45
|
+
: undefined,
|
|
46
|
+
min: otherInputProps.min
|
|
47
|
+
? {
|
|
48
|
+
value: otherInputProps.min,
|
|
49
|
+
message: minErrorMessage,
|
|
50
|
+
}
|
|
51
|
+
: undefined,
|
|
52
|
+
}}
|
|
53
|
+
render={({ field: { value, onChange, ...fieldProps }, fieldState: { error } }) => (
|
|
54
|
+
<Input
|
|
55
|
+
{...fieldProps}
|
|
56
|
+
inputType="date"
|
|
57
|
+
value={toDateInputValue(value, format)}
|
|
58
|
+
onChange={(e) => {
|
|
59
|
+
const formValue = toDateFormValue(e.target.valueAsDate, format);
|
|
60
|
+
|
|
61
|
+
onChange(formValue);
|
|
62
|
+
|
|
63
|
+
externalOnChange?.({
|
|
64
|
+
target: {
|
|
65
|
+
name: fieldProps.name,
|
|
66
|
+
type: 'date',
|
|
67
|
+
value: formValue,
|
|
68
|
+
valueAsDate: e.target.valueAsDate,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}}
|
|
72
|
+
hint={
|
|
73
|
+
error?.type == 'max' || error?.type == 'min'
|
|
74
|
+
? error.message
|
|
75
|
+
: otherInputProps.hint
|
|
76
|
+
}
|
|
77
|
+
hasError={!!error}
|
|
78
|
+
{...otherInputProps}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import { useForm } from 'react-hook-form';
|
|
6
|
+
|
|
7
|
+
import { act, fireEvent, render, screen } from '../../../../.jest/reactTestingLibrary.config';
|
|
8
|
+
import { ControlledInputDate } from '../ControlledInputDate';
|
|
9
|
+
|
|
10
|
+
describe('Components => ControlledInputDate', () => {
|
|
11
|
+
const FormComponent = ({
|
|
12
|
+
testId,
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
}: {
|
|
16
|
+
testId: string;
|
|
17
|
+
value?: string;
|
|
18
|
+
onChange?: (value: string) => void;
|
|
19
|
+
}) => {
|
|
20
|
+
const { control, setValue, getValues, watch } = useForm<{ date: string }>({
|
|
21
|
+
defaultValues: { date: '' },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (value) {
|
|
26
|
+
setValue('date', value);
|
|
27
|
+
}
|
|
28
|
+
}, [value]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
onChange?.(getValues('date'));
|
|
32
|
+
}, [watch('date')]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ControlledInputDate
|
|
36
|
+
control={control}
|
|
37
|
+
name="date"
|
|
38
|
+
inputProps={{
|
|
39
|
+
'data-testid': testId,
|
|
40
|
+
type: 'primary',
|
|
41
|
+
size: 'sm',
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
it('should render', () => {
|
|
48
|
+
render(<FormComponent testId="test" />);
|
|
49
|
+
|
|
50
|
+
expect(screen.getByTestId('test')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByTestId('test+input')).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should apply value', () => {
|
|
55
|
+
render(<FormComponent testId="test" value="01/01/1999" />);
|
|
56
|
+
expect(screen.getByTestId('test+input')).toBeInTheDocument();
|
|
57
|
+
expect(screen.getByTestId('test+input')).toHaveValue('01/01/1999');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle onChange', async () => {
|
|
61
|
+
const onChange = jest.fn();
|
|
62
|
+
|
|
63
|
+
render(<FormComponent testId="test" onChange={onChange} />);
|
|
64
|
+
|
|
65
|
+
const input = screen.getByTestId('test+input');
|
|
66
|
+
|
|
67
|
+
expect(input).toBeInTheDocument();
|
|
68
|
+
|
|
69
|
+
// eslint-disable-next-line testing-library/no-unnecessary-act
|
|
70
|
+
await act(async () => {
|
|
71
|
+
await userEvent.type(input, '2/2/22');
|
|
72
|
+
fireEvent.focusOut(input);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(input).toHaveValue('02/02/2022');
|
|
76
|
+
expect(onChange).toHaveBeenCalledWith('02/02/2022');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ControlledInputDate';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Input, InputProps } from '@mistertemp/design-system';
|
|
2
|
+
import { DEFAULT_TIME_SEPARATOR } from '@mistertemp/design-system/src/components/design-system/Input/InputTime/InputTime.utils';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Controller, FieldPath, FieldValues, UseControllerProps } from 'react-hook-form';
|
|
5
|
+
|
|
6
|
+
const controlledInputTimeDefaultSeparator = ':';
|
|
7
|
+
|
|
8
|
+
const toTimeInputValue = (
|
|
9
|
+
formTimeValue: string | null | undefined,
|
|
10
|
+
inputSeparator: string,
|
|
11
|
+
dataSeparator: string,
|
|
12
|
+
) => {
|
|
13
|
+
return formTimeValue?.replace(dataSeparator, inputSeparator) ?? '';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const toTimeFormValue = (
|
|
17
|
+
inputValue: string | null | undefined,
|
|
18
|
+
inputSeparator: string,
|
|
19
|
+
dataSeparator: string,
|
|
20
|
+
) => {
|
|
21
|
+
return inputValue?.replace(inputSeparator, dataSeparator) ?? '';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ControlledInputTimeProps<
|
|
25
|
+
TFieldValues extends FieldValues,
|
|
26
|
+
TName extends FieldPath<TFieldValues>,
|
|
27
|
+
> = UseControllerProps<TFieldValues, TName> & {
|
|
28
|
+
inputProps: Omit<InputProps, 'inputType' | 'value'>;
|
|
29
|
+
separator?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const ControlledInputTime = <
|
|
33
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
34
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
35
|
+
>({
|
|
36
|
+
inputProps: {
|
|
37
|
+
onChange: externalOnChange,
|
|
38
|
+
timeSeparator = DEFAULT_TIME_SEPARATOR,
|
|
39
|
+
...otherInputProps
|
|
40
|
+
},
|
|
41
|
+
separator = controlledInputTimeDefaultSeparator,
|
|
42
|
+
...controllerProps
|
|
43
|
+
}: ControlledInputTimeProps<TFieldValues, TName>) => (
|
|
44
|
+
<Controller
|
|
45
|
+
{...controllerProps}
|
|
46
|
+
render={({ field: { value, onChange, ...fieldProps }, fieldState: { error } }) => (
|
|
47
|
+
<Input
|
|
48
|
+
{...fieldProps}
|
|
49
|
+
inputType="time"
|
|
50
|
+
timeSeparator={timeSeparator}
|
|
51
|
+
value={toTimeInputValue(value, timeSeparator, separator)}
|
|
52
|
+
onChange={(e) => {
|
|
53
|
+
const formValue = toTimeFormValue(e.target.value, timeSeparator, separator);
|
|
54
|
+
|
|
55
|
+
onChange(formValue);
|
|
56
|
+
|
|
57
|
+
externalOnChange?.({
|
|
58
|
+
target: {
|
|
59
|
+
name: fieldProps.name,
|
|
60
|
+
type: 'string',
|
|
61
|
+
value: formValue,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}}
|
|
65
|
+
hasError={!!error}
|
|
66
|
+
{...otherInputProps}
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import { useForm } from 'react-hook-form';
|
|
6
|
+
|
|
7
|
+
import { act, render, screen } from '../../../../.jest/reactTestingLibrary.config';
|
|
8
|
+
import { ControlledInputTime } from '../ControlledInputTime';
|
|
9
|
+
|
|
10
|
+
describe('Components => ControlledInputTime', () => {
|
|
11
|
+
const FormComponent = ({
|
|
12
|
+
testId,
|
|
13
|
+
value,
|
|
14
|
+
dataSeparator,
|
|
15
|
+
inputSeparator,
|
|
16
|
+
onChange,
|
|
17
|
+
}: {
|
|
18
|
+
testId: string;
|
|
19
|
+
value?: string;
|
|
20
|
+
dataSeparator?: string;
|
|
21
|
+
inputSeparator?: string;
|
|
22
|
+
onChange?: (value: string) => void;
|
|
23
|
+
}) => {
|
|
24
|
+
const { control, setValue, getValues, watch } = useForm<{ time: string }>({
|
|
25
|
+
defaultValues: { time: '' },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (value) {
|
|
30
|
+
setValue('time', value);
|
|
31
|
+
}
|
|
32
|
+
}, [value]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
onChange?.(getValues('time'));
|
|
36
|
+
}, [watch('time')]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<ControlledInputTime
|
|
40
|
+
control={control}
|
|
41
|
+
name="time"
|
|
42
|
+
separator={dataSeparator}
|
|
43
|
+
inputProps={{
|
|
44
|
+
'data-testid': testId,
|
|
45
|
+
type: 'primary',
|
|
46
|
+
size: 'sm',
|
|
47
|
+
timeSeparator: inputSeparator,
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
it('should render', () => {
|
|
54
|
+
render(<FormComponent testId="test" />);
|
|
55
|
+
|
|
56
|
+
expect(screen.getByTestId('test')).toBeInTheDocument();
|
|
57
|
+
expect(screen.getByTestId('test+input')).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should apply value', () => {
|
|
61
|
+
render(<FormComponent testId="test" value="10:10" />);
|
|
62
|
+
expect(screen.getByTestId('test+input')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByTestId('test+input')).toHaveValue('10:10');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle onChange', async () => {
|
|
67
|
+
const onChange = jest.fn();
|
|
68
|
+
|
|
69
|
+
render(<FormComponent testId="test" onChange={onChange} />);
|
|
70
|
+
|
|
71
|
+
const input = screen.getByTestId('test+input');
|
|
72
|
+
|
|
73
|
+
expect(input).toBeInTheDocument();
|
|
74
|
+
|
|
75
|
+
// eslint-disable-next-line testing-library/no-unnecessary-act
|
|
76
|
+
await act(async () => {
|
|
77
|
+
await userEvent.type(input, '2020');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(input).toHaveValue('20:20');
|
|
81
|
+
expect(onChange).toHaveBeenCalledWith('20:20');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle different separators between input and data', async () => {
|
|
85
|
+
const onChange = jest.fn();
|
|
86
|
+
|
|
87
|
+
render(
|
|
88
|
+
<FormComponent
|
|
89
|
+
testId="test"
|
|
90
|
+
dataSeparator="LL"
|
|
91
|
+
inputSeparator="-"
|
|
92
|
+
onChange={onChange}
|
|
93
|
+
/>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const input = screen.getByTestId('test+input');
|
|
97
|
+
|
|
98
|
+
expect(input).toBeInTheDocument();
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line testing-library/no-unnecessary-act
|
|
101
|
+
await act(async () => {
|
|
102
|
+
await userEvent.type(input, '2020');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(input).toHaveValue('20-20');
|
|
106
|
+
expect(onChange).toHaveBeenCalledWith('20LL20');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ControlledInputTime';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
DuplicateSvg,
|
|
4
|
+
SvgIcon,
|
|
5
|
+
ToastContext,
|
|
6
|
+
ToastProps,
|
|
7
|
+
ToastType,
|
|
8
|
+
} from '@mistertemp/design-system';
|
|
9
|
+
import React, { MouseEvent, useContext } from 'react';
|
|
10
|
+
|
|
11
|
+
export type CopyToClipboardProps = {
|
|
12
|
+
value?: string | null;
|
|
13
|
+
className?: string;
|
|
14
|
+
icon?: SvgIcon;
|
|
15
|
+
toast?: string | Omit<ToastProps, 'id'>;
|
|
16
|
+
onCopy?: (event: MouseEvent<Element>, value: string) => void;
|
|
17
|
+
'data-testid'?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const CopyToClipboard = ({
|
|
21
|
+
value,
|
|
22
|
+
className,
|
|
23
|
+
icon = DuplicateSvg,
|
|
24
|
+
toast,
|
|
25
|
+
onCopy,
|
|
26
|
+
'data-testid': dataTestId = 'copyToClipboard',
|
|
27
|
+
}: CopyToClipboardProps) => {
|
|
28
|
+
const toastContext = useContext(ToastContext);
|
|
29
|
+
|
|
30
|
+
const showToast = () => {
|
|
31
|
+
if (toastContext && toast) {
|
|
32
|
+
const toastProps =
|
|
33
|
+
typeof toast === 'string'
|
|
34
|
+
? {
|
|
35
|
+
type: ToastType.SUCCESS,
|
|
36
|
+
title: toast,
|
|
37
|
+
}
|
|
38
|
+
: toast;
|
|
39
|
+
|
|
40
|
+
toastContext.enqueue(toastProps);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const copyToClipboard = async (event: MouseEvent<Element>) => {
|
|
45
|
+
if (value) {
|
|
46
|
+
await navigator.clipboard.writeText(value);
|
|
47
|
+
showToast();
|
|
48
|
+
onCopy?.(event, value);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Button
|
|
54
|
+
className={className}
|
|
55
|
+
onClick={copyToClipboard}
|
|
56
|
+
data-testid={dataTestId}
|
|
57
|
+
size="xsmall"
|
|
58
|
+
leftIcon={icon}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import userEvent, { UserEvent } from '@testing-library/user-event';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { render, screen } from '../../../../.jest/reactTestingLibrary.config';
|
|
5
|
+
import { CopyToClipboard } from '../CopyToClipboard';
|
|
6
|
+
|
|
7
|
+
describe('Components => CopyToClipboard', () => {
|
|
8
|
+
let user: UserEvent;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
// Setup clipboard
|
|
12
|
+
user = userEvent.setup();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should render', () => {
|
|
16
|
+
render(<CopyToClipboard data-testid="test" />);
|
|
17
|
+
|
|
18
|
+
expect(screen.getByTestId('test')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should handle classNames', () => {
|
|
22
|
+
render(<CopyToClipboard data-testid="test" className="className" />);
|
|
23
|
+
|
|
24
|
+
expect(screen.getByTestId('test')).toHaveClass('className');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle copy to clipboard', async () => {
|
|
28
|
+
const onCopy = jest.fn();
|
|
29
|
+
const valueToCopy = 'hello world!';
|
|
30
|
+
|
|
31
|
+
render(<CopyToClipboard data-testid="test" value={valueToCopy} onCopy={onCopy} />);
|
|
32
|
+
|
|
33
|
+
await user.click(screen.getByTestId('test'));
|
|
34
|
+
|
|
35
|
+
const clipboardValue = await navigator.clipboard.readText();
|
|
36
|
+
|
|
37
|
+
expect(clipboardValue).toEqual(valueToCopy);
|
|
38
|
+
expect(onCopy).toHaveBeenCalledWith(expect.any(Object), valueToCopy);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle missing value', async () => {
|
|
42
|
+
const onCopy = jest.fn();
|
|
43
|
+
const valueToCopy = null;
|
|
44
|
+
|
|
45
|
+
render(<CopyToClipboard data-testid="test" value={valueToCopy} onCopy={onCopy} />);
|
|
46
|
+
|
|
47
|
+
await user.click(screen.getByTestId('test'));
|
|
48
|
+
|
|
49
|
+
const clipboardValue = await navigator.clipboard.readText();
|
|
50
|
+
|
|
51
|
+
expect(clipboardValue).toEqual('');
|
|
52
|
+
expect(onCopy).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CopyToClipboard';
|