@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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { render } from '../../../../.jest/reactTestingLibrary.config';
|
|
5
|
+
import { filterOptionsMock } from '../../../mocks/professions';
|
|
6
|
+
import { ProfessionFilter } from '../ProfessionFilter';
|
|
7
|
+
|
|
8
|
+
const baseProps = {
|
|
9
|
+
searchConfig: {
|
|
10
|
+
debounceDelay: 500,
|
|
11
|
+
forceEmptyValue: true,
|
|
12
|
+
inputThreshold: 2,
|
|
13
|
+
},
|
|
14
|
+
isLoading: false,
|
|
15
|
+
options: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('ProfessionFilter', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('component rendering & prop options', () => {
|
|
24
|
+
it('should render filter and dropdown with empty list and search load state', async () => {
|
|
25
|
+
render(<ProfessionFilter {...baseProps} isLoading />);
|
|
26
|
+
|
|
27
|
+
expect(screen.getByTestId('filters+profession')).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByTestId('filters+profession+trigger-button-title')).toHaveTextContent(
|
|
29
|
+
'scc-common-filters:filters.profession.placeholder',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
fireEvent.click(screen.getByTestId('filters+profession+trigger-button'));
|
|
33
|
+
|
|
34
|
+
expect(screen.getByTestId('filters+profession_popup')).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByTestId('filters+profession-input-container')).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByTestId('filters+profession+search_input')).toHaveProperty(
|
|
37
|
+
'placeholder',
|
|
38
|
+
'scc-common-filters:filters.profession.search.placeholder',
|
|
39
|
+
);
|
|
40
|
+
expect(screen.getByTestId('Loading-icon')).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByTestId('filters+profession-bottom-action-button')).toHaveTextContent(
|
|
42
|
+
'scc-common-filters:actions.removeFilter',
|
|
43
|
+
);
|
|
44
|
+
expect(
|
|
45
|
+
//eslint-disable-next-line testing-library/no-node-access
|
|
46
|
+
screen.getByTestId('filters+profession-options').children.length,
|
|
47
|
+
).toBe(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should render custom cta title and placeholder', () => {
|
|
51
|
+
const placeholder = 'Custom placeholder';
|
|
52
|
+
const ctaTitle = 'Custom title';
|
|
53
|
+
|
|
54
|
+
render(
|
|
55
|
+
<ProfessionFilter {...baseProps} ctaTitle={ctaTitle} placeholder={placeholder} />,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(screen.getByTestId('filters+profession+trigger-button-title')).toHaveTextContent(
|
|
59
|
+
`${placeholder}`,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
fireEvent.click(screen.getByTestId('filters+profession+trigger-button'));
|
|
63
|
+
|
|
64
|
+
expect(screen.getByTestId('filters+profession-bottom-action-button')).toHaveTextContent(
|
|
65
|
+
ctaTitle,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should display all profession options', async () => {
|
|
70
|
+
render(<ProfessionFilter {...baseProps} options={filterOptionsMock} />);
|
|
71
|
+
|
|
72
|
+
fireEvent.click(screen.getByTestId('filters+profession+trigger-button'));
|
|
73
|
+
|
|
74
|
+
filterOptionsMock
|
|
75
|
+
// We can't test everything because of the virtualizer
|
|
76
|
+
.slice(0, 6)
|
|
77
|
+
.forEach((option) => {
|
|
78
|
+
expect(
|
|
79
|
+
screen.getByTestId(`filters+profession_option_${option.id}`),
|
|
80
|
+
).toBeInTheDocument();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('dropdown', () => {
|
|
86
|
+
const onChangeSelectedOptions = jest.fn();
|
|
87
|
+
|
|
88
|
+
it('should select two profession options', () => {
|
|
89
|
+
const { rerender } = render(
|
|
90
|
+
<ProfessionFilter
|
|
91
|
+
{...baseProps}
|
|
92
|
+
options={filterOptionsMock}
|
|
93
|
+
onChangeSelectedOptions={onChangeSelectedOptions}
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
fireEvent.click(screen.getByTestId('filters+profession+trigger-button'));
|
|
98
|
+
fireEvent.click(
|
|
99
|
+
screen.getByTestId(
|
|
100
|
+
`filters+profession_${filterOptionsMock[0].id}_check_${filterOptionsMock[0].id}`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
fireEvent.click(
|
|
104
|
+
screen.getByTestId(
|
|
105
|
+
`filters+profession_${filterOptionsMock[2].id}_check_${filterOptionsMock[2].id}`,
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
// Dropdown needs to be closed for changes to propagate
|
|
109
|
+
fireEvent.click(screen.getByTestId('filters+profession+trigger-button'));
|
|
110
|
+
|
|
111
|
+
const selectedOptions = [filterOptionsMock[0], filterOptionsMock[2]];
|
|
112
|
+
const { dropdownValue, labels } = selectedOptions.reduce<{
|
|
113
|
+
dropdownValue: string[];
|
|
114
|
+
labels: string[];
|
|
115
|
+
}>(
|
|
116
|
+
(acc, { id, label }) => {
|
|
117
|
+
acc.dropdownValue.push(id);
|
|
118
|
+
acc.labels.push(label);
|
|
119
|
+
|
|
120
|
+
return acc;
|
|
121
|
+
},
|
|
122
|
+
{ labels: [], dropdownValue: [] },
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(onChangeSelectedOptions).toHaveBeenCalledWith(selectedOptions);
|
|
126
|
+
|
|
127
|
+
// New dropdownValue needs to loop back down from parent (usually a FilterGroup)
|
|
128
|
+
rerender(
|
|
129
|
+
<ProfessionFilter
|
|
130
|
+
{...baseProps}
|
|
131
|
+
options={filterOptionsMock}
|
|
132
|
+
dropdownValue={dropdownValue}
|
|
133
|
+
onChangeSelectedOptions={onChangeSelectedOptions}
|
|
134
|
+
/>,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(screen.getByTestId('filters+profession+trigger-button-title')).toHaveTextContent(
|
|
138
|
+
`scc-common-filters:filters.profession.placeholder: ${labels.join(', ')}`,
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should display default dropdown value without profession value (less or equal than min)', async () => {
|
|
143
|
+
const defaultValue = [filterOptionsMock[1]].map(({ id }) => id);
|
|
144
|
+
|
|
145
|
+
render(
|
|
146
|
+
<ProfessionFilter
|
|
147
|
+
{...baseProps}
|
|
148
|
+
dropdownValue={defaultValue}
|
|
149
|
+
options={filterOptionsMock}
|
|
150
|
+
onChangeSelectedOptions={onChangeSelectedOptions}
|
|
151
|
+
/>,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
expect(screen.getByTestId('filters+profession+trigger-button-title')).toHaveTextContent(
|
|
155
|
+
`scc-common-filters:filters.profession.placeholder: ${filterOptionsMock[1].label}`,
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('search input', () => {
|
|
161
|
+
it('should trigger handler only if threshold is crossed', async () => {
|
|
162
|
+
const onChangeSearchText = jest.fn();
|
|
163
|
+
const searchText = 'fake search';
|
|
164
|
+
|
|
165
|
+
render(<ProfessionFilter {...baseProps} onChangeSearchText={onChangeSearchText} />);
|
|
166
|
+
|
|
167
|
+
fireEvent.click(screen.getByTestId('filters+profession+trigger-button'));
|
|
168
|
+
fireEvent.change(screen.getByTestId('filters+profession+search_input'), {
|
|
169
|
+
target: { value: searchText.slice(0, baseProps.searchConfig.inputThreshold) },
|
|
170
|
+
});
|
|
171
|
+
await waitFor(() => {
|
|
172
|
+
expect(onChangeSearchText).toHaveBeenNthCalledWith(1, '');
|
|
173
|
+
});
|
|
174
|
+
fireEvent.change(screen.getByTestId('filters+profession+search_input'), {
|
|
175
|
+
target: { value: searchText },
|
|
176
|
+
});
|
|
177
|
+
await waitFor(() => {
|
|
178
|
+
expect(onChangeSearchText).toHaveBeenNthCalledWith(2, searchText);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('CTA', () => {
|
|
184
|
+
it('should click on cta button', () => {
|
|
185
|
+
const onDropdownCTAClick = jest.fn();
|
|
186
|
+
|
|
187
|
+
render(<ProfessionFilter {...baseProps} onDropdownCTAClick={onDropdownCTAClick} />);
|
|
188
|
+
|
|
189
|
+
fireEvent.click(screen.getByTestId('filters+profession+trigger-button'));
|
|
190
|
+
fireEvent.click(screen.getByTestId('filters+profession-bottom-action-button'));
|
|
191
|
+
|
|
192
|
+
expect(onDropdownCTAClick).toHaveBeenCalled();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ProfessionFilter';
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dropdown,
|
|
3
|
+
DropdownProps,
|
|
4
|
+
DropdownSelectorTrigger,
|
|
5
|
+
DropdownSelectorTriggerProps,
|
|
6
|
+
Input,
|
|
7
|
+
} from '@mistertemp/design-system';
|
|
8
|
+
import { InputTextProps } from '@mistertemp/design-system/src/components/design-system/Input/InputText';
|
|
9
|
+
import isEmpty from 'lodash/isEmpty';
|
|
10
|
+
import isString from 'lodash/isString';
|
|
11
|
+
import React, { useEffect, useState, VFC } from 'react';
|
|
12
|
+
|
|
13
|
+
import { useBundledTranslation } from '../../hooks';
|
|
14
|
+
import fr from '../../locales/fr/scc-common-filters.json';
|
|
15
|
+
import it from '../../locales/it/scc-common-filters.json';
|
|
16
|
+
import styles from './TextFilter.module.scss';
|
|
17
|
+
|
|
18
|
+
export type TextFilterProps = Omit<
|
|
19
|
+
DropdownProps<Record<string, string>>,
|
|
20
|
+
'id' | 'dropdownValue' | 'onDropdownValueChanged' | 'trigger' | 'withCTA' | 'ctaTitle' | 'size'
|
|
21
|
+
> & {
|
|
22
|
+
name: string;
|
|
23
|
+
size?: 'small' | 'medium';
|
|
24
|
+
placeholder: string;
|
|
25
|
+
dropdownValue?: string;
|
|
26
|
+
onDropdownValueChanged?: (value: string | undefined) => void;
|
|
27
|
+
selectorProps?: Omit<
|
|
28
|
+
DropdownSelectorTriggerProps,
|
|
29
|
+
'isSelected' | 'title' | 'data-testid' | 'disabled' | 'size'
|
|
30
|
+
>;
|
|
31
|
+
inputProps?: Omit<
|
|
32
|
+
InputTextProps,
|
|
33
|
+
| 'placeholder'
|
|
34
|
+
| 'data-testid'
|
|
35
|
+
| 'value'
|
|
36
|
+
| 'size'
|
|
37
|
+
| 'type'
|
|
38
|
+
| 'onSubmit'
|
|
39
|
+
| 'onChange'
|
|
40
|
+
| 'inputType'
|
|
41
|
+
>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const TextFilter: VFC<TextFilterProps> = ({
|
|
45
|
+
name,
|
|
46
|
+
placeholder,
|
|
47
|
+
dropdownValue,
|
|
48
|
+
onDropdownValueChanged,
|
|
49
|
+
'data-testid': dataTestId = `filters+${name}`,
|
|
50
|
+
onOpenChange,
|
|
51
|
+
selectorProps,
|
|
52
|
+
inputProps,
|
|
53
|
+
size = 'small',
|
|
54
|
+
...dropdownProps
|
|
55
|
+
}) => {
|
|
56
|
+
const { t } = useBundledTranslation('scc-common-filters', { fr, it });
|
|
57
|
+
|
|
58
|
+
const [inputValue, setInputValue] = useState('');
|
|
59
|
+
|
|
60
|
+
const triggerValueChanged = () => {
|
|
61
|
+
if (inputValue !== dropdownValue) {
|
|
62
|
+
onDropdownValueChanged?.(inputValue);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setInputValue(isString(dropdownValue) ? dropdownValue : '');
|
|
68
|
+
}, [dropdownValue]);
|
|
69
|
+
|
|
70
|
+
const isValidValue = isString(dropdownValue) && !isEmpty(dropdownValue);
|
|
71
|
+
|
|
72
|
+
const displayedValue = isValidValue ? `${placeholder} : ${dropdownValue}` : placeholder;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Dropdown
|
|
76
|
+
trigger={
|
|
77
|
+
<DropdownSelectorTrigger
|
|
78
|
+
data-testid={`${dataTestId}+trigger`}
|
|
79
|
+
title={displayedValue}
|
|
80
|
+
isSelected={isValidValue}
|
|
81
|
+
disabled={dropdownProps.disabled}
|
|
82
|
+
size={size}
|
|
83
|
+
{...selectorProps}
|
|
84
|
+
/>
|
|
85
|
+
}
|
|
86
|
+
data-testid={dataTestId}
|
|
87
|
+
dropdownValue={{ [name]: dropdownValue }}
|
|
88
|
+
onDropdownValueChanged={(value) => onDropdownValueChanged?.(value[name])}
|
|
89
|
+
withCTA
|
|
90
|
+
ctaTitle={t('scc-common-filters:actions.removeFilter')}
|
|
91
|
+
size={size}
|
|
92
|
+
onOpenChange={(isOpen) => {
|
|
93
|
+
onOpenChange?.(isOpen);
|
|
94
|
+
!isOpen && triggerValueChanged();
|
|
95
|
+
}}
|
|
96
|
+
{...dropdownProps}>
|
|
97
|
+
<div className={styles.inputContainer}>
|
|
98
|
+
<Input
|
|
99
|
+
data-testid={`${dataTestId}+input`}
|
|
100
|
+
value={inputValue}
|
|
101
|
+
size={size}
|
|
102
|
+
type="primary"
|
|
103
|
+
onSubmit={triggerValueChanged}
|
|
104
|
+
onChange={(e) => setInputValue(e.target.value ?? '')}
|
|
105
|
+
inputType="text"
|
|
106
|
+
withSubmitIcon={true}
|
|
107
|
+
{...inputProps}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
</Dropdown>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { fireEvent, screen } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { render } from '../../../../.jest/reactTestingLibrary.config';
|
|
5
|
+
import { TextFilter } from '../TextFilter';
|
|
6
|
+
|
|
7
|
+
describe('TextFilter', () => {
|
|
8
|
+
it('should have default value', () => {
|
|
9
|
+
render(<TextFilter name="test" placeholder="placeholder" dropdownValue="value1" />);
|
|
10
|
+
|
|
11
|
+
expect(screen.getByTestId('filters+test+trigger-button')).toHaveTextContent(
|
|
12
|
+
'placeholder : value1',
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
fireEvent.click(screen.getByTestId('filters+test+trigger-button'));
|
|
16
|
+
|
|
17
|
+
expect(screen.getByTestId('filters+test+input_input')).toHaveValue('value1');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should handle submit', () => {
|
|
21
|
+
const onChange = jest.fn();
|
|
22
|
+
|
|
23
|
+
render(
|
|
24
|
+
<TextFilter
|
|
25
|
+
name="test"
|
|
26
|
+
placeholder="placeholder"
|
|
27
|
+
dropdownValue="value1"
|
|
28
|
+
onDropdownValueChanged={onChange}
|
|
29
|
+
/>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
fireEvent.click(screen.getByTestId('filters+test+trigger-button'));
|
|
33
|
+
|
|
34
|
+
const input = screen.getByTestId('filters+test+input_input');
|
|
35
|
+
|
|
36
|
+
expect(input).toHaveValue('value1');
|
|
37
|
+
|
|
38
|
+
fireEvent.change(input, {
|
|
39
|
+
target: { value: 'value2' },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(input).toHaveValue('value2');
|
|
43
|
+
|
|
44
|
+
fireEvent.click(screen.getByTestId('filters+test+input_icon-button'));
|
|
45
|
+
|
|
46
|
+
expect(onChange).toHaveBeenLastCalledWith('value2');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should trigger change on close', () => {
|
|
50
|
+
const onChange = jest.fn();
|
|
51
|
+
|
|
52
|
+
render(
|
|
53
|
+
<TextFilter
|
|
54
|
+
name="test"
|
|
55
|
+
placeholder="placeholder"
|
|
56
|
+
dropdownValue="value1"
|
|
57
|
+
onDropdownValueChanged={onChange}
|
|
58
|
+
/>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const trigger = screen.getByTestId('filters+test+trigger-button');
|
|
62
|
+
|
|
63
|
+
fireEvent.click(trigger);
|
|
64
|
+
|
|
65
|
+
const input = screen.getByTestId('filters+test+input_input');
|
|
66
|
+
|
|
67
|
+
expect(input).toHaveValue('value1');
|
|
68
|
+
|
|
69
|
+
fireEvent.change(input, { target: { value: 'value2' } });
|
|
70
|
+
|
|
71
|
+
fireEvent.click(trigger);
|
|
72
|
+
|
|
73
|
+
expect(onChange).toHaveBeenLastCalledWith('value2');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TextFilter';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './ControlledInputDate';
|
|
2
|
+
export * from './ControlledInputTime';
|
|
3
|
+
export * from './CopyToClipboard';
|
|
4
|
+
export * from './DateFilter';
|
|
5
|
+
export * from './EnvironmentFilter';
|
|
6
|
+
export * from './Filter';
|
|
7
|
+
export * from './ProfessionFilter';
|
|
8
|
+
export * from './TextFilter';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { KeyPrefix, Namespace, TFunction } from 'i18next';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
export type UseBundedTranslationResponse<N extends Namespace, TKPrefix> = {
|
|
6
|
+
t: TFunction<N, TKPrefix> | ((key: string) => string);
|
|
7
|
+
isReady: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const useBundledTranslation = <
|
|
11
|
+
N extends Namespace,
|
|
12
|
+
TKPrefix extends KeyPrefix<N> = undefined,
|
|
13
|
+
>(
|
|
14
|
+
namespace: N | Readonly<N>,
|
|
15
|
+
translations: Record<string, unknown>,
|
|
16
|
+
): UseBundedTranslationResponse<N, TKPrefix> => {
|
|
17
|
+
const { i18n, t, ready: initReady } = useTranslation<N, TKPrefix>();
|
|
18
|
+
const [isBundleAdded, setIsBundleAdded] = useState(false);
|
|
19
|
+
|
|
20
|
+
const ready = initReady && isBundleAdded;
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (i18n.language) {
|
|
24
|
+
const lang = i18n.language.slice(0, 2);
|
|
25
|
+
|
|
26
|
+
if (!i18n.hasResourceBundle(lang, namespace as string) && lang in translations) {
|
|
27
|
+
i18n.addResourceBundle(lang, namespace as string, translations[lang]);
|
|
28
|
+
}
|
|
29
|
+
setIsBundleAdded(true);
|
|
30
|
+
}
|
|
31
|
+
}, [i18n, namespace, ready, translations]);
|
|
32
|
+
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
return { t: ready ? t : (key: any) => key, isReady: ready };
|
|
35
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useDebounce } from '@mistertemp/design-system';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { usePrevious } from './usePrevious';
|
|
5
|
+
|
|
6
|
+
export type DebouncedSearchConfig = {
|
|
7
|
+
debounceDelay?: number;
|
|
8
|
+
forceEmptyValue?: boolean;
|
|
9
|
+
inputThreshold?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useDebouncedSearchInput(
|
|
13
|
+
inputValue: string,
|
|
14
|
+
config?: DebouncedSearchConfig,
|
|
15
|
+
): string {
|
|
16
|
+
const debouncedInputValue = useDebounce(inputValue, config?.debounceDelay ?? 100);
|
|
17
|
+
const previousInputValue = usePrevious(debouncedInputValue);
|
|
18
|
+
|
|
19
|
+
const searchValue = useMemo(() => {
|
|
20
|
+
const threshold = config?.inputThreshold ?? 2;
|
|
21
|
+
|
|
22
|
+
if (debouncedInputValue.length > threshold) {
|
|
23
|
+
return debouncedInputValue;
|
|
24
|
+
} else if (
|
|
25
|
+
config?.forceEmptyValue &&
|
|
26
|
+
debouncedInputValue.length <= threshold &&
|
|
27
|
+
(previousInputValue === undefined || previousInputValue.length > threshold)
|
|
28
|
+
) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return previousInputValue;
|
|
33
|
+
}, [debouncedInputValue, previousInputValue, config?.inputThreshold, config?.forceEmptyValue]);
|
|
34
|
+
|
|
35
|
+
return searchValue ?? '';
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export const usePrevious = <T>(value: T) => {
|
|
4
|
+
const currentRef = useRef<T>(value);
|
|
5
|
+
const previousRef = useRef<T>();
|
|
6
|
+
|
|
7
|
+
if (currentRef.current !== value) {
|
|
8
|
+
previousRef.current = currentRef.current;
|
|
9
|
+
currentRef.current = value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return previousRef.current;
|
|
13
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"filters": {
|
|
3
|
+
"profession": {
|
|
4
|
+
"label": "Qualification",
|
|
5
|
+
"placeholder": "Qualification",
|
|
6
|
+
"search": {
|
|
7
|
+
"placeholder": "Rechercher une qualification"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"environment": {
|
|
11
|
+
"placeholder": "Environment"
|
|
12
|
+
},
|
|
13
|
+
"date": {
|
|
14
|
+
"placeholder": "Date"
|
|
15
|
+
},
|
|
16
|
+
"noResult": "Pas de résultat"
|
|
17
|
+
},
|
|
18
|
+
"actions": {
|
|
19
|
+
"removeFilter": "Supprimer le filtre",
|
|
20
|
+
"resetFilter": "Réinitialiser le filtre"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"filters": {
|
|
3
|
+
"profession": {
|
|
4
|
+
"label": "Qualificazione",
|
|
5
|
+
"placeholder": "Qualificazione",
|
|
6
|
+
"search": {
|
|
7
|
+
"placeholder": "Rechercher una qualificazione"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"environment": {
|
|
11
|
+
"placeholder": "Ambiente"
|
|
12
|
+
},
|
|
13
|
+
"date": {
|
|
14
|
+
"placeholder": "Data"
|
|
15
|
+
},
|
|
16
|
+
"noResult": "Nessun risultato"
|
|
17
|
+
},
|
|
18
|
+
"actions": {
|
|
19
|
+
"removeFilter": "Rimuovi il filtro",
|
|
20
|
+
"resetFilter": "Reimposta filtro"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FilterDropdownOption } from '../components';
|
|
2
|
+
|
|
3
|
+
export const filterOptionsMock: FilterDropdownOption[] = [
|
|
4
|
+
{ id: '00270025-6f9e-4e98-83da-4e7c9409653a', label: 'Webmaster' },
|
|
5
|
+
{ id: '21960b88-c606-4daa-b100-1fbc88834f7d', label: 'Carreleur' },
|
|
6
|
+
{ id: '006ff3e2-3ad7-4180-bba1-c3e19c847bf2', label: 'Jointeur' },
|
|
7
|
+
{ id: '0e5482de-f123-45b2-b2d7-41ba0d85300c', label: 'Valet de chambre' },
|
|
8
|
+
{ id: '126db8fc-27c5-4aaf-8d7e-75e23a4b2f59', label: 'Chocolatier' },
|
|
9
|
+
{ id: '13cfe3f8-717f-4609-aa42-76f8896f5e69', label: 'Charpentier industriel' },
|
|
10
|
+
{ id: '092f2514-de90-4ce4-934d-eb71453325e0', label: 'Dessinateur en construction métallique' },
|
|
11
|
+
{ id: '0974d4f2-e245-4e77-ac67-47f10888baed', label: 'Maroquinier industriel' },
|
|
12
|
+
{ id: '0f37e3e0-f4f8-40d9-a27b-fdf41e371fb8', label: 'Règleur de machine de production' },
|
|
13
|
+
{ id: '0faf2779-fb6d-4648-90f6-427ce10ff19f', label: 'Maraîcher' },
|
|
14
|
+
{
|
|
15
|
+
id: '117124f1-4699-44dd-8e8a-4e7a48445c47',
|
|
16
|
+
label: "Gestionnaire de l'administration du personnel",
|
|
17
|
+
},
|
|
18
|
+
{ id: '0e4e9f86-eb76-4897-ae67-abc1b972b8e7', label: "Chargé d'affaires en industrie" },
|
|
19
|
+
{ id: '10fbc798-bae7-4f57-bac1-ce6ea4d026f9', label: 'Conducteur de tombereau' },
|
|
20
|
+
{ id: '14c12ca9-2a5e-4294-8f47-17fe03305754', label: 'Conducteur PL' },
|
|
21
|
+
{ id: '156121c5-e766-4de7-af79-4b1858d482e2', label: 'Gardien-Conciergerie' },
|
|
22
|
+
{ id: '0804eb04-21df-4fda-a2be-a7a470e7e9cb', label: 'Masseur kinésithérapeute' },
|
|
23
|
+
{ id: '09f1f51c-9cc1-48bb-95f7-ae693eb76103', label: 'Vendeur en prêt-à-porter' },
|
|
24
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './filters';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["./src", "./.jest", "./demo", "index.d.ts", "index.ts"],
|
|
3
|
+
"exclude": ["node_modules", ".jest"],
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"useDefineForClassFields": true,
|
|
7
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
8
|
+
"allowJs": false,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"allowSyntheticDefaultImports": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"strictNullChecks": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"module": "ESNext",
|
|
16
|
+
"moduleResolution": "Node",
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"isolatedModules": true,
|
|
19
|
+
"noEmit": true,
|
|
20
|
+
"jsx": "react-jsx",
|
|
21
|
+
"noImplicitAny": true,
|
|
22
|
+
"types": ["node", "jest"],
|
|
23
|
+
"typeRoots": ["./node_modules/@types"]
|
|
24
|
+
}
|
|
25
|
+
}
|