@jasperoosthoek/react-toolbox 0.9.4 → 0.10.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.
- package/README.md +455 -155
- package/change-log.md +18 -1
- package/dist/components/forms/FormField.d.ts +13 -0
- package/dist/components/forms/FormModal.d.ts +3 -2
- package/dist/components/forms/FormProvider.d.ts +3 -0
- package/dist/components/forms/fields/FormInput.d.ts +1 -1
- package/dist/components/indicators/FixedLoadingIndicator.d.ts +16 -0
- package/dist/components/tables/DataTable.d.ts +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +2235 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +28 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +26 -12
- package/src/components/forms/FormField.tsx +6 -2
- package/src/components/forms/FormModal.tsx +38 -20
- package/src/components/forms/FormProvider.tsx +7 -1
- package/src/components/forms/fields/FormBadgesSelection.tsx +20 -9
- package/src/components/forms/fields/FormCheckbox.tsx +8 -10
- package/src/components/forms/fields/FormDropdown.tsx +4 -5
- package/src/components/forms/fields/FormInput.tsx +6 -6
- package/src/components/forms/fields/FormSelect.tsx +4 -3
- package/src/components/indicators/FixedLoadingIndicator.tsx +49 -0
- package/src/components/tables/DataTable.tsx +27 -30
- package/src/index.ts +1 -0
- package/dist/index.js +0 -3
- package/dist/index.js.LICENSE.txt +0 -15
- package/src/__tests__/buttons.test.tsx +0 -545
- package/src/__tests__/errors.test.tsx +0 -339
- package/src/__tests__/forms.test.tsx +0 -3021
- package/src/__tests__/hooks.test.tsx +0 -413
- package/src/__tests__/indicators.test.tsx +0 -284
- package/src/__tests__/localization.test.tsx +0 -462
- package/src/__tests__/login.test.tsx +0 -417
- package/src/__tests__/setupTests.ts +0 -328
- package/src/__tests__/tables.test.tsx +0 -609
- package/src/__tests__/timeAndDate.test.tsx +0 -308
- package/src/__tests__/utils.test.tsx +0 -422
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jasperoosthoek/react-toolbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"author": "jasperoosthoek",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"description": "Extensive library of React components that work together with react-bootstrap",
|
|
11
11
|
"files": [
|
|
12
|
-
"src/",
|
|
12
|
+
"src/components/",
|
|
13
|
+
"src/localization/",
|
|
14
|
+
"src/utils/",
|
|
15
|
+
"src/index.ts",
|
|
13
16
|
"dist/",
|
|
14
17
|
"change-log.md"
|
|
15
18
|
],
|
|
@@ -26,24 +29,35 @@
|
|
|
26
29
|
"@types/jest": "^29.5.2",
|
|
27
30
|
"@types/lodash": "^4.14.191",
|
|
28
31
|
"@types/node": "^18.11.19",
|
|
32
|
+
"@types/react": "^19.0.0",
|
|
33
|
+
"@types/react-dom": "^19.0.0",
|
|
34
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
35
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
29
36
|
"babel-jest": "^29.7.0",
|
|
30
|
-
"babel-loader": "^8.2.5",
|
|
31
37
|
"bootstrap": "^5.1.3",
|
|
32
|
-
"css-loader": "^6.7.1",
|
|
33
38
|
"jest": "^29.5.0",
|
|
34
39
|
"jest-environment-jsdom": "^29.5.0",
|
|
35
|
-
"
|
|
40
|
+
"react-dnd-html5-backend": "^16.0.1",
|
|
41
|
+
"react-syntax-highlighter": "^15.6.1",
|
|
42
|
+
"react-icons": "^5.4.0",
|
|
36
43
|
"ts-jest": "^29.1.0",
|
|
37
|
-
"ts-loader": "^9.4.2",
|
|
38
44
|
"typescript": "^5.8.2",
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
"vite": "^5.0.0"
|
|
46
|
+
},
|
|
47
|
+
"main": "dist/index.umd.js",
|
|
48
|
+
"module": "dist/index.es.js",
|
|
49
|
+
"exports": {
|
|
50
|
+
".": {
|
|
51
|
+
"import": "./dist/index.es.js",
|
|
52
|
+
"require": "./dist/index.umd.js",
|
|
53
|
+
"types": "./dist/index.d.ts"
|
|
54
|
+
}
|
|
43
55
|
},
|
|
44
|
-
"main": "dist/index.js",
|
|
45
56
|
"scripts": {
|
|
46
|
-
"build": "
|
|
57
|
+
"build": "vite build --config vite.config.lib.ts && tsc --project tsconfig.lib.json",
|
|
58
|
+
"build:examples": "vite build",
|
|
59
|
+
"dev": "vite",
|
|
60
|
+
"preview": "vite preview",
|
|
47
61
|
"test": "jest",
|
|
48
62
|
"test:watch": "jest --watch",
|
|
49
63
|
"test:coverage": "jest --coverage",
|
|
@@ -37,12 +37,13 @@ export const FormField = ({ children, ...props }: FormFieldProps) => {
|
|
|
37
37
|
|
|
38
38
|
// Hook to get form field value and setter for a specific field
|
|
39
39
|
export const useFormField = (componentProps: { name: string; label?: any; required?: boolean; [key: string]: any }) => {
|
|
40
|
-
const { name, label: propLabel, required: propRequired, ...htmlProps } = componentProps;
|
|
40
|
+
const { name, label: propLabel, required: propRequired, className, ...htmlProps } = componentProps;
|
|
41
41
|
|
|
42
42
|
const {
|
|
43
|
-
getValue,
|
|
43
|
+
getValue,
|
|
44
44
|
setValue,
|
|
45
45
|
formFields,
|
|
46
|
+
formId,
|
|
46
47
|
validationErrors,
|
|
47
48
|
pristine,
|
|
48
49
|
validated,
|
|
@@ -61,6 +62,7 @@ export const useFormField = (componentProps: { name: string; label?: any; requir
|
|
|
61
62
|
required: false,
|
|
62
63
|
mergedProps: {},
|
|
63
64
|
submit: () => {},
|
|
65
|
+
formId: '',
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -88,5 +90,7 @@ export const useFormField = (componentProps: { name: string; label?: any; requir
|
|
|
88
90
|
required,
|
|
89
91
|
mergedProps,
|
|
90
92
|
submit,
|
|
93
|
+
formId,
|
|
94
|
+
className,
|
|
91
95
|
};
|
|
92
96
|
};
|
|
@@ -17,6 +17,7 @@ export type FormModalProps = {
|
|
|
17
17
|
width?: Width;
|
|
18
18
|
submitText?: string;
|
|
19
19
|
cancelText?: string;
|
|
20
|
+
children?: React.ReactNode;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export const FormModal = ({
|
|
@@ -27,6 +28,7 @@ export const FormModal = ({
|
|
|
27
28
|
width,
|
|
28
29
|
submitText,
|
|
29
30
|
cancelText,
|
|
31
|
+
children,
|
|
30
32
|
}: FormModalProps) => {
|
|
31
33
|
const {
|
|
32
34
|
formData,
|
|
@@ -63,7 +65,7 @@ export const FormModal = ({
|
|
|
63
65
|
)}
|
|
64
66
|
|
|
65
67
|
<Modal.Body>
|
|
66
|
-
<FormFieldsRenderer />
|
|
68
|
+
{children ? children : <FormFieldsRenderer />}
|
|
67
69
|
</Modal.Body>
|
|
68
70
|
|
|
69
71
|
<Modal.Footer>
|
|
@@ -91,22 +93,30 @@ export const FormModal = ({
|
|
|
91
93
|
export const FormFieldsRenderer = () => {
|
|
92
94
|
const { formFields, hasProvider } = useForm();
|
|
93
95
|
|
|
94
|
-
if (!hasProvider || !formFields) {
|
|
96
|
+
if (!hasProvider || !formFields) {
|
|
95
97
|
return null;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
return (
|
|
99
101
|
<Form>
|
|
100
102
|
{Object.entries(formFields).map(([name, config]) => {
|
|
103
|
+
// Common props for all field types
|
|
104
|
+
const commonProps = {
|
|
105
|
+
name,
|
|
106
|
+
label: config.label,
|
|
107
|
+
placeholder: config.placeholder,
|
|
108
|
+
required: config.required,
|
|
109
|
+
...config.formProps
|
|
110
|
+
};
|
|
111
|
+
|
|
101
112
|
// Renderer decides which component to use based on config
|
|
102
113
|
if (config.component) {
|
|
103
114
|
// Custom component specified in config
|
|
104
115
|
const Component = config.component;
|
|
105
116
|
return (
|
|
106
117
|
<Component
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{...config.formProps}
|
|
118
|
+
{...commonProps}
|
|
119
|
+
key={name}
|
|
110
120
|
/>
|
|
111
121
|
);
|
|
112
122
|
}
|
|
@@ -115,10 +125,9 @@ export const FormFieldsRenderer = () => {
|
|
|
115
125
|
if (config.type === 'select' && config.options) {
|
|
116
126
|
return (
|
|
117
127
|
<FormSelect
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
{...config.formProps}
|
|
128
|
+
{...commonProps}
|
|
129
|
+
options={config.options || []}
|
|
130
|
+
key={name}
|
|
122
131
|
/>
|
|
123
132
|
);
|
|
124
133
|
}
|
|
@@ -126,12 +135,11 @@ export const FormFieldsRenderer = () => {
|
|
|
126
135
|
if (config.type === 'dropdown' && config.list) {
|
|
127
136
|
return (
|
|
128
137
|
<FormDropdown
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
list={config.list}
|
|
138
|
+
{...commonProps}
|
|
139
|
+
list={config.list || []}
|
|
132
140
|
idKey={config.idKey}
|
|
133
141
|
nameKey={config.nameKey}
|
|
134
|
-
{
|
|
142
|
+
key={name}
|
|
135
143
|
/>
|
|
136
144
|
);
|
|
137
145
|
}
|
|
@@ -139,19 +147,29 @@ export const FormFieldsRenderer = () => {
|
|
|
139
147
|
if (config.type === 'checkbox' || config.type === 'boolean') {
|
|
140
148
|
return (
|
|
141
149
|
<FormCheckbox
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
{...commonProps}
|
|
151
|
+
key={name}
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (config.type === 'textarea') {
|
|
157
|
+
return (
|
|
158
|
+
<FormInput
|
|
159
|
+
{...commonProps}
|
|
160
|
+
as="textarea"
|
|
161
|
+
rows={config.rows || 3}
|
|
162
|
+
key={name}
|
|
145
163
|
/>
|
|
146
164
|
);
|
|
147
165
|
}
|
|
148
166
|
|
|
149
|
-
// Default to FormInput for most cases
|
|
167
|
+
// Default to FormInput for most cases (text, number, email, etc.)
|
|
150
168
|
return (
|
|
151
169
|
<FormInput
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
{
|
|
170
|
+
{...commonProps}
|
|
171
|
+
type={config.type === 'number' ? 'number' : 'text'}
|
|
172
|
+
key={name}
|
|
155
173
|
/>
|
|
156
174
|
);
|
|
157
175
|
})}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useState, useContext, ReactNode, createContext } from 'react';
|
|
1
|
+
import React, { useEffect, useState, useContext, ReactNode, createContext, useId } from 'react';
|
|
2
2
|
import { useSetState, usePrevious } from '../../utils/hooks';
|
|
3
3
|
import { isEmpty } from '../../utils/utils';
|
|
4
4
|
import { useLocalization } from '../../localization/LocalizationContext';
|
|
@@ -12,10 +12,12 @@ export type FormFieldConfig = {
|
|
|
12
12
|
component?: any;
|
|
13
13
|
onChange?: (value: FormValue, formData?: any) => any;
|
|
14
14
|
label?: React.ReactElement | string;
|
|
15
|
+
placeholder?: string;
|
|
15
16
|
options?: Array<{ value: string | number; label: string; disabled?: boolean }>; // For select fields
|
|
16
17
|
list?: any[]; // For dropdown fields
|
|
17
18
|
idKey?: string; // For dropdown fields
|
|
18
19
|
nameKey?: string; // For dropdown fields
|
|
20
|
+
rows?: number; // For textarea fields
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export type FormFields = { [key: string]: FormFieldConfig };
|
|
@@ -30,6 +32,7 @@ export type Validate = (state: any) => any;
|
|
|
30
32
|
|
|
31
33
|
type FormContextType<T extends FormFields> = {
|
|
32
34
|
formFields: T;
|
|
35
|
+
formId: string;
|
|
33
36
|
formData: { [key in keyof T]: FormValue } | null;
|
|
34
37
|
initialFormData: { [key in keyof T]: FormValue } | null;
|
|
35
38
|
pristine: boolean;
|
|
@@ -48,6 +51,7 @@ type FormContextType<T extends FormFields> = {
|
|
|
48
51
|
|
|
49
52
|
const defaultFormState: FormContextType<any> = {
|
|
50
53
|
formFields: {},
|
|
54
|
+
formId: '',
|
|
51
55
|
formData: null,
|
|
52
56
|
initialFormData: null,
|
|
53
57
|
pristine: true,
|
|
@@ -88,6 +92,7 @@ export const FormProvider = <T extends FormFields>({
|
|
|
88
92
|
resetTrigger,
|
|
89
93
|
}: FormProviderProps<T>) => {
|
|
90
94
|
const { strings } = useLocalization();
|
|
95
|
+
const formId = useId();
|
|
91
96
|
|
|
92
97
|
if (!formFields) {
|
|
93
98
|
console.error(`Property formFields cannot be empty.`);
|
|
@@ -194,6 +199,7 @@ export const FormProvider = <T extends FormFields>({
|
|
|
194
199
|
|
|
195
200
|
const contextValue: FormContextType<T> = {
|
|
196
201
|
formFields,
|
|
202
|
+
formId,
|
|
197
203
|
formData,
|
|
198
204
|
initialFormData,
|
|
199
205
|
pristine,
|
|
@@ -17,6 +17,7 @@ export const BadgeSelection = ({ selected = true, disabled, cursor, onClick, sty
|
|
|
17
17
|
...cursor ? { cursor } : {},
|
|
18
18
|
...style || {},
|
|
19
19
|
}}
|
|
20
|
+
className='mx-1'
|
|
20
21
|
{...disabled ? {} : { onClick }}
|
|
21
22
|
{...restProps}
|
|
22
23
|
/>
|
|
@@ -43,23 +44,29 @@ export interface FormBadgesSelectionProps extends Omit<
|
|
|
43
44
|
disabled?: boolean | ((props: DisabledProps) => boolean);
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
// This component is in serious need of some TLC...
|
|
46
48
|
export const FormBadgesSelection = (props: FormBadgesSelectionProps) => {
|
|
47
49
|
const {
|
|
48
50
|
list,
|
|
49
|
-
idKey = '
|
|
51
|
+
idKey = 'value',
|
|
50
52
|
multiple,
|
|
51
53
|
integer,
|
|
52
54
|
disabled,
|
|
55
|
+
className,
|
|
53
56
|
...componentProps
|
|
54
57
|
} = props;
|
|
55
58
|
|
|
56
|
-
const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(componentProps);
|
|
57
|
-
|
|
59
|
+
const { value, onChange, isInvalid, error, label, required, mergedProps, formId } = useFormField(componentProps);
|
|
60
|
+
|
|
58
61
|
const isMultiple = multiple || multiple === false ? multiple : value instanceof Array;
|
|
59
62
|
const parseInteger = (value: string | number): string | number => integer ? parseInt(`${value}`) : `${value}`;
|
|
63
|
+
const controlId = `${formId}-${props.name}`;
|
|
64
|
+
if (!list) {
|
|
65
|
+
console.error('Missing required list property in FormBadgesSelection')
|
|
66
|
+
}
|
|
60
67
|
|
|
61
68
|
return (
|
|
62
|
-
<Form.Group controlId={
|
|
69
|
+
<Form.Group controlId={controlId} className={className}>
|
|
63
70
|
{label && <Form.Label>{label}{required && ' *'}</Form.Label>}
|
|
64
71
|
{isInvalid && error && (
|
|
65
72
|
<Form.Text className="text-danger">
|
|
@@ -84,12 +91,16 @@ export const FormBadgesSelection = (props: FormBadgesSelectionProps) => {
|
|
|
84
91
|
onClick={() => {
|
|
85
92
|
if (Array.isArray(value)) {
|
|
86
93
|
if(selected) {
|
|
87
|
-
|
|
94
|
+
// The "as string[]" cast is not ideal and use cases need to be reviewed
|
|
95
|
+
onChange(value.filter(id => parseInteger(id) !== parseInteger(item[idKey])) as string[]);
|
|
88
96
|
} else {
|
|
89
97
|
onChange(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
[
|
|
99
|
+
...value,
|
|
100
|
+
integer
|
|
101
|
+
? parseInt(item[idKey])
|
|
102
|
+
: `${item[idKey]}`
|
|
103
|
+
] as string[]
|
|
93
104
|
);
|
|
94
105
|
}
|
|
95
106
|
} else {
|
|
@@ -98,7 +109,7 @@ export const FormBadgesSelection = (props: FormBadgesSelectionProps) => {
|
|
|
98
109
|
}}
|
|
99
110
|
{...mergedProps}
|
|
100
111
|
>
|
|
101
|
-
{item.
|
|
112
|
+
{item.label}
|
|
102
113
|
</BadgeSelection>
|
|
103
114
|
);
|
|
104
115
|
})}
|
|
@@ -8,20 +8,19 @@ export interface FormCheckboxProps extends Omit<React.InputHTMLAttributes<HTMLIn
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const FormCheckbox = (props: FormCheckboxProps) => {
|
|
11
|
-
const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(props);
|
|
11
|
+
const { value, onChange, isInvalid, error, label, required, mergedProps, formId, className } = useFormField(props);
|
|
12
12
|
|
|
13
|
-
const errorId = isInvalid && error ? `${props.name}-error` : undefined;
|
|
14
|
-
const
|
|
13
|
+
const errorId = isInvalid && error ? `${formId}-${props.name}-error` : undefined;
|
|
14
|
+
const controlId = `${formId}-${props.name}`;
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<Form.Group controlId={
|
|
17
|
+
<Form.Group controlId={controlId} className={`mt-2 mb-2${className ? ` ${className}` : ''}`}>
|
|
18
18
|
{isInvalid && error && (
|
|
19
19
|
<Form.Text id={errorId} className="text-danger">
|
|
20
20
|
{error}
|
|
21
21
|
</Form.Text>
|
|
22
22
|
)}
|
|
23
23
|
<Form.Check
|
|
24
|
-
id={checkboxId}
|
|
25
24
|
type="checkbox"
|
|
26
25
|
{...mergedProps}
|
|
27
26
|
checked={!!value}
|
|
@@ -42,20 +41,19 @@ export const FormCheckbox = (props: FormCheckboxProps) => {
|
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
export const FormSwitch = (props: FormCheckboxProps) => {
|
|
45
|
-
const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(props);
|
|
44
|
+
const { value, onChange, isInvalid, error, label, required, mergedProps, formId, className } = useFormField(props);
|
|
46
45
|
|
|
47
|
-
const errorId = isInvalid && error ? `${props.name}-error` : undefined;
|
|
48
|
-
const
|
|
46
|
+
const errorId = isInvalid && error ? `${formId}-${props.name}-error` : undefined;
|
|
47
|
+
const controlId = `${formId}-${props.name}`;
|
|
49
48
|
|
|
50
49
|
return (
|
|
51
|
-
<Form.Group controlId={
|
|
50
|
+
<Form.Group controlId={controlId} className={className}>
|
|
52
51
|
{isInvalid && error && (
|
|
53
52
|
<Form.Text id={errorId} className="text-danger">
|
|
54
53
|
{error}
|
|
55
54
|
</Form.Text>
|
|
56
55
|
)}
|
|
57
56
|
<Form.Check
|
|
58
|
-
id={switchId}
|
|
59
57
|
type="switch"
|
|
60
58
|
{...mergedProps}
|
|
61
59
|
checked={!!value}
|
|
@@ -34,8 +34,7 @@ export const FormDropdown = <T,>(props: FormDropdownProps<T>) => {
|
|
|
34
34
|
...componentProps
|
|
35
35
|
} = props;
|
|
36
36
|
|
|
37
|
-
const { value, onChange, isInvalid, error, label, required, mergedProps, submit } = useFormField(componentProps);
|
|
38
|
-
const { strings } = useLocalization();
|
|
37
|
+
const { value, onChange, isInvalid, error, label, required, mergedProps, submit, formId, className } = useFormField(componentProps);
|
|
39
38
|
|
|
40
39
|
// Use options or list, with options taking precedence
|
|
41
40
|
const listBase = options || listProp;
|
|
@@ -81,10 +80,11 @@ export const FormDropdown = <T,>(props: FormDropdownProps<T>) => {
|
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
const selectedItem = list.find(item => item[idKey] === value);
|
|
83
|
+
const controlId = `${formId}-${props.name}`;
|
|
84
84
|
|
|
85
85
|
return (
|
|
86
|
-
<Form.Group controlId={
|
|
87
|
-
{label && <Form.Label
|
|
86
|
+
<Form.Group controlId={controlId} className={className}>
|
|
87
|
+
{label && <Form.Label>{label}{required && ' *'}</Form.Label>}
|
|
88
88
|
{isInvalid && error && (
|
|
89
89
|
<Form.Text className="text-danger">
|
|
90
90
|
{error}
|
|
@@ -92,7 +92,6 @@ export const FormDropdown = <T,>(props: FormDropdownProps<T>) => {
|
|
|
92
92
|
)}
|
|
93
93
|
|
|
94
94
|
<Form.Select
|
|
95
|
-
id={props.name}
|
|
96
95
|
value={value || ''}
|
|
97
96
|
isInvalid={isInvalid}
|
|
98
97
|
onChange={(e) => onChange(e.target.value)}
|
|
@@ -7,24 +7,24 @@ export interface FormInputProps extends Omit<React.InputHTMLAttributes<HTMLInput
|
|
|
7
7
|
label?: React.ReactElement | string;
|
|
8
8
|
as?: string; // For textarea, select, etc.
|
|
9
9
|
rows?: number; // For textarea
|
|
10
|
-
onChange
|
|
10
|
+
onChange?: (value: string) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export const FormInput = (props: FormInputProps) => {
|
|
14
|
-
const { value, onChange, isInvalid, error, label, required, mergedProps, submit } = useFormField(props);
|
|
14
|
+
const { value, onChange, isInvalid, error, label, required, mergedProps, submit, formId, className } = useFormField(props);
|
|
15
15
|
|
|
16
|
-
const errorId = isInvalid && error ? `${props.name}-error` : undefined;
|
|
16
|
+
const errorId = isInvalid && error ? `${formId}-${props.name}-error` : undefined;
|
|
17
|
+
const controlId = `${formId}-${props.name}`;
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
|
-
<Form.Group controlId={
|
|
20
|
-
{label && <Form.Label
|
|
20
|
+
<Form.Group controlId={controlId} className={className}>
|
|
21
|
+
{label && <Form.Label>{label}{required && ' *'}</Form.Label>}
|
|
21
22
|
{isInvalid && error && (
|
|
22
23
|
<Form.Text id={errorId} className="text-danger">
|
|
23
24
|
{error}
|
|
24
25
|
</Form.Text>
|
|
25
26
|
)}
|
|
26
27
|
<Form.Control
|
|
27
|
-
id={props.name}
|
|
28
28
|
autoComplete="off"
|
|
29
29
|
{...mergedProps}
|
|
30
30
|
value={value || ''}
|
|
@@ -10,11 +10,12 @@ export interface FormSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSel
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export const FormSelect = (props: FormSelectProps) => {
|
|
13
|
-
const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(props);
|
|
14
|
-
const { options, placeholder = "Choose..." } = props;
|
|
13
|
+
const { value, onChange, isInvalid, error, label, required, mergedProps, formId, className } = useFormField(props);
|
|
14
|
+
const { options = [], placeholder = "Choose..." } = props;
|
|
15
|
+
const controlId = `${formId}-${props.name}`;
|
|
15
16
|
|
|
16
17
|
return (
|
|
17
|
-
<Form.Group controlId={
|
|
18
|
+
<Form.Group controlId={controlId} className={className}>
|
|
18
19
|
{label && <Form.Label>{label}{required && ' *'}</Form.Label>}
|
|
19
20
|
{isInvalid && error && (
|
|
20
21
|
<Form.Text className="text-danger">
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface FixedLoadingIndicatorProps {
|
|
4
|
+
show: boolean;
|
|
5
|
+
message: string;
|
|
6
|
+
variant?: 'info' | 'warning' | 'success' | 'danger' | 'primary' | 'secondary';
|
|
7
|
+
position?: {
|
|
8
|
+
top?: string;
|
|
9
|
+
right?: string;
|
|
10
|
+
bottom?: string;
|
|
11
|
+
left?: string;
|
|
12
|
+
};
|
|
13
|
+
className?: string;
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const FixedLoadingIndicator: React.FC<FixedLoadingIndicatorProps> = ({
|
|
18
|
+
show,
|
|
19
|
+
message,
|
|
20
|
+
variant = 'info',
|
|
21
|
+
position = { top: '20px', right: '20px' },
|
|
22
|
+
className = '',
|
|
23
|
+
style = {}
|
|
24
|
+
}) => {
|
|
25
|
+
if (!show) return null;
|
|
26
|
+
|
|
27
|
+
const defaultStyle: React.CSSProperties = {
|
|
28
|
+
position: 'fixed',
|
|
29
|
+
zIndex: 1050,
|
|
30
|
+
minWidth: '250px',
|
|
31
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
32
|
+
...position,
|
|
33
|
+
...style
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={`alert alert-${variant} ${className}`}
|
|
39
|
+
style={defaultStyle}
|
|
40
|
+
>
|
|
41
|
+
<div className="d-flex align-items-center">
|
|
42
|
+
<div className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></div>
|
|
43
|
+
{message}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default FixedLoadingIndicator;
|
|
@@ -45,6 +45,7 @@ export type DataTableColumn<R> = {
|
|
|
45
45
|
name: ReactNode | string | number;
|
|
46
46
|
orderBy?: OrderByColumn<R>;
|
|
47
47
|
optionsDropdown?: OptionsDropdown;
|
|
48
|
+
search?: string | ((row: R) => string | number);
|
|
48
49
|
className?: string;
|
|
49
50
|
value?: number | string | ((row: R) => number);
|
|
50
51
|
formatSum?: ((value: number) => ReactElement | string | number) | ReactElement | string | number;
|
|
@@ -78,7 +79,6 @@ export type DataTableProps<D extends any[]> = {
|
|
|
78
79
|
columns: DataTableColumn<D[number]>[];
|
|
79
80
|
rowsPerPage?: number | null;
|
|
80
81
|
rowsPerPageOptions?: RowsPerPageOptions;
|
|
81
|
-
filterColumn?: string | ((row: D[number]) => string) | (string | ((row: D[number]) => string))[];
|
|
82
82
|
orderByDefault?: ((row: D[number]) => number) | string | null;
|
|
83
83
|
orderByDefaultDirection?: OrderByDirection;
|
|
84
84
|
onMove?: OnMove<D[number]>;
|
|
@@ -99,7 +99,6 @@ export const DataTable = <D extends any[]>({
|
|
|
99
99
|
columns,
|
|
100
100
|
rowsPerPage: rowsPerPageDefault = 10,
|
|
101
101
|
rowsPerPageOptions = [10, 25, 50, 100, null],
|
|
102
|
-
filterColumn,
|
|
103
102
|
orderByDefault,
|
|
104
103
|
orderByDefaultDirection='asc',
|
|
105
104
|
onMove,
|
|
@@ -121,29 +120,34 @@ export const DataTable = <D extends any[]>({
|
|
|
121
120
|
if (Object.keys(restProps).length !== 0) console.error('Unrecognised props:', restProps);
|
|
122
121
|
|
|
123
122
|
const [filterText, setFilterText] = useState('');
|
|
124
|
-
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
125
123
|
const [orderBy, setOrderBy] = useState<{ order: OrderByDirection; column: OrderByColumn<R> } | null>(null);
|
|
126
124
|
const [rowsPerPage, setRowsPerPage] = useState(rowsPerPageDefault);
|
|
127
125
|
const [page, setPage] = useState(0);
|
|
126
|
+
|
|
128
127
|
let data = allData && (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
Object.values(allData).filter(row => {
|
|
129
|
+
if (!filterText) return true;
|
|
130
|
+
|
|
131
|
+
const regex = new RegExp(filterText, 'i');
|
|
132
|
+
|
|
133
|
+
for (const { search } of columns) {
|
|
134
|
+
if (!search) continue;
|
|
135
|
+
|
|
136
|
+
let value: any = '';
|
|
137
|
+
|
|
138
|
+
if (typeof search === 'function') {
|
|
139
|
+
value = search(row);
|
|
140
|
+
} else if (row[search] !== undefined) {
|
|
141
|
+
value = row[search];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (value && regex.test(String(value))) {
|
|
145
|
+
return true; // early return on first match
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return false;
|
|
150
|
+
})
|
|
147
151
|
);
|
|
148
152
|
|
|
149
153
|
const pagesCount = (data && rowsPerPage && Math.ceil(data.length / rowsPerPage));
|
|
@@ -217,7 +221,7 @@ export const DataTable = <D extends any[]>({
|
|
|
217
221
|
value && data.reduce((sum, row) => (
|
|
218
222
|
sum + (typeof value === 'function' ? value(row) : row[value])
|
|
219
223
|
), 0)
|
|
220
|
-
)), [columns])
|
|
224
|
+
)), [columns, data])
|
|
221
225
|
|
|
222
226
|
if (!Component) return null;
|
|
223
227
|
|
|
@@ -270,12 +274,6 @@ export const DataTable = <D extends any[]>({
|
|
|
270
274
|
setRowsPerPage(e.target.value === 'everything' ? null : parseInt(e.target.value))
|
|
271
275
|
}
|
|
272
276
|
>
|
|
273
|
-
<option
|
|
274
|
-
value="everything"
|
|
275
|
-
disabled={rowsPerPage !== null}
|
|
276
|
-
>
|
|
277
|
-
{strings.getString('select')}
|
|
278
|
-
</option>
|
|
279
277
|
{rowsPerPageOptions.map((option, index) => (
|
|
280
278
|
<option key={index} value={option === null ? 'everything' : option}>
|
|
281
279
|
{option === null ? strings.getString('everything') : option}
|
|
@@ -370,7 +368,7 @@ export const DataTable = <D extends any[]>({
|
|
|
370
368
|
)}
|
|
371
369
|
active={key === optionsDropdown.selected}
|
|
372
370
|
>
|
|
373
|
-
{text}
|
|
371
|
+
{text === null ? strings.getString('everything') : text}
|
|
374
372
|
</Dropdown.Item>
|
|
375
373
|
)
|
|
376
374
|
}
|
|
@@ -452,7 +450,6 @@ export const DataTable = <D extends any[]>({
|
|
|
452
450
|
{showSum && (
|
|
453
451
|
<tfoot>
|
|
454
452
|
<tr>
|
|
455
|
-
|
|
456
453
|
{columns.map(({ value, formatSum }, index) =>
|
|
457
454
|
<td
|
|
458
455
|
key={index}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from './components/forms/fields';
|
|
|
13
13
|
|
|
14
14
|
export * from './components/indicators/LoadingIndicator';
|
|
15
15
|
export * from './components/indicators/CheckIndicator';
|
|
16
|
+
export * from './components/indicators/FixedLoadingIndicator';
|
|
16
17
|
|
|
17
18
|
export * from './components/tables/DataTable';
|
|
18
19
|
export * from './components/tables/DragAndDropList';
|