@jasperoosthoek/react-toolbox 0.8.1 → 0.9.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.
Files changed (63) hide show
  1. package/change-log.md +330 -312
  2. package/dist/components/buttons/ConfirmButton.d.ts +2 -2
  3. package/dist/components/buttons/DeleteConfirmButton.d.ts +2 -2
  4. package/dist/components/buttons/IconButtons.d.ts +40 -41
  5. package/dist/components/errors/Errors.d.ts +1 -2
  6. package/dist/components/forms/FormField.d.ts +22 -0
  7. package/dist/components/forms/FormFields.d.ts +1 -56
  8. package/dist/components/forms/FormModal.d.ts +7 -34
  9. package/dist/components/forms/FormModalProvider.d.ts +25 -15
  10. package/dist/components/forms/FormProvider.d.ts +66 -0
  11. package/dist/components/forms/fields/FormBadgesSelection.d.ts +26 -0
  12. package/dist/components/forms/fields/FormCheckbox.d.ts +7 -0
  13. package/dist/components/forms/fields/FormDropdown.d.ts +19 -0
  14. package/dist/components/forms/fields/FormInput.d.ts +17 -0
  15. package/dist/components/forms/fields/FormSelect.d.ts +12 -0
  16. package/dist/components/forms/fields/index.d.ts +5 -0
  17. package/dist/components/indicators/CheckIndicator.d.ts +1 -2
  18. package/dist/components/indicators/LoadingIndicator.d.ts +4 -4
  19. package/dist/components/login/LoginPage.d.ts +1 -1
  20. package/dist/components/tables/DataTable.d.ts +2 -2
  21. package/dist/components/tables/DragAndDropList.d.ts +2 -2
  22. package/dist/components/tables/SearchBox.d.ts +2 -2
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +2 -2
  25. package/dist/index.js.LICENSE.txt +0 -4
  26. package/dist/localization/LocalizationContext.d.ts +1 -1
  27. package/dist/utils/hooks.d.ts +1 -1
  28. package/dist/utils/timeAndDate.d.ts +5 -2
  29. package/dist/utils/utils.d.ts +3 -3
  30. package/package.json +10 -11
  31. package/src/__tests__/buttons.test.tsx +545 -0
  32. package/src/__tests__/errors.test.tsx +339 -0
  33. package/src/__tests__/forms.test.tsx +3021 -0
  34. package/src/__tests__/hooks.test.tsx +413 -0
  35. package/src/__tests__/indicators.test.tsx +284 -0
  36. package/src/__tests__/localization.test.tsx +462 -0
  37. package/src/__tests__/login.test.tsx +417 -0
  38. package/src/__tests__/setupTests.ts +328 -0
  39. package/src/__tests__/tables.test.tsx +609 -0
  40. package/src/__tests__/timeAndDate.test.tsx +308 -0
  41. package/src/__tests__/utils.test.tsx +422 -0
  42. package/src/components/forms/FormField.tsx +92 -0
  43. package/src/components/forms/FormFields.tsx +3 -423
  44. package/src/components/forms/FormModal.tsx +168 -243
  45. package/src/components/forms/FormModalProvider.tsx +164 -85
  46. package/src/components/forms/FormProvider.tsx +218 -0
  47. package/src/components/forms/fields/FormBadgesSelection.tsx +108 -0
  48. package/src/components/forms/fields/FormCheckbox.tsx +76 -0
  49. package/src/components/forms/fields/FormDropdown.tsx +123 -0
  50. package/src/components/forms/fields/FormInput.tsx +114 -0
  51. package/src/components/forms/fields/FormSelect.tsx +47 -0
  52. package/src/components/forms/fields/index.ts +6 -0
  53. package/src/index.ts +32 -29
  54. package/src/localization/LocalizationContext.tsx +156 -131
  55. package/src/localization/localization.ts +131 -131
  56. package/src/utils/hooks.ts +108 -94
  57. package/src/utils/timeAndDate.ts +33 -4
  58. package/src/utils/utils.ts +74 -66
  59. package/dist/components/forms/CreateEditModal.d.ts +0 -41
  60. package/dist/components/forms/CreateEditModalProvider.d.ts +0 -41
  61. package/dist/components/forms/FormFields.test.d.ts +0 -4
  62. package/dist/login/Login.d.ts +0 -70
  63. package/src/components/forms/FormFields.test.tsx +0 -107
