@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.
Files changed (38) hide show
  1. package/README.md +455 -155
  2. package/change-log.md +18 -1
  3. package/dist/components/forms/FormField.d.ts +13 -0
  4. package/dist/components/forms/FormModal.d.ts +3 -2
  5. package/dist/components/forms/FormProvider.d.ts +3 -0
  6. package/dist/components/forms/fields/FormInput.d.ts +1 -1
  7. package/dist/components/indicators/FixedLoadingIndicator.d.ts +16 -0
  8. package/dist/components/tables/DataTable.d.ts +2 -2
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.es.js +2235 -0
  11. package/dist/index.es.js.map +1 -0
  12. package/dist/index.umd.js +28 -0
  13. package/dist/index.umd.js.map +1 -0
  14. package/package.json +26 -12
  15. package/src/components/forms/FormField.tsx +6 -2
  16. package/src/components/forms/FormModal.tsx +38 -20
  17. package/src/components/forms/FormProvider.tsx +7 -1
  18. package/src/components/forms/fields/FormBadgesSelection.tsx +20 -9
  19. package/src/components/forms/fields/FormCheckbox.tsx +8 -10
  20. package/src/components/forms/fields/FormDropdown.tsx +4 -5
  21. package/src/components/forms/fields/FormInput.tsx +6 -6
  22. package/src/components/forms/fields/FormSelect.tsx +4 -3
  23. package/src/components/indicators/FixedLoadingIndicator.tsx +49 -0
  24. package/src/components/tables/DataTable.tsx +27 -30
  25. package/src/index.ts +1 -0
  26. package/dist/index.js +0 -3
  27. package/dist/index.js.LICENSE.txt +0 -15
  28. package/src/__tests__/buttons.test.tsx +0 -545
  29. package/src/__tests__/errors.test.tsx +0 -339
  30. package/src/__tests__/forms.test.tsx +0 -3021
  31. package/src/__tests__/hooks.test.tsx +0 -413
  32. package/src/__tests__/indicators.test.tsx +0 -284
  33. package/src/__tests__/localization.test.tsx +0 -462
  34. package/src/__tests__/login.test.tsx +0 -417
  35. package/src/__tests__/setupTests.ts +0 -328
  36. package/src/__tests__/tables.test.tsx +0 -609
  37. package/src/__tests__/timeAndDate.test.tsx +0 -308
  38. 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.9.4",
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
- "style-loader": "^3.3.1",
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
- "url-loader": "^4.1.1",
40
- "webpack": "^5.72.0",
41
- "webpack-cli": "^4.9.2",
42
- "webpack-node-externals": "^3.0.0"
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": "webpack",
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
- key={name}
108
- name={name}
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
- key={name}
119
- name={name}
120
- options={config.options}
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
- key={name}
130
- name={name}
131
- list={config.list}
138
+ {...commonProps}
139
+ list={config.list || []}
132
140
  idKey={config.idKey}
133
141
  nameKey={config.nameKey}
134
- {...config.formProps}
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
- key={name}
143
- name={name}
144
- {...config.formProps}
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
- key={name}
153
- name={name}
154
- {...config.formProps}
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 = 'id',
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={props.name}>
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
- onChange(value.filter(id => parseInteger(id) !== parseInteger(item[idKey])).toString());
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
- integer
91
- ? [...value.map((v: string | number ) => parseInt(v as string)), parseInt(item[idKey])]
92
- : [...value.map((v: string | number ) => `${v}`), `${item[idKey]}`]
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.name}
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 checkboxId = `${props.name}-checkbox`; // Use different ID to avoid conflict
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={props.name}>
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 switchId = `${props.name}-switch`; // Use different ID to avoid conflict
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={props.name}>
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={props.name}>
87
- {label && <Form.Label htmlFor={props.name}>{label}{required && ' *'}</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: (value: string) => void;
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={props.name}>
20
- {label && <Form.Label htmlFor={props.name}>{label}{required && ' *'}</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={props.name}>
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
- Object.values(allData).filter(row =>
130
- filterColumn && filterText
131
- ? (
132
- typeof filterColumn === 'function'
133
- ? filterColumn(row)
134
- : Array.isArray(filterColumn)
135
- ? filterColumn.reduce((acc, col) => {
136
- if (typeof col === 'function') {
137
- return acc + ' ' + col(row);
138
- } else if (typeof row[col] !== 'undefined') {
139
- return acc + ' ' + row[col];
140
- }
141
- return acc;
142
- }, '')
143
- : row[filterColumn]
144
- ).toString().match(new RegExp(`${filterText}`, 'i'))
145
- : true
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';