@jasperoosthoek/react-toolbox 0.9.4 → 0.9.5

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 (33) hide show
  1. package/README.md +455 -155
  2. package/change-log.md +8 -1
  3. package/dist/components/forms/FormField.d.ts +1 -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/index.es.js +2188 -0
  8. package/dist/index.es.js.map +1 -0
  9. package/dist/index.umd.js +28 -0
  10. package/dist/index.umd.js.map +1 -0
  11. package/package.json +26 -12
  12. package/src/components/forms/FormField.tsx +3 -0
  13. package/src/components/forms/FormModal.tsx +32 -19
  14. package/src/components/forms/FormProvider.tsx +7 -1
  15. package/src/components/forms/fields/FormBadgesSelection.tsx +3 -2
  16. package/src/components/forms/fields/FormCheckbox.tsx +8 -10
  17. package/src/components/forms/fields/FormDropdown.tsx +4 -4
  18. package/src/components/forms/fields/FormInput.tsx +6 -6
  19. package/src/components/forms/fields/FormSelect.tsx +4 -3
  20. package/src/components/tables/DataTable.tsx +1 -7
  21. package/dist/index.js +0 -3
  22. package/dist/index.js.LICENSE.txt +0 -15
  23. package/src/__tests__/buttons.test.tsx +0 -545
  24. package/src/__tests__/errors.test.tsx +0 -339
  25. package/src/__tests__/forms.test.tsx +0 -3021
  26. package/src/__tests__/hooks.test.tsx +0 -413
  27. package/src/__tests__/indicators.test.tsx +0 -284
  28. package/src/__tests__/localization.test.tsx +0 -462
  29. package/src/__tests__/login.test.tsx +0 -417
  30. package/src/__tests__/setupTests.ts +0 -328
  31. package/src/__tests__/tables.test.tsx +0 -609
  32. package/src/__tests__/timeAndDate.test.tsx +0 -308
  33. 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.9.5",
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",
@@ -43,6 +43,7 @@ export const useFormField = (componentProps: { name: string; label?: any; requir
43
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,6 @@ export const useFormField = (componentProps: { name: string; label?: any; requir
88
90
  required,
89
91
  mergedProps,
90
92
  submit,
93
+ formId,
91
94
  };
92
95
  };
@@ -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>
@@ -98,15 +100,23 @@ export const FormFieldsRenderer = () => {
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
+ key: name,
106
+ name,
107
+ label: config.label,
108
+ placeholder: config.placeholder,
109
+ required: config.required,
110
+ ...config.formProps
111
+ };
112
+
101
113
  // Renderer decides which component to use based on config
102
114
  if (config.component) {
103
115
  // Custom component specified in config
104
116
  const Component = config.component;
105
117
  return (
106
118
  <Component
107
- key={name}
108
- name={name}
109
- {...config.formProps}
119
+ {...commonProps}
110
120
  />
111
121
  );
112
122
  }
@@ -115,10 +125,8 @@ 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 || []}
122
130
  />
123
131
  );
124
132
  }
@@ -126,12 +134,10 @@ export const FormFieldsRenderer = () => {
126
134
  if (config.type === 'dropdown' && config.list) {
127
135
  return (
128
136
  <FormDropdown
129
- key={name}
130
- name={name}
131
- list={config.list}
137
+ {...commonProps}
138
+ list={config.list || []}
132
139
  idKey={config.idKey}
133
140
  nameKey={config.nameKey}
134
- {...config.formProps}
135
141
  />
136
142
  );
137
143
  }
@@ -139,19 +145,26 @@ export const FormFieldsRenderer = () => {
139
145
  if (config.type === 'checkbox' || config.type === 'boolean') {
140
146
  return (
141
147
  <FormCheckbox
142
- key={name}
143
- name={name}
144
- {...config.formProps}
148
+ {...commonProps}
149
+ />
150
+ );
151
+ }
152
+
153
+ if (config.type === 'textarea') {
154
+ return (
155
+ <FormInput
156
+ {...commonProps}
157
+ as="textarea"
158
+ rows={config.rows || 3}
145
159
  />
146
160
  );
147
161
  }
148
162
 
149
- // Default to FormInput for most cases
163
+ // Default to FormInput for most cases (text, number, email, etc.)
150
164
  return (
151
165
  <FormInput
152
- key={name}
153
- name={name}
154
- {...config.formProps}
166
+ {...commonProps}
167
+ type={config.type === 'number' ? 'number' : 'text'}
155
168
  />
156
169
  );
157
170
  })}