@@ -1,92 +1,101 @@
1
1
  import React, { useState, useContext, ReactNode } from 'react';
2
-
3
2
  import { useLocalization } from '../../localization/LocalizationContext';
4
- import {
5
- FormFields,
6
- IncludeData,
7
- InitialState,
8
- OnSave,
9
- Validate,
10
- ModalTitle,
11
- Width,
12
- FormModal,
13
-
14
- } from './FormModal';
3
+ import { FormProvider, FormFields, InitialState, OnSubmit, Validate } from './FormProvider';
4
+ import { FormModal, ModalTitle, Width } from './FormModal';
15
5
  import { FormValue } from './FormFields';
16
6
  import { ButtonProps, CreateButton, EditButton } from '../buttons/IconButtons';
17
7
 
8
+ // Add the IncludeData type from master version
9
+ export type IncludeData<T extends FormFields> = {
10
+ [key in Exclude<string, keyof T>]: any;
11
+ }
12
+
18
13
  export type ShowCreateModal = (show?: boolean) => void;
19
- export type ShowEditModal<T, K> = (state: { [key in keyof T]: FormValue } & K) => void;
14
+ export type ShowEditModal<T, K> = (state: ({ [key in keyof T]: FormValue } & K) | null) => void;
15
+
16
+ type FormModalContextType<T, K> = {
17
+ showCreateModal: ShowCreateModal;
18
+ showEditModal: ShowEditModal<T, K>;
19
+ hasProvider: boolean;
20
+ }
21
+
22
+ type T = any;
23
+ type K = any;
24
+ const defaultErrorState: FormModalContextType<T, K> = {
25
+ showCreateModal: () => {
26
+ console.error('The showCreateModal function should only be used in a child of the FormModalProvider component.');
27
+ },
28
+ showEditModal: (state: ({ [key in keyof T]: FormValue } & K) | null) => {
29
+ console.error('The showEditModal function should only be used in a child of the FormModalProvider component.');
30
+ },
31
+ hasProvider: false,
32
+ };
33
+
34
+ export const FormModalContext = React.createContext(defaultErrorState);
35
+
36
+ export const useFormModal = () => useContext(FormModalContext);
20
37
 
21
38
  export type FormCreateModalButton = ButtonProps;
22
39
  export const FormCreateModalButton = ({ onClick, ...props }: ButtonProps) => {
23
- const { showCreateModal, hasProvider } = useFormModal();
24
-
40
+ const context = useContext(FormModalContext);
41
+
25
42
  return (
26
43
  <CreateButton
27
44
  {...props}
28
45
  onClick={(e) => {
29
46
  // A onClick function was given to CreateButton
30
47
  if (onClick) onClick(e);
31
- // CreateButton is inside a CreateEditButtonProvider which can handle
32
- // showCreateModal. Without the provider, showCreateModal will log an error
33
- if (hasProvider) showCreateModal();
48
+
49
+ // Check if we have a provider context
50
+ if (!context || !context.hasProvider) {
51
+ console.error('The showCreateModal function should only be used in a child of the FormModalProvider component.');
52
+ return;
53
+ }
54
+
55
+ // CreateButton is inside a FormModalProvider which can handle showCreateModal
56
+ context.showCreateModal();
34
57
  }}
35
58
  />
36
59
  )
37
60
  }
61
+
38
62
  export interface FormEditModalButtonProps<T, K> extends ButtonProps {
39
63
  state: { [key in keyof T]: FormValue } & K;
40
64
  }
