@jasperoosthoek/react-toolbox 0.6.1 → 0.6.3

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.
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
2
  import { AdditionalLocalization } from './localization';
3
+ export declare const combineLocalization: (...locals: AdditionalLocalization[]) => AdditionalLocalization;
3
4
  export declare const LocalizationContext: React.Context<{
4
5
  lang: string;
5
6
  languages: string[];
6
7
  setLanguage: (lang: string) => void;
7
8
  text: (str: TemplateStringsArray, ...values: (string | number)[]) => string;
9
+ textByLang: (lang: string) => (str: TemplateStringsArray, ...values: (string | number)[]) => string;
8
10
  strings: import("react-localization").LocalizedStringsMethods;
9
11
  localizationStrings: AdditionalLocalization;
10
12
  setLocalization: (localization: AdditionalLocalization) => void;
@@ -25,6 +27,7 @@ export declare const useLocalization: () => {
25
27
  languages: string[];
26
28
  setLanguage: (lang: string) => void;
27
29
  text: (str: TemplateStringsArray, ...values: (string | number)[]) => string;
30
+ textByLang: (lang: string) => (str: TemplateStringsArray, ...values: (string | number)[]) => string;
28
31
  strings: import("react-localization").LocalizedStringsMethods;
29
32
  localizationStrings: AdditionalLocalization;
30
33
  setLocalization: (localization: AdditionalLocalization) => void;
@@ -1,8 +1,9 @@
1
1
  export type LocalizationFunction = (...args: (string | number)[]) => string;
2
+ export type LocalizationElement = {
3
+ [languageString: string]: (string | LocalizationFunction);
4
+ };
2
5
  export type AdditionalLocalization = {
3
- [lang: string]: {
4
- [languageString: string]: string | LocalizationFunction;
5
- };
6
+ [lang: string]: LocalizationElement;
6
7
  };
7
8
  export interface LocalizationStrings {
8
9
  select: string;
@@ -48,7 +48,7 @@ export declare const loginFactory: ({ authenticatedComponent, passwordResetUrl,
48
48
  unsetCurrentUser: () => any;
49
49
  logout: () => any;
50
50
  };
51
- login: (userData: any, callback?: () => void) => ThunkAction<Promise<void>, any, unknown, LoginActions>;
51
+ login: (userData: any, callback?: () => void) => ThunkAction<Promise<void>, any, undefined, LoginActions>;
52
52
  getCurrentUser: ({ callback }?: {
53
53
  callback?: (userData: any) => void;
54
54
  }) => (dispatch: ThunkDispatch<any, undefined, any>) => Promise<void>;
@@ -1,4 +1,4 @@
1
- export declare const usePrevious: <T>(value: T) => T;
1
+ export declare const usePrevious: <T>(value: T) => T | undefined;
2
2
  export declare const useDebouncedEffect: (effect: () => void, deps: any[], delay: number) => void;
3
3
  export declare const useForceUpdate: () => () => void;
4
4
  export declare const useSetState: <T>(initialState: T) => [T, (subState: Partial<T>) => void];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jasperoosthoek/react-toolbox",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "author": "jasperoosthoek",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "style-loader": "^3.3.1",
35
35
  "ts-jest": "^29.1.0",
36
36
  "ts-loader": "^9.4.2",
37
- "typescript": "^4.9.5",
37
+ "typescript": "^5.8.2",
38
38
  "url-loader": "^4.1.1",
39
39
  "webpack": "^5.72.0",
40
40
  "webpack-cli": "^4.9.2",
@@ -54,11 +54,11 @@
54
54
  "axios": "^1.4.0",
55
55
  "bootstrap": "^5.1.3",
56
56
  "moment": "^2.29.4",
57
- "react": "^19.0.0",
57
+ "react": "19.0.0",
58
58
  "react-bootstrap": "^2.10.9",
59
59
  "react-dnd": "^16.0.1",
60
- "react-dom": "^19.0.0",
61
- "react-icons": "^5.3.0",
60
+ "react-dom": "19.0.0",
61
+ "react-icons": "^5.4.0",
62
62
  "react-localization": "^2.0.5",
63
63
  "react-redux": "^9.2.0",
64
64
  "redux": "^5.0.1",
@@ -6,7 +6,7 @@ import { LocalizationContext } from '../../localization/LocalizationContext';
6
6
  import { ButtonProps } from './IconButtons';
7
7
 
8
8
  export interface ConfirmButtonProps extends ButtonProps {
9
- modalTitle: ReactElement | string;
9
+ modalTitle?: ReactElement | string;
10
10
  modalBody?: ReactElement | string;
11
11
  confirmText?: ReactElement | string;
12
12
  cancelText?: ReactElement | string;
@@ -26,6 +26,8 @@ import { HiOutlineCog, HiOutlineLink } from 'react-icons/hi';
26
26
  import { VscMenu } from 'react-icons/vsc';
27
27
  import { RiQuestionnaireLine, RiDropdownList } from 'react-icons/ri';
28
28
  import { LuClipboardPaste } from "react-icons/lu";
29
+ import { RiResetLeftLine } from "react-icons/ri";
30
+
29
31
  export interface ButtonProps extends ReactBootstrapButtonProps {
30
32
  loading?: boolean,
31
33
  iconSize?: string,
@@ -56,10 +58,9 @@ export const IconButton = ({
56
58
  }}
57
59
  {...restProps}
58
60
  >
59
- {loading
60
- ? <CgSpinner style={{ animation: 'spinner-border .75s linear infinite' }}/>
61
- : Icon && <Icon size={iconSize}/>
62
- }
61
+ {loading && <CgSpinner style={{ animation: 'spinner-border .75s linear infinite' }} />}
62
+ {!loading && Icon && typeof Icon === "function" && <Icon size={iconSize} />}
63
+
63
64
  {children && <>&nbsp;</>}
64
65
  {children}
65
66
  </Button>
@@ -102,6 +103,7 @@ export const UpButton = makeIconButton(AiFillCaretUp);
102
103
  export const UploadButton = makeIconButton(AiOutlineUpload);
103
104
  export const QuestionnaireButton = makeIconButton(RiQuestionnaireLine);
104
105
  export const DropdownButton = makeIconButton(RiDropdownList);
106
+ export const ResetButton = makeIconButton(RiResetLeftLine);
105
107
 
106
108
  export interface UploadTextButtonProps extends ButtonProps {
107
109
  accept?: string;
@@ -7,12 +7,14 @@ import { isEmpty } from '../../utils/utils';
7
7
  import { useLocalization } from '../../localization/LocalizationContext';
8
8
  import { FormComponentProps, FormSelectProps, FormOnChange, FormValue } from './FormFields';
9
9
 
10
+ export type FormFieldComponent = (props: FormComponentProps | FormSelectProps) => ReactElement
11
+
10
12
  export type FormField = {
11
13
  initialValue?: any;
12
14
  type?: 'string' | 'number';
13
15
  required?: boolean;
14
16
  formProps?: any;
15
- component?: (props: FormComponentProps | FormSelectProps) => ReactElement;
17
+ component?: FormFieldComponent;
16
18
  onChange?: FormOnChange;
17
19
  label?: ReactElement | string;
18
20
  }
@@ -53,14 +55,14 @@ export const FormModal = <
53
55
  T extends FormFields,
54
56
  K extends IncludeData<T>
55
57
  >({
56
- initialState = null,
58
+ initialState = {} as K,
57
59
  formFields,
58
60
  includeData = {} as K,
59
61
  show = true,
60
62
  onSave,
61
63
  onHide,
62
- validate = null,
63
- modalTitle = null,
64
+ validate,
65
+ modalTitle = '',
64
66
  loading = false,
65
67
  dialogClassName='',
66
68
  width,
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useContext, ReactNode } from 'react';
1
+ import React, { useState, useContext, ReactNode } from 'react';
2
2
 
3
3
  import { useLocalization } from '../../localization/LocalizationContext';
4
4
  import {
@@ -18,8 +18,8 @@ import { ButtonProps, CreateButton, EditButton } from '../buttons/IconButtons';
18
18
  export type ShowCreateModal = (show?: boolean) => void;
19
19
  export type ShowEditModal<T, K> = (state: { [key in keyof T]: FormValue } & K) => void;
20
20
 
21
- export type ShowCreateModalButton = ButtonProps;
22
- export const ShowCreateModalButton = ({ onClick, ...props }: ButtonProps) => {
21
+ export type FormCreateModalButton = ButtonProps;
22
+ export const FormCreateModalButton = ({ onClick, ...props }: ButtonProps) => {
23
23
  const { showCreateModal, hasProvider } = useFormModal();
24
24
 
25
25
  return (
@@ -35,10 +35,10 @@ export const ShowCreateModalButton = ({ onClick, ...props }: ButtonProps) => {
35
35
  />
36
36
  )
37
37
  }
38
- export interface ShowEditModalButtonProps<T, K> extends ButtonProps {
38
+ export interface FormEditModalButtonProps<T, K> extends ButtonProps {
39
39
  state: { [key in keyof T]: FormValue } & K;
40
40
  }
41
- export const ShowEditModalButton = ({ state, onClick, ... props }: ShowEditModalButtonProps<T, K>) => {
41
+ export const FormEditModalButton = ({ state, onClick, ... props }: FormEditModalButtonProps<T, K>) => {
42
42
  const { showEditModal, hasProvider } = useFormModal();
43
43
 
44
44
  return (
@@ -100,6 +100,7 @@ export const FormModalProvider: React.FC<FormModalProviderProps<T, K>> = ({
100
100
  editModalTitle,
101
101
  formFields,
102
102
  initialState,
103
+ validate,
103
104
  loading,
104
105
  onCreate,
105
106
  onUpdate,
@@ -126,6 +127,7 @@ export const FormModalProvider: React.FC<FormModalProviderProps<T, K>> = ({
126
127
  onHide={() => showCreateModal(false)}
127
128
  initialState={initialState}
128
129
  formFields={formFields}
130
+ validate={validate}
129
131
  loading={loading}
130
132
  // @ts-ignore Ignore as Typescript does not recognize that this is allowed
131
133
  onSave={onCreate || onSave}
@@ -139,6 +141,7 @@ export const FormModalProvider: React.FC<FormModalProviderProps<T, K>> = ({
139
141
  onHide={() => showEditModal(null)}
140
142
  initialState={instanceInEditModal}
141
143
  formFields={formFields}
144
+ validate={validate}
142
145
  loading={loading}
143
146
  // @ts-ignore
144
147
  onSave={onUpdate || onSave}
@@ -137,7 +137,7 @@ export const DataTable = <D extends any[]>({
137
137
  )
138
138
  );
139
139
 
140
- const pagesCount = (data && rowsPerPage && Math.ceil((data.length - 1) / rowsPerPage));
140
+ const pagesCount = (data && rowsPerPage && Math.ceil(data.length / rowsPerPage));
141
141
  useEffect(
142
142
  () => { if (pagesCount && page >= pagesCount) setPage(pagesCount - 1) },
143
143
  [setPage, pagesCount, page]
@@ -165,11 +165,11 @@ export const DataTable = <D extends any[]>({
165
165
  if (pagesCount) {
166
166
  data = data.slice(page * rowsPerPage, (page + 1) * rowsPerPage);
167
167
  }
168
+
169
+ type ComponentProps<R> = { row: R } & DragAndDropListComponentProps;
168
170
 
169
- const Component = forwardRef<
170
- HTMLTableRowElement,
171
- { row: R } & DragAndDropListComponentProps
172
- >(({ row }: R, ref: Ref<HTMLTableRowElement> | null) =>
171
+ const Component = forwardRef<HTMLTableRowElement, ComponentProps<R>>(
172
+ ({ row }, ref) => (
173
173
  <tr
174
174
  ref={ref}
175
175
  {
@@ -202,7 +202,7 @@ export const DataTable = <D extends any[]>({
202
202
  </td>
203
203
  )}
204
204
  </tr>
205
- );
205
+ ));
206
206
 
207
207
  const sums = useMemo(() => columns.map(({ value }) => (
208
208
  value && data.reduce((sum, row) => (
@@ -1,4 +1,4 @@
1
- import React, { ChangeEvent } from 'react';
1
+ import React, { ChangeEvent, ReactNode } from 'react';
2
2
  import { InputGroup, Form, FormControlProps } from 'react-bootstrap';
3
3
 
4
4
  import { useLocalization } from '../../localization/LocalizationContext';
@@ -10,29 +10,47 @@ export type SearchBoxProps = {
10
10
  onChange: (value: string) => void;
11
11
  onClear?: () => void;
12
12
  onSearch?: () => void;
13
+ label?: ReactNode | string | number;
14
+ placeholder?: string | false;
13
15
  }
14
- export const SearchBox = ({ className='', value, onChange, onClear, onSearch }: SearchBoxProps) => {
16
+ export const SearchBox = ({
17
+ className='',
18
+ value,
19
+ onChange,
20
+ onClear,
21
+ onSearch,
22
+ label,
23
+ placeholder,
24
+ }: SearchBoxProps) => {
15
25
  const { strings } = useLocalization();
16
26
 
17
- return (<>
18
- <InputGroup {...className ? { className } : {}}>
19
- <Form.Control
20
- value={value}
21
- placeholder={strings.getString('search')}
22
- onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
23
- />
24
- {onClear &&
25
- <UnCheckButton
26
- variant="outline-secondary"
27
- onClick={() => onClear()}
27
+ return (
28
+ <Form.Group>
29
+ {label && <Form.Label>{label}</Form.Label>}
30
+
31
+ <InputGroup {...className ? { className } : {}}>
32
+ <Form.Control
33
+ value={value}
34
+ placeholder={
35
+ placeholder !== false
36
+ ? placeholder || strings.getString('search')
37
+ : undefined
38
+ }
39
+ onChange={(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
28
40
  />
29
- }
30
- {onSearch &&
31
- <SearchButton
32
- variant="outline-secondary"
33
- onClick={() => onSearch()}
34
- />
35
- }
36
- </InputGroup></>
41
+ {onClear &&
42
+ <UnCheckButton
43
+ variant="outline-secondary"
44
+ onClick={() => onClear()}
45
+ />
46
+ }
47
+ {onSearch &&
48
+ <SearchButton
49
+ variant="outline-secondary"
50
+ onClick={() => onSearch()}
51
+ />
52
+ }
53
+ </InputGroup>
54
+ </Form.Group>
37
55
  )
38
56
  }
@@ -3,12 +3,29 @@ import LocalizedStrings from 'react-localization';
3
3
  import {
4
4
  defaultLocalization,
5
5
  AdditionalLocalization,
6
+ LocalizationElement,
6
7
  LocalizationFunction,
7
8
  defaultLanguages,
8
9
  } from './localization';
9
10
 
10
11
  const out_of_context_error = 'This function should only be used in a child of LocalizationProvider.';
11
12
 
13
+ export const combineLocalization = (...locals: AdditionalLocalization[]) => {
14
+ // Extract all unique language keys
15
+ const languages = [...new Set(locals.flatMap(Object.keys))];
16
+
17
+ return languages.reduce(
18
+ (o, lang) => ({
19
+ ...o,
20
+ [lang]: locals.reduce<LocalizationElement>(
21
+ (acc, l) => ({ ...acc, ...(l[lang] || {}) }),
22
+ {} as LocalizationElement
23
+ ),
24
+ }),
25
+ {} as AdditionalLocalization
26
+ );
27
+ };
28
+
12
29
  export const LocalizationContext = React.createContext({
13
30
  lang: 'en',
14
31
  languages: Object.keys(defaultLanguages),
@@ -17,6 +34,10 @@ export const LocalizationContext = React.createContext({
17
34
  console.error(out_of_context_error);
18
35
  return str[0];
19
36
  },
37
+ textByLang: (lang: string) => (str: TemplateStringsArray, ...values: (string | number)[]) => {
38
+ console.error(out_of_context_error);
39
+ return str[0];
40
+ },
20
41
  strings: new LocalizedStrings({ en: {} }),
21
42
  localizationStrings: {} as AdditionalLocalization,
22
43
  setLocalization: (localization: AdditionalLocalization) => console.error(out_of_context_error),
@@ -53,13 +74,15 @@ export const LocalizationProvider = ({
53
74
  ...defaultLocalization[lang] || {},
54
75
  ...additionalLocalization[lang] || {},
55
76
  },
56
- }), {});
77
+ }),
78
+ {}
79
+ );
57
80
 
58
81
  const strings = new LocalizedStrings(localizationStrings);
59
82
  strings.setLanguage(lang);
60
83
 
61
- const text = (str: TemplateStringsArray, ...values: (string | number)[]) => {
62
- const text_or_func = strings.getString(str[0]) as string | LocalizationFunction;
84
+ const textByLang = (lang: string) => (str: TemplateStringsArray, ...values: (string | number)[]) => {
85
+ const text_or_func = strings.getString(str[0], lang) as string | LocalizationFunction;
63
86
  if (!text_or_func) {
64
87
  console.error(`Language string not found: "${str[0]}"`);
65
88
  return str[0];
@@ -93,7 +116,8 @@ export const LocalizationProvider = ({
93
116
  setLanguage(lang);
94
117
  },
95
118
  strings,
96
- text,
119
+ text: textByLang(lang),
120
+ textByLang,
97
121
  localizationStrings,
98
122
  setLocalization,
99
123
  ...restProps,
@@ -1,12 +1,13 @@
1
- import LocalizedStrings, { LocalizedStringsMethods } from 'react-localization';
2
-
3
1
  export type LocalizationFunction = (...args: (string | number)[]) => string;
4
2
 
3
+ export type LocalizationElement = {
4
+ [languageString: string]: (string | LocalizationFunction)
5
+ }
6
+
5
7
  export type AdditionalLocalization = {
6
- [lang: string]: {
7
- [languageString: string]: string | LocalizationFunction;
8
- }
8
+ [lang: string]: LocalizationElement;
9
9
  }
10
+
10
11
  export interface LocalizationStrings {
11
12
  select: string;
12
13
  search: string;
@@ -19,11 +19,9 @@ export type LoginActions =
19
19
  | { type: 'LOGIN_UNSET_CURRENT_USER' };
20
20
 
21
21
  const useThunkDispatch = () => {
22
- const store = useStore();
23
22
  const dispatch = useDispatch() as ThunkDispatch<any, undefined, LoginActions>;
24
-
25
23
  return dispatch;
26
- }
24
+ };
27
25
 
28
26
  export type AuthState = {
29
27
  isAuthenticated: boolean;
@@ -87,8 +85,8 @@ export const loginFactory = ({
87
85
  const login = (
88
86
  userData: any,
89
87
  callback?: () => void
90
- ): ThunkAction<Promise<void>, any, unknown, LoginActions> => {
91
- return async (dispatch: ThunkDispatch<any, unknown, LoginActions>) => {
88
+ ): ThunkAction<Promise<void>, any, undefined, LoginActions> => {
89
+ return async (dispatch: ThunkDispatch<any, undefined, LoginActions>) => {
92
90
  try {
93
91
  const response = await axios.post(loginUrl, userData);
94
92
  const { auth_token } = response.data;