@@ -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,
@@ -53,13 +53,14 @@ export const FormBadgesSelection = (props: FormBadgesSelectionProps) => {
53
53
  ...componentProps
54
54
  } = props;
55
55
 
56
- const { value, onChange, isInvalid, error, label, required, mergedProps } = useFormField(componentProps);
56
+ const { value, onChange, isInvalid, error, label, required, mergedProps, formId } = useFormField(componentProps);
57
57
 
58
58
  const isMultiple = multiple || multiple === false ? multiple : value instanceof Array;
59
59
  const parseInteger = (value: string | number): string | number => integer ? parseInt(`${value}`) : `${value}`;
60
+ const controlId = `${formId}-${props.name}`;
60
61
 
61
62
  return (
62
- <Form.Group controlId={props.name}>
63
+ <Form.Group controlId={controlId}>
63
64
  {label && <Form.Label>{label}{required && ' *'}</Form.Label>}
64
65
  {isInvalid && error && (
65
66
  <Form.Text className="text-danger">
@@ -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 } = 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}>
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 } = 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}>
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,7 +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);
37
+ const { value, onChange, isInvalid, error, label, required, mergedProps, submit, formId } = useFormField(componentProps);
38
38
  const { strings } = useLocalization();
39
39
 
40
40
  // Use options or list, with options taking precedence
@@ -81,10 +81,11 @@ export const FormDropdown = <T,>(props: FormDropdownProps<T>) => {
81
81
  }
82
82
 
83
83
  const selectedItem = list.find(item => item[idKey] === value);
84
+ const controlId = `${formId}-${props.name}`;
84
85
 
85
86
  return (
86
- <Form.Group controlId={props.name}>
87
- {label && <Form.Label htmlFor={props.name}>{label}{required && ' *'}</Form.Label>}
87
+ <Form.Group controlId={controlId}>
88
+ {label && <Form.Label>{label}{required && ' *'}</Form.Label>}
88
89
  {isInvalid && error && (
89
90
  <Form.Text className="text-danger">
90
91
  {error}
@@ -92,7 +93,6 @@ export const FormDropdown = <T,>(props: FormDropdownProps<T>) => {
92
93
  )}
93
94
 
94
95
  <Form.Select
95
- id={props.name}
96
96
  value={value || ''}
97
97
  isInvalid={isInvalid}
98
98
  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 } = 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}>
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 } = 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}>
18
19
  {label && <Form.Label>{label}{required && ' *'}</Form.Label>}
19
20
  {isInvalid && error && (
20
21
  <Form.Text className="text-danger">
@@ -270,12 +270,6 @@ export const DataTable = <D extends any[]>({
270
270
  setRowsPerPage(e.target.value === 'everything' ? null : parseInt(e.target.value))
271
271
  }
272
272
  >
273
- <option
274
- value="everything"
275
- disabled={rowsPerPage !== null}
276
- >
277
- {strings.getString('select')}
278
- </option>
279
273
  {rowsPerPageOptions.map((option, index) => (
280
274
  <option key={index} value={option === null ? 'everything' : option}>
281
275
  {option === null ? strings.getString('everything') : option}
@@ -370,7 +364,7 @@ export const DataTable = <D extends any[]>({
370
364
  )}
371
365
  active={key === optionsDropdown.selected}
372
366
  >
373
- {text}
367
+ {text === null ? strings.getString('everything') : text}
374
368
  </Dropdown.Item>
375
369
  )
376
370
  }