41
- export const FormEditModalButton = ({ state, onClick, ... props }: FormEditModalButtonProps<T, K>) => {
42
- const { showEditModal, hasProvider } = useFormModal();
43
65
 
66
+ export const FormEditModalButton = ({ state, onClick, ...props }: FormEditModalButtonProps<T, K>) => {
67
+ const context = useContext(FormModalContext);
68
+
44
69
  return (
45
70
  <EditButton
46
71
  {...props}
47
72
  onClick={(e) => {
48
- // A onClick function was given to CreateButton
73
+ // A onClick function was given to EditButton
49
74
  if (onClick) onClick(e);
50
- // CreateButton is inside a CreateEditButtonProvider which can handle
51
- // showEditModal. Without the provider, showEditModal will log an error
52
- if (hasProvider) showEditModal(state);
75
+
76
+ // Check if we have a provider context
77
+ if (!context || !context.hasProvider) {
78
+ console.error('The showEditModal function should only be used in a child of the FormModalProvider component.');
79
+ return;
80
+ }
81
+
82
+ // EditButton is inside a FormModalProvider which can handle showEditModal
83
+ context.showEditModal(state);
53
84
  }}
54
85
  />
55
86
  )
56
87
  }
57
88
 
58
- type FormModalContextType<T, K> = {
59
- showCreateModal: ShowCreateModal;
60
- showEditModal: ShowEditModal<T, K>;
61
- hasProvider: boolean;
62
- }
63
-
64
- type T = any;
65
- type K = any;
66
- const defaultErrorState: FormModalContextType<T, K> = {
67
- showCreateModal: () => {
68
- console.error('The showCreateModal function should only be used in a child of the FormModalProvider component.');
69
- },
70
- showEditModal: (state: { [key in keyof T]: FormValue } & K) => {
71
- console.error('The showEditModal function should only be used in a child of the FormModalProvider component.');
72
- },
73
- hasProvider: false,
74
- };
75
- export const FormModalContext = React.createContext(defaultErrorState);
76
-
77
- export const useFormModal = () => useContext(FormModalContext);
78
-
79
-
80
89
  export type FormModalProviderProps<
81
90
  T extends FormFields,
82
91
  K extends IncludeData<T>
83
92
  > = {
93
+ formFields: T;
84
94
  initialState: InitialState<T> | K;
85
95
  includeData?: K;
86
- formFields: T;
87
- onSave?: OnSave<T, K>;
88
- onCreate?: OnSave<T, K>;
89
- onUpdate?: OnSave<T, K>;
96
+ onSave?: (state: { [key in keyof T]: FormValue } & K, callback?: () => void) => void;
97
+ onCreate?: (state: { [key in keyof T]: FormValue } & K, callback?: () => void) => void;
98
+ onUpdate?: (state: { [key in keyof T]: FormValue } & K, callback?: () => void) => void;
90
99
  validate?: Validate;
91
100
  createModalTitle?: ModalTitle;
92
101
  editModalTitle?: ModalTitle;
@@ -95,58 +104,128 @@ export type FormModalProviderProps<
95
104
  width?: Width;
96
105
  children: ReactNode;
97
106
  }
107
+
98
108
  export const FormModalProvider: React.FC<FormModalProviderProps<T, K>> = ({
99
- createModalTitle,
100
- editModalTitle,
101
- formFields,
109
+ createModalTitle,
110
+ editModalTitle,
111
+ formFields,
102
112
  initialState,
113
+ includeData,
103
114
  validate,
104
- loading,
105
- onCreate,
106
- onUpdate,
115
+ loading = false,
116
+ onCreate,
117
+ onUpdate,
107
118
  onSave,
108
119
  dialogClassName,
109
- children,
120
+ width,
121
+ children,
110
122
  }) => {
111
- const [createModalActive, showCreateModal] = useState(false);
112
- const [instanceInEditModal, showEditModal] = useState<({ [key in keyof T]: FormValue } & K) | null>(null);
123
+ const [createModalActive, setCreateModalActive] = useState(false);
124
+ const [editModalState, setEditModalState] = useState<({ [key in keyof T]: FormValue } & K) | null>(null);
113
125
  const { strings } = useLocalization();
126
+
127
+ // Provide default values
128
+ const safeIncludeData = (includeData || {}) as K;
129
+
130
+ const handleCreateSubmit: OnSubmit<T> = (state, callback) => {
131
+ // Priority: onCreate > onSave fallback
132
+ const submitHandler = onCreate || onSave;
133
+ if (submitHandler) {
134
+ // Merge form state with includeData
135
+ submitHandler(
136
+ {
137
+ ...state,
138
+ ...safeIncludeData,
139
+ } as { [key in keyof T]: FormValue } & K,
140
+ () => {
141
+ setCreateModalActive(false);
142
+ if (callback) callback();
143
+ }
144
+ );
145
+ } else {
146
+ // No handler provided, just close modal
147
+ setCreateModalActive(false);
148
+ if (callback) callback();
149
+ }
150
+ };
151
+
152
+ const handleEditSubmit: OnSubmit<T> = (state, callback) => {
153
+ // Priority: onUpdate > onSave fallback
154
+ const submitHandler = onUpdate || onSave;
155
+ if (submitHandler) {
156
+ // Merge form state with includeData
157
+ submitHandler(
158
+ {
159
+ ...state,
160
+ ...safeIncludeData,
161
+ } as { [key in keyof T]: FormValue } & K,
162
+ () => {
163
+ setEditModalState(null);
164
+ if (callback) callback();
165
+ }
166
+ );
167
+ } else {
168
+ // No handler provided, just close modal
169
+ setEditModalState(null);
170
+ if (callback) callback();
171
+ }
172
+ };
173
+
174
+ // Convert entity state to form initial state
175
+ const getFormInitialState = (entityState: ({ [key in keyof T]: FormValue } & K) | null): InitialState<T> => {
176
+ if (!entityState) {
177
+ return initialState as InitialState<T>;
178
+ }
179
+ return entityState as unknown as InitialState<T>;
180
+ };
181
+
114
182
  return (
115
183
  <FormModalContext.Provider
116
184
  value={{
117
- showCreateModal: (show?: boolean) =>
118
- showCreateModal(typeof show === 'undefined' ? true : show),
119
- showEditModal,
185
+ showCreateModal: (show?: boolean) =>
186
+ setCreateModalActive(typeof show === 'undefined' ? true : show),
187
+ showEditModal: (state: ({ [key in keyof T]: FormValue } & K) | null) => setEditModalState(state),
120
188
  hasProvider: true,
121
- }}>
189
+ }}
190
+ >
122
191
  {children}
123
-
192
+
124
193
  {createModalActive && (onCreate || onSave) && (
125
- <FormModal
126
- modalTitle={createModalTitle || strings.getString('modal_create')}
127
- onHide={() => showCreateModal(false)}
128
- initialState={initialState}
129
- formFields={formFields}
194
+ <FormProvider
195
+ formFields={formFields}
196
+ initialState={initialState as InitialState<T>}
197
+ onSubmit={handleCreateSubmit}
130
198
  validate={validate}
131
- loading={loading}
132
- // @ts-ignore Ignore as Typescript does not recognize that this is allowed
133
- onSave={onCreate || onSave}
134
- dialogClassName={dialogClassName}
135
- />
136
- )}
137
- {instanceInEditModal && (onUpdate || onSave) && (
138
- <FormModal
139
- show={!!instanceInEditModal}
140
- modalTitle={editModalTitle || strings.getString('modal_edit')}
141
- onHide={() => showEditModal(null)}
142
- initialState={instanceInEditModal}
199
+ loading={loading}
200
+ resetTrigger={createModalActive}
201
+ >
202
+ <FormModal
203
+ show={createModalActive}
204
+ modalTitle={createModalTitle || strings.getString('modal_create')}
205
+ onHide={() => setCreateModalActive(false)}
206
+ dialogClassName={dialogClassName}
207
+ width={width}
208
+ />
209
+ </FormProvider>
210
+ )}
211
+
212
+ {editModalState && (onUpdate || onSave) && (
213
+ <FormProvider
143
214
  formFields={formFields}
215
+ initialState={getFormInitialState(editModalState)}
216
+ onSubmit={handleEditSubmit}
144
217
  validate={validate}
145
218
  loading={loading}
146
- // @ts-ignore
147
- onSave={onUpdate || onSave}
148
- dialogClassName={dialogClassName}
149
- />
219
+ resetTrigger={editModalState}
220
+ >
221
+ <FormModal
222
+ show={!!editModalState}
223
+ modalTitle={editModalTitle || strings.getString('modal_edit')}
224
+ onHide={() => setEditModalState(null)}
225
+ dialogClassName={dialogClassName}
226
+ width={width}
227
+ />
228
+ </FormProvider>
150
229
  )}
151
230
  </FormModalContext.Provider>
152
231
  );
