@jasperoosthoek/react-toolbox 0.8.0 → 0.9.0

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 -309
  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 +19 -26
  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 +4 -1
  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 +141 -95
  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 -28
  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,90 @@
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
 
18
8
  export type ShowCreateModal = (show?: boolean) => void;
19
- export type ShowEditModal<T, K> = (state: { [key in keyof T]: FormValue } & K) => void;
9
+ export type ShowEditModal<T extends FormFields> = (state: InitialState<T> | null) => void;
10
+
11
+ type FormModalContextType<T extends FormFields> = {
12
+ showCreateModal: ShowCreateModal;
13
+ showEditModal: ShowEditModal<T>;
14
+ hasProvider: boolean;
15
+ }
16
+
17
+ const defaultErrorState: FormModalContextType<any> = {
18
+ showCreateModal: () => {
19
+ console.error('The showCreateModal function should only be used in a child of the FormModalProvider component.');
20
+ },
21
+ showEditModal: () => {
22
+ console.error('The showEditModal function should only be used in a child of the FormModalProvider component.');
23
+ },
24
+ hasProvider: false,
25
+ };
26
+
27
+ export const FormModalContext = React.createContext(defaultErrorState);
28
+
29
+ export const useFormModal = <T extends FormFields>() => useContext(FormModalContext) as FormModalContextType<T>;
20
30
 
21
31
  export type FormCreateModalButton = ButtonProps;
22
32
  export const FormCreateModalButton = ({ onClick, ...props }: ButtonProps) => {
23
- const { showCreateModal, hasProvider } = useFormModal();
24
-
33
+ const context = useContext(FormModalContext);
34
+
25
35
  return (
26
36
  <CreateButton
27
37
  {...props}
28
38
  onClick={(e) => {
29
39
  // A onClick function was given to CreateButton
30
40
  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();
41
+
42
+ // Check if we have a provider context
43
+ if (!context || !context.hasProvider) {
44
+ console.error('The showCreateModal function should only be used in a child of the FormModalProvider component.');
45
+ return;
46
+ }
47
+
48
+ // CreateButton is inside a FormModalProvider which can handle showCreateModal
49
+ context.showCreateModal();
34
50
  }}
35
51
  />
36
52
  )
37
53
  }