@@ -0,0 +1,218 @@
1
+ import React, { useEffect, useState, useContext, ReactNode, createContext } from 'react';
2
+ import { useSetState, usePrevious } from '../../utils/hooks';
3
+ import { isEmpty } from '../../utils/utils';
4
+ import { useLocalization } from '../../localization/LocalizationContext';
5
+ import { FormValue } from './FormFields';
6
+
7
+ export type FormFieldConfig = {
8
+ initialValue?: any;
9
+ type?: 'string' | 'number' | 'select' | 'checkbox' | 'boolean' | 'textarea' | 'dropdown';
10
+ required?: boolean;
11
+ formProps?: any;
12
+ component?: any;
13
+ onChange?: (value: FormValue, formData?: any) => any;
14
+ label?: React.ReactElement | string;
15
+ options?: Array<{ value: string | number; label: string; disabled?: boolean }>; // For select fields
16
+ list?: any[]; // For dropdown fields
17
+ idKey?: string; // For dropdown fields
18
+ nameKey?: string; // For dropdown fields
19
+ }
20
+
21
+ export type FormFields = { [key: string]: FormFieldConfig };
22
+
23
+ export type InitialState<T> = Partial<{
24
+ [key in keyof T]: FormValue;
25
+ }>
26
+
27
+ export type OnSubmit<T> = (state: { [key in keyof T]: FormValue }, callback?: () => void) => void;
28
+
29
+ export type Validate = (state: any) => any;
30
+
31
+ type FormContextType<T extends FormFields> = {
32
+ formFields: T;
33
+ formData: { [key in keyof T]: FormValue } | null;
34
+ initialFormData: { [key in keyof T]: FormValue } | null;
35
+ pristine: boolean;
36
+ validated: boolean;
37
+ validationErrors: { [key: string]: any };
38
+ loading: boolean;
39
+ getValue: (key: string) => FormValue;
40
+ setValue: (key: string, value: FormValue) => void;
41
+ setFormData: (data: Partial<{ [key in keyof T]: FormValue }>) => void;
42
+ resetForm: () => void;
43
+ submit: () => void;
44
+ setPristine: (pristine: boolean) => void;
45
+ setLoading: (loading: boolean) => void;
46
+ hasProvider: boolean;
47
+ }
48
+
49
+ const defaultFormState: FormContextType<any> = {
50
+ formFields: {},
51
+ formData: null,
52
+ initialFormData: null,
53
+ pristine: true,
54
+ validated: false,
55
+ validationErrors: {},
56
+ loading: false,
57
+ getValue: () => '',
58
+ setValue: () => console.error('setValue should only be used within a FormProvider'),
59
+ setFormData: () => console.error('setFormData should only be used within a FormProvider'),
60
+ resetForm: () => console.error('resetForm should only be used within a FormProvider'),
61
+ submit: () => console.error('submit should only be used within a FormProvider'),
62
+ setPristine: () => console.error('setPristine should only be used within a FormProvider'),
63
+ setLoading: () => console.error('setLoading should only be used within a FormProvider'),
64
+ hasProvider: false,
65
+ };
66
+
67
+ const FormContext = createContext<FormContextType<any>>(defaultFormState);
68
+
69
+ export const useForm = <T extends FormFields>() => useContext(FormContext) as FormContextType<T>;
70
+
71
+ export type FormProviderProps<T extends FormFields> = {
72
+ formFields: T;
73
+ initialState?: InitialState<T>;
74
+ onSubmit: OnSubmit<T>;
75
+ validate?: Validate;
76
+ loading?: boolean;
77
+ children: ReactNode;
78
+ resetTrigger?: any; // When this value changes, the form will reset
79
+ }
80
+
81
+ export const FormProvider = <T extends FormFields>({
82
+ formFields,
83
+ initialState = {} as InitialState<T>,
84
+ onSubmit,
85
+ validate,
86
+ loading = false,
87
+ children,
88
+ resetTrigger,
89
+ }: FormProviderProps<T>) => {
90
+ const { strings } = useLocalization();
91
+
92
+ if (!formFields) {
93
+ console.error(`Property formFields cannot be empty.`);
94
+ return null;
95
+ }
96
+
97
+ const getInitialFormData = () => ({
98
+ ...Object.entries(formFields).reduce((o, [key, { initialValue }]) => ({ ...o, [key]: initialValue || '' }), {}),
99
+ ...initialState || {},
100
+ }) as { [key in keyof T]: FormValue };
101
+
102
+ const [initialFormData, setInitialFormData] = useState<{ [key in keyof T]: FormValue }>(getInitialFormData());
103
+
104
+ const [{ pristine, formData, internalLoading }, setState] = useSetState({
105
+ pristine: true,
106
+ formData: initialFormData,
107
+ internalLoading: false,
108
+ });
109
+
110
+ const prevResetTrigger = usePrevious(resetTrigger);
111
+
112
+ useEffect(() => {
113
+ if (resetTrigger !== undefined && prevResetTrigger !== resetTrigger) {
114
+ const newInitialFormData = getInitialFormData();
115
+ setInitialFormData(newInitialFormData);
116
+ setState({
117
+ pristine: true,
118
+ formData: newInitialFormData,
119
+ });
120
+ }
121
+ }, [resetTrigger, prevResetTrigger]);
122
+
123
+ const getValue = (key: string): FormValue => {
124
+ const value = formData[key];
125
+ if (value !== undefined && value !== null) {
126
+ return value;
127
+ }
128
+ return (formFields[key] || {}).type === 'number' ? '0' : '';
129
+ };
130
+
131
+ const setValue = (key: string, value: FormValue) => {
132
+ const fieldOnChange = formFields[key]?.onChange;
133
+ const newFormData = typeof fieldOnChange === 'function'
134
+ ? { ...formData, ...fieldOnChange(value, formData) }
135
+ : { ...formData, [key]: value };
136
+
137
+ setState({ formData: newFormData });
138
+ };
139
+
140
+ const setFormData = (data: Partial<{ [key in keyof T]: FormValue }>) => {
141
+ setState({ formData: { ...formData, ...data } });
142
+ };
143
+
144
+ const resetForm = () => {
145
+ const newInitialFormData = getInitialFormData();
146
+ setInitialFormData(newInitialFormData);
147
+ setState({
148
+ pristine: true,
149
+ formData: newInitialFormData,
150
+ });
151
+ };
152
+
153
+ const validationErrors = {
154
+ ...validate
155
+ ? Object.entries(validate(formData) || {})
156
+ .reduce(
157
+ (o, [key, val]) => {
158
+ // Remove all empty elements
159
+ if (isEmpty(val)) return o;
160
+ return { ...o, [key]: val };
161
+ },
162
+ {}
163
+ )
164
+ : {},
165
+ ...Object.keys(formData).reduce(
166
+ (o, key) => {
167
+ if (!formFields[key] || !formFields[key].required || !isEmpty(getValue(key))) return o;
168
+ return { ...o, [key]: strings.getString('required_field') };
169
+ },
170
+ {}
171
+ ),
172
+ } as { [key: string]: any };
173
+
174
+ const validated = Object.values(validationErrors).length === 0;
175
+
176
+ const submit = () => {
177
+ setState({ pristine: false });
178
+ if (!validated) return;
179
+
180
+ setState({ internalLoading: true });
181
+ onSubmit(
182
+ formData,
183
+ () => setState({ internalLoading: false })
184
+ );
185
+ };
186
+
187
+ const setPristine = (pristine: boolean) => {
188
+ setState({ pristine });
189
+ };
190
+
191
+ const setLoading = (loading: boolean) => {
192
+ setState({ internalLoading: loading });
193
+ };
194
+
195
+ const contextValue: FormContextType<T> = {
196
+ formFields,
197
+ formData,
198
+ initialFormData,
199
+ pristine,
200
+ validated,
201
+ validationErrors,
202
+ loading: loading || internalLoading,
203
+ getValue,
204
+ setValue,
205
+ setFormData,
206
+ resetForm,
207
+ submit,
208
+ setPristine,
209
+ setLoading,
210
+ hasProvider: true,
211
+ };
212
+
213
+ return (
214
+ <FormContext.Provider value={contextValue}>
215
+ {children}
216
+ </FormContext.Provider>
217
+ );
218
+ };
@@ -0,0 +1,108 @@
1
+ import React from 'react';
2
+ import { Form, Badge } from 'react-bootstrap';
3
+ import { BadgeProps } from 'react-bootstrap';
4
+ import { useFormField } from '../FormField';
5
+
6
+ export interface BadgeSelectionProps extends BadgeProps {
7
+ selected: boolean;
8
+ cursor: string;
9
+ disabled?: boolean;
10
+ }
11
+
12
+ export const BadgeSelection = ({ selected = true, disabled, cursor, onClick, style, bg, ...restProps }: BadgeSelectionProps) => (
13
+ <Badge
14
+ bg={bg || (selected ? 'primary' : 'secondary')}
15
+ style={{
16
+ userSelect: 'none',
17
+ ...cursor ? { cursor } : {},
18
+ ...style || {},
19
+ }}
20
+ {...disabled ? {} : { onClick }}
21
+ {...restProps}
22
+ />
23
+ )
24
+
25
+ type DisabledProps = {
26
+ list: any[];
27
+ value: string | number;
28
+ state: any;
29
+ initialState: any;
30
+ initialValue: any;
31
+ }
32
+
33
+ export interface FormBadgesSelectionProps extends Omit<
34
+ React.InputHTMLAttributes<HTMLInputElement>,
35
+ 'name' | 'value' | 'onChange' | 'disabled' | 'list'
36
+ > {
37
+ name: string;
38
+ label?: React.ReactElement | string;
39
+ list: any[];
40
+ idKey?: string;
41
+ multiple?: boolean;
42
+ integer?: boolean;
43
+ disabled?: boolean | ((props: DisabledProps) => boolean);
44
+ }
45
+
46
+ export const FormBadgesSelection = (props: FormBadgesSelectionProps) => {
47
+ const {
48
+ list,
49
+ idKey = 'id',
50
+ multiple,
51
+ integer,
52
+ disabled,
53
+ ...componentProps
54
+ } = props;
55
+
56
+ const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(componentProps);
57
+
58
+ const isMultiple = multiple || multiple === false ? multiple : value instanceof Array;
59
+ const parseInteger = (value: string | number): string | number => integer ? parseInt(`${value}`) : `${value}`;
60
+
61
+ return (
62
+ <Form.Group controlId={props.name}>
63
+ {label && <Form.Label>{label}{required && ' *'}</Form.Label>}
64
+ {isInvalid && error && (
65
+ <Form.Text className="text-danger">
66
+ {error}
67
+ </Form.Text>
68
+ )}
69
+ <div className={`form-control ${isInvalid ? 'is-invalid' : ''}`}>
70
+ {list.map((item: any, key) => {
71
+ const selected = isMultiple
72
+ ? !!value && Array.isArray(value) && (value as Array<any>).includes(item[idKey])
73
+ : value === item[idKey];
74
+ return (
75
+ <BadgeSelection
76
+ key={key}
77
+ disabled={
78
+ typeof disabled === 'function'
79
+ ? disabled({ list, value: item[idKey], state: {}, initialState: {}, initialValue: '' })
80
+ : disabled
81
+ }
82
+ selected={selected}
83
+ cursor='pointer'
84
+ onClick={() => {
85
+ if (Array.isArray(value)) {
86
+ if(selected) {
87
+ onChange(value.filter(id => parseInteger(id) !== parseInteger(item[idKey])).toString());
88
+ } else {
89
+ onChange(
90
+ integer
91
+ ? [...value.map((v: string | number ) => parseInt(v as string)), parseInt(item[idKey])]
92
+ : [...value.map((v: string | number ) => `${v}`), `${item[idKey]}`]
93
+ );
94
+ }
95
+ } else {
96
+ onChange(item[idKey])
97
+ }
98
+ }}
99
+ {...mergedProps}
100
+ >
101
+ {item.name}
102
+ </BadgeSelection>
103
+ );
104
+ })}
105
+ </div>
106
+ </Form.Group>
107
+ );
108
+ }