38
- export interface FormEditModalButtonProps<T, K> extends ButtonProps {
39
- state: { [key in keyof T]: FormValue } & K;
54
+
55
+ export interface FormEditModalButtonProps<T extends FormFields> extends ButtonProps {
56
+ state: InitialState<T>;
40
57
  }
41
- export const FormEditModalButton = ({ state, onClick, ... props }: FormEditModalButtonProps<T, K>) => {
42
- const { showEditModal, hasProvider } = useFormModal();
43
58
 
59
+ export const FormEditModalButton = <T extends FormFields>({ state, onClick, ...props }: FormEditModalButtonProps<T>) => {
60
+ const context = useContext(FormModalContext);
61
+
44
62
  return (
45
63
  <EditButton
46
64
  {...props}
47
65
  onClick={(e) => {
48
- // A onClick function was given to CreateButton
66
+ // A onClick function was given to EditButton
49
67
  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);
68
+
69
+ // Check if we have a provider context
70
+ if (!context || !context.hasProvider) {
71
+ console.error('The showEditModal function should only be used in a child of the FormModalProvider component.');
72
+ return;
73
+ }
74
+
75
+ // EditButton is inside a FormModalProvider which can handle showEditModal
76
+ context.showEditModal(state);
53
77
  }}
54
78
  />
55
79
  )
56
80
  }
57
81
 
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
- export type FormModalProviderProps<
81
- T extends FormFields,
82
- K extends IncludeData<T>
83
- > = {
84
- initialState: InitialState<T> | K;
85
- includeData?: K;
82
+ export type FormModalProviderProps<T extends FormFields> = {
86
83
  formFields: T;
87
- onSave?: OnSave<T, K>;
88
- onCreate?: OnSave<T, K>;
89
- onUpdate?: OnSave<T, K>;
84
+ initialState?: InitialState<T>;
85
+ onSave?: OnSubmit<T>;
86
+ onCreate?: OnSubmit<T>;
87
+ onUpdate?: OnSubmit<T>;
90
88
  validate?: Validate;
91
89
  createModalTitle?: ModalTitle;
92
90
  editModalTitle?: ModalTitle;
@@ -95,58 +93,106 @@ export type FormModalProviderProps<
95
93
  width?: Width;
96
94
  children: ReactNode;
97
95
  }
98
- export const FormModalProvider: React.FC<FormModalProviderProps<T, K>> = ({
99
- createModalTitle,
100
- editModalTitle,
101
- formFields,
102
- initialState,
96
+
97
+ export const FormModalProvider = <T extends FormFields>({
98
+ createModalTitle,
99
+ editModalTitle,
100
+ formFields,
101
+ initialState = {} as InitialState<T>,
103
102
  validate,
104
- loading,
105
- onCreate,
106
- onUpdate,
103
+ loading = false,
104
+ onCreate,
105
+ onUpdate,
107
106
  onSave,
108
107
  dialogClassName,
109
- children,
110
- }) => {
111
- const [createModalActive, showCreateModal] = useState(false);
112
- const [instanceInEditModal, showEditModal] = useState<({ [key in keyof T]: FormValue } & K) | null>(null);
108
+ width,
109
+ children,
110
+ }: FormModalProviderProps<T>) => {
111
+ const [createModalActive, setCreateModalActive] = useState(false);
112
+ const [editModalState, setEditModalState] = useState<InitialState<T> | null>(null);
113
113
  const { strings } = useLocalization();
114
+
115
+ const handleCreateSubmit: OnSubmit<T> = (state, callback) => {
116
+ // Priority: onCreate > onSave fallback
117
+ const submitHandler = onCreate || onSave;
118
+ if (submitHandler) {
119
+ // For testing purposes and direct submission, always call the handler
120
+ // The FormProvider's submit() will call this with current form state
121
+ submitHandler(state, () => {
122
+ setCreateModalActive(false);
123
+ if (callback) callback();
124
+ });
125
+ } else {
126
+ // No handler provided, just close modal
127
+ setCreateModalActive(false);
128
+ if (callback) callback();
129
+ }
130
+ };
131
+
132
+ const handleEditSubmit: OnSubmit<T> = (state, callback) => {
133
+ // Priority: onUpdate > onSave fallback
134
+ const submitHandler = onUpdate || onSave;
135
+ if (submitHandler) {
136
+ submitHandler(state, () => {
137
+ setEditModalState(null);
138
+ if (callback) callback();
139
+ });
140
+ } else {
141
+ // No handler provided, just close modal
142
+ setEditModalState(null);
143
+ if (callback) callback();
144
+ }
145
+ };
146
+
114
147
  return (
115
148
  <FormModalContext.Provider
116
149
  value={{
117
- showCreateModal: (show?: boolean) =>
118
- showCreateModal(typeof show === 'undefined' ? true : show),
119
- showEditModal,
150
+ showCreateModal: (show?: boolean) =>
151
+ setCreateModalActive(typeof show === 'undefined' ? true : show),
152
+ showEditModal: (
153
+ (state: InitialState<T> | null) => setEditModalState(state)
154
+ ) as (state: Partial<{ [x: string]: FormValue; }> | null) => void,
120
155
  hasProvider: true,
121
- }}>
156
+ }}
157
+ >
122
158
  {children}
123
-
159
+
124
160
  {createModalActive && (onCreate || onSave) && (
125
- <FormModal
126
- modalTitle={createModalTitle || strings.getString('modal_create')}
127
- onHide={() => showCreateModal(false)}
128
- initialState={initialState}
129
- formFields={formFields}
161
+ <FormProvider
162
+ formFields={formFields}
163
+ initialState={initialState}
164
+ onSubmit={handleCreateSubmit}
130
165
  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}
166
+ loading={loading}
167
+ resetTrigger={createModalActive}
168
+ >
169
+ <FormModal
170
+ show={createModalActive}
171
+ modalTitle={createModalTitle || strings.getString('modal_create')}
172
+ onHide={() => setCreateModalActive(false)}
173
+ dialogClassName={dialogClassName}
174
+ width={width}
175
+ />
176
+ </FormProvider>
177
+ )}
178
+
179
+ {editModalState && (onUpdate || onSave) && (
180
+ <FormProvider
143
181
  formFields={formFields}
182
+ initialState={editModalState}
183
+ onSubmit={handleEditSubmit}
144
184
  validate={validate}
145
185
  loading={loading}
146
- // @ts-ignore
147
- onSave={onUpdate || onSave}
148
- dialogClassName={dialogClassName}
149
- />
186
+ resetTrigger={editModalState}
187
+ >
188
+ <FormModal
189
+ show={!!editModalState}
190
+ modalTitle={editModalTitle || strings.getString('modal_edit')}
191
+ onHide={() => setEditModalState(null)}
192
+ dialogClassName={dialogClassName}
193
+ width={width}
194
+ />
195
+ </FormProvider>
150
196
  )}
151
197
  </FormModalContext.Provider>
152
198
  );
@@ -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
+ }
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ import { Form } from 'react-bootstrap';
3
+ import { useFormField } from '../FormField';
4
+
5
+ export interface FormCheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name' | 'value' | 'onChange' | 'type'> {
6
+ name: string;
7
+ label?: React.ReactElement | string;
8
+ }
9
+
10
+ export const FormCheckbox = (props: FormCheckboxProps) => {
11
+ const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(props);
12
+
13
+ const errorId = isInvalid && error ? `${props.name}-error` : undefined;
14
+ const checkboxId = `${props.name}-checkbox`; // Use different ID to avoid conflict
15
+
16
+ return (
17
+ <Form.Group controlId={props.name}>
18
+ {isInvalid && error && (
19
+ <Form.Text id={errorId} className="text-danger">
20
+ {error}
21
+ </Form.Text>
22
+ )}
23
+ <Form.Check
24
+ id={checkboxId}
25
+ type="checkbox"
26
+ {...mergedProps}
27
+ checked={!!value}
28
+ isInvalid={isInvalid}
29
+ aria-describedby={errorId}
30
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
31
+ onChange(e.target.checked);
32
+ }}
33
+ label={label ? (
34
+ <span>
35
+ {label}
36
+ {required && ' *'}
37
+ </span>
38
+ ) : undefined}
39
+ />
40
+ </Form.Group>
41
+ );
42
+ };
43
+
44
+ export const FormSwitch = (props: FormCheckboxProps) => {
45
+ const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(props);
46
+
47
+ const errorId = isInvalid && error ? `${props.name}-error` : undefined;
48
+ const switchId = `${props.name}-switch`; // Use different ID to avoid conflict
49
+
50
+ return (
51
+ <Form.Group controlId={props.name}>
52
+ {isInvalid && error && (
53
+ <Form.Text id={errorId} className="text-danger">
54
+ {error}
55
+ </Form.Text>
56
+ )}
57
+ <Form.Check
58
+ id={switchId}
59
+ type="switch"
60
+ {...mergedProps}
61
+ checked={!!value}
62
+ isInvalid={isInvalid}
63
+ aria-describedby={errorId}
64
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
65
+ onChange(e.target.checked);
66
+ }}
67
+ label={label ? (
68
+ <span>
69
+ {label}
70
+ {required && ' *'}
71
+ </span>
72
+ ) : undefined}
73
+ />
74
+ </Form.Group>
75
+ );
76
+ };