@ainias42/react-bootstrap-mobile 0.2.12 → 0.2.14

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 (44) hide show
  1. package/bin/release.sh +0 -1
  2. package/bin/updateCopies.js +1 -0
  3. package/bootstrapReactMobile.ts +1 -0
  4. package/dist/bootstrapReactMobile.d.ts +1 -0
  5. package/dist/bootstrapReactMobile.js +3295 -18691
  6. package/dist/bootstrapReactMobile.js.map +1 -1
  7. package/dist/src/Components/FormElements/Button/Button.d.ts +2 -1
  8. package/dist/src/Components/FormElements/ColorInput/ColorInput.d.ts +3 -3
  9. package/dist/src/Components/FormElements/ColorInput/sharedSelectedColor.d.ts +1 -1
  10. package/dist/src/Components/FormElements/Controller/useYupResolver.d.ts +11 -0
  11. package/dist/src/Components/FormElements/SearchSelectInput/SearchSelectInput.d.ts +8 -1
  12. package/dist/src/Components/FormElements/Slider/Slider.d.ts +2 -1
  13. package/dist/src/Components/FormElements/Switch/Switch.d.ts +4 -1
  14. package/dist/src/Components/SpoilerList/Spoiler/Spoiler.d.ts +2 -1
  15. package/package.json +4 -3
  16. package/src/Components/Clickable/Clickable.tsx +1 -1
  17. package/src/Components/Dialog/ButtonDialog.tsx +0 -2
  18. package/src/Components/Dialog/DialogBackground.tsx +1 -1
  19. package/src/Components/Dialog/DialogContext.ts +1 -1
  20. package/src/Components/Dialog/dialogBackground.scss +1 -0
  21. package/src/Components/FormElements/Button/Button.tsx +11 -3
  22. package/src/Components/FormElements/ColorInput/ColorInput.tsx +22 -37
  23. package/src/Components/FormElements/ColorInput/colorInput.scss +39 -0
  24. package/src/Components/FormElements/ColorInput/sharedSelectedColor.ts +33 -4
  25. package/src/Components/FormElements/Controller/FileInputController.tsx +2 -2
  26. package/src/Components/FormElements/Controller/HookForm.tsx +1 -1
  27. package/src/Components/FormElements/Controller/useYupResolver.ts +53 -0
  28. package/src/Components/FormElements/FormError.tsx +1 -1
  29. package/src/Components/FormElements/Input/FileInput/MultipleFileInput.tsx +0 -1
  30. package/src/Components/FormElements/SearchSelectInput/SearchSelectInput.tsx +161 -129
  31. package/src/Components/FormElements/SearchSelectInput/seachSelectInput.scss +6 -1
  32. package/src/Components/FormElements/Select/Select.tsx +1 -1
  33. package/src/Components/FormElements/Slider/Slider.tsx +9 -1
  34. package/src/Components/FormElements/Switch/Switch.tsx +26 -14
  35. package/src/Components/FormElements/Switch/switch.scss +18 -16
  36. package/src/Components/FullScreen/FullScreen.tsx +5 -7
  37. package/src/Components/Hooks/useComposedRef.ts +1 -1
  38. package/src/Components/Hooks/useDelayedEffect.ts +4 -3
  39. package/src/Components/Hooks/useRerender.ts +1 -1
  40. package/src/Components/Icon/Icon.tsx +0 -1
  41. package/src/Components/List/InfiniteList.tsx +3 -4
  42. package/src/Components/List/List.tsx +0 -2
  43. package/src/Components/SpoilerList/Spoiler/Spoiler.tsx +24 -18
  44. package/src/scss/_default.scss +14 -12
@@ -11,6 +11,7 @@ export type ButtonProps<ClickData> = RbmComponentProps<Override<HTMLAttributes<H
11
11
  disabled?: boolean;
12
12
  flavor?: Flavor;
13
13
  fullWidth?: boolean;
14
+ stopPropagation?: boolean;
14
15
  size?: Omit<Size, "xxLarge" | "xLarge" | "large" | "xSmall">;
15
16
  } & OptionalListener<'onClick', ClickData>>>;
16
- export declare const Button: <ClickData>({ children, className, disabled, size, fullWidth, flavor, type, ...props }: ButtonProps<ClickData>) => React.JSX.Element;
17
+ export declare const Button: <ClickData>({ children, className, disabled, size, fullWidth, flavor, type, stopPropagation, ...props }: ButtonProps<ClickData>) => React.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { ColorResult } from '@uiw/react-color';
2
3
  import { OptionalListener } from '../../Hooks/useListener';
3
4
  export type ColorInputProps<OnChangeData> = {
4
5
  defaultValue?: string;
@@ -6,7 +7,6 @@ export type ColorInputProps<OnChangeData> = {
6
7
  label?: string;
7
8
  onChangeColor?: (newColor: string) => void;
8
9
  onOpen?: (currentColor: string) => void;
9
- onChangeColorComplete?: (newColor: string) => void;
10
10
  onClose?: (newColor: string) => void;
11
11
  disableAlpha?: boolean;
12
12
  presetColors?: string[];
@@ -14,7 +14,7 @@ export type ColorInputProps<OnChangeData> = {
14
14
  disabled?: boolean;
15
15
  error?: string;
16
16
  className?: string;
17
- } & OptionalListener<'onChange', OnChangeData>;
18
- declare function ColorInput<OnChangeData>({ defaultValue, value, label, onChangeColor, onChangeColorComplete, onOpen, onClose, disableAlpha, presetColors, error, sharedColorKey, disabled, className, ...otherProps }: ColorInputProps<OnChangeData>): React.JSX.Element;
17
+ } & OptionalListener<'onChange', OnChangeData, ColorResult>;
18
+ declare function ColorInput<OnChangeData>({ defaultValue, value, label, onChangeColor, onOpen, onClose, disableAlpha, presetColors, error, sharedColorKey, disabled, className, ...otherProps }: ColorInputProps<OnChangeData>): React.JSX.Element;
19
19
  declare const ColorInputMemo: typeof ColorInput;
20
20
  export { ColorInputMemo as ColorInput };
@@ -1,4 +1,4 @@
1
- export declare function useSharedSelectedColor(key?: string, numberSavedColors?: number): {
1
+ export declare function useSharedSelectedColor(key: string | undefined, predefinedColors?: string[], numberSavedColors?: number): {
2
2
  colors: string[];
3
3
  addColor: (newColor: string) => void;
4
4
  };
@@ -0,0 +1,11 @@
1
+ import { AnyObject, InferType, Maybe, ObjectSchema } from 'yup';
2
+ export declare function useYupResolver<ObjectType extends Maybe<AnyObject>>(validationSchema: ObjectSchema<ObjectType>, translate: (key: string, args?: Record<string, string | number>) => string): (data: InferType<ObjectSchema<ObjectType>>) => Promise<{
3
+ values: ObjectType extends AnyObject ? import("yup").MakePartial<ObjectType> extends infer T ? T extends import("yup").MakePartial<ObjectType> ? T extends {} ? { [k in keyof T]: T[k]; } : T : never : never : ObjectType;
4
+ errors: {};
5
+ } | {
6
+ values: {};
7
+ errors: Record<string, {
8
+ type: string;
9
+ message: string;
10
+ }>;
11
+ }>;
@@ -1,11 +1,18 @@
1
1
  import * as React from 'react';
2
2
  import { OptionalListener } from '../../Hooks/useListener';
3
3
  import { SelectOption } from '../Select/Select';
4
+ import { ReactNode } from 'react';
4
5
  import { RbmComponentProps } from '../../RbmComponentProps';
5
6
  export type SearchSelectInputProps<OnChangeData> = RbmComponentProps<{
6
7
  label?: string;
7
8
  options: SelectOption[];
8
9
  onChangeValue?: (newValues: string[]) => void;
9
10
  values: string[];
11
+ renderSelectableOptions?: (option: SelectOption, isActive: boolean, index: number, activeIndex: number) => ReactNode;
12
+ renderSelectedOption?: (option: SelectOption) => ReactNode;
13
+ showSelectedOptions?: boolean;
14
+ closeOnSelect?: boolean;
15
+ enableSearch?: boolean;
16
+ allowDeselect?: boolean;
10
17
  } & OptionalListener<'onChange', OnChangeData>>;
11
- export declare const SearchSelectInput: <OnChangeData>({ label, options, values, onChangeValue, className, style, }: SearchSelectInputProps<OnChangeData>) => React.JSX.Element;
18
+ export declare const SearchSelectInput: <OnChangeData>({ label, options, values, onChangeValue, className, renderSelectableOptions, renderSelectedOption, showSelectedOptions, closeOnSelect, enableSearch, allowDeselect, style, }: SearchSelectInputProps<OnChangeData>) => React.JSX.Element;
@@ -5,5 +5,6 @@ import { Override } from '../../../TypeHelpers';
5
5
  import { OptionalListener } from '../../Hooks/useListener';
6
6
  export type SliderProps<OnChangeData, OnChangeValueData, OnChangeDoneData> = RbmComponentProps<Override<Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>, {
7
7
  value?: number;
8
+ stopPropagation?: boolean;
8
9
  } & OptionalListener<'onChange', OnChangeData> & OptionalListener<'onChangeValue', OnChangeValueData, number> & OptionalListener<'onChangeDone', OnChangeDoneData>>>;
9
- export declare const Slider: <OnChangeData, OnChangeValueData, OnChangeDoneData>({ className, style, ...props }: SliderProps<OnChangeData, OnChangeValueData, OnChangeDoneData>) => React.JSX.Element;
10
+ export declare const Slider: <OnChangeData, OnChangeValueData, OnChangeDoneData>({ className, style, stopPropagation, ...props }: SliderProps<OnChangeData, OnChangeValueData, OnChangeDoneData>) => React.JSX.Element;
@@ -10,5 +10,8 @@ export type SwitchProps<OnChangeCheckedData> = RbmComponentProps<Override<InputH
10
10
  isLabelBeforeSwitch?: boolean;
11
11
  isDual?: boolean;
12
12
  error?: string;
13
+ classNameLabel?: string;
14
+ classNamePreLabel?: string;
15
+ stopPropagation?: boolean;
13
16
  } & OptionalListener<"onChangeChecked", OnChangeCheckedData, boolean>>>;
14
- export declare const Switch: <OnChangeCheckedData>({ children, label, preLabel, isLabelBeforeSwitch, isDual, id, className, style, error, onChange, ...props }: SwitchProps<OnChangeCheckedData>) => React.JSX.Element;
17
+ export declare const Switch: <OnChangeCheckedData>({ children, label, preLabel, isLabelBeforeSwitch, isDual, stopPropagation, id, className, classNamePreLabel, classNameLabel, style, error, onChange, ...props }: SwitchProps<OnChangeCheckedData>) => React.JSX.Element;
@@ -7,10 +7,11 @@ export type SpoilerProps<OnClickData> = RbmComponentProps<{
7
7
  title: ReactChild;
8
8
  initialOpen?: boolean;
9
9
  open?: boolean;
10
+ onlyTitleToggles?: boolean;
10
11
  noClosingAnimation?: boolean;
11
12
  openIcon?: IconSource | null;
12
13
  closeIcon?: IconSource | null;
13
14
  } & OptionalListener<'onClick', OnClickData>>;
14
- declare function Spoiler<OnClickData>({ title, children, initialOpen, noClosingAnimation, openIcon, closeIcon, className, style, open, ...listenerProps }: SpoilerProps<OnClickData>): React.JSX.Element;
15
+ declare function Spoiler<OnClickData>({ title, children, initialOpen, noClosingAnimation, openIcon, closeIcon, className, onlyTitleToggles, style, open, ...listenerProps }: SpoilerProps<OnClickData>): React.JSX.Element;
15
16
  declare const SpoilerMemo: typeof Spoiler;
16
17
  export { SpoilerMemo as Spoiler };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainias42/react-bootstrap-mobile",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Mobile React Components using Bootstrap",
5
5
  "main": "dist/bootstrapReactMobile",
6
6
  "scripts": {
@@ -54,7 +54,6 @@
54
54
  "@fortawesome/react-fontawesome": "^0.2.2",
55
55
  "@types/react": ">=18.0.0",
56
56
  "@types/react-beautiful-dnd": "^13.1.8",
57
- "@types/react-color": "^3.0.12",
58
57
  "@types/react-dom": ">=18.0.0",
59
58
  "@types/react-table": "^7.7.20",
60
59
  "@types/react-window": "^1.8.8",
@@ -95,12 +94,14 @@
95
94
  "dependencies": {
96
95
  "@ainias42/js-helper": ">=0.8.16",
97
96
  "@types/react-virtualized-auto-sizer": "^1.0.4",
97
+ "@uiw/react-color": "^2.5.5",
98
98
  "classnames": "^2.5.1",
99
99
  "isomorphic-style-loader": "^5.3.2",
100
100
  "react-color": "^2.19.3",
101
101
  "react-table": "^7.8.0",
102
102
  "react-virtualized-auto-sizer": "^1.0.24",
103
103
  "react-window": "^1.8.10",
104
- "react-window-infinite-loader": "^1.0.9"
104
+ "react-window-infinite-loader": "^1.0.9",
105
+ "yup": "^1.6.1"
105
106
  }
106
107
  }
@@ -11,7 +11,7 @@ import {
11
11
  useEffect,
12
12
  MouseEvent,
13
13
  PointerEvent,
14
- useRef, DragEventHandler
14
+ useRef
15
15
  } from 'react';
16
16
  import {withForwardRef} from '../../helper/withForwardRef';
17
17
  import {useComposedRef} from "../Hooks/useComposedRef";
@@ -4,12 +4,10 @@ import { withMemo } from '../../helper/withMemo';
4
4
  import { Block } from '../Layout/Block';
5
5
  import { Text, TEXT_SIZE } from '../Text/Text';
6
6
  import { Clickable } from '../Clickable/Clickable';
7
-
8
7
  import styles from './buttonDialog.scss';
9
8
  import { RbmComponentProps, WithNoChildren } from '../RbmComponentProps';
10
9
  import classNames from 'classnames';
11
10
  import { Flavor } from "../Flavor";
12
- import { Size } from "../../Size";
13
11
 
14
12
  export type ButtonDialogProps = RbmComponentProps<
15
13
  {
@@ -40,7 +40,7 @@ export const DialogBackground = withMemo(function DialogBackground({
40
40
  <Block __allowChildren="all" className={classNames(styles.dialogBackground, className)} style={style}>
41
41
  {(!!title || !!onClose) && <Flex horizontal={true} className={styles.title}>{!!title &&
42
42
  <Grow><Heading >{title}</Heading></Grow>}{!!onClose &&
43
- <Clickable onClick={onClose}><Icon size={"lg"} icon={faCircleXmark}/></Clickable>}</Flex>}
43
+ <Clickable onClick={onClose}><Icon size="lg" icon={faCircleXmark}/></Clickable>}</Flex>}
44
44
  {children}
45
45
  </Block>
46
46
  );
@@ -14,7 +14,7 @@ export type ShowDialog = <
14
14
 
15
15
  const DialogContext = React.createContext<ShowDialog>(() => {
16
16
  console.error("DialogContext not initialized");
17
- return Promise.reject("DialogContext not initialized")
17
+ return Promise.reject(new Error("DialogContext not initialized"));
18
18
  });
19
19
  export const DialogProvider = DialogContext.Provider;
20
20
 
@@ -5,6 +5,7 @@
5
5
  overflow: auto;
6
6
 
7
7
  .title {
8
+ gap: 16px;
8
9
  margin: 0 0 0.8rem;
9
10
  }
10
11
  }
@@ -1,11 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import { Override } from '@ainias42/js-helper';
3
3
  import { OptionalListener, useListenerWithExtractedProps } from '../../Hooks/useListener';
4
-
5
4
  import styles from './button.scss';
6
5
  import classNames from 'classnames';
7
6
  import { withMemo } from '../../../helper/withMemo';
8
- import { HTMLAttributes } from 'react';
7
+ import { HTMLAttributes, MouseEvent, useCallback } from 'react';
9
8
  import { RbmComponentProps } from '../../RbmComponentProps';
10
9
  import { ButtonType } from "./ButtonType";
11
10
  import { Flavor } from "../../Flavor";
@@ -17,6 +16,7 @@ export type ButtonProps<ClickData> = RbmComponentProps<
17
16
  disabled?: boolean;
18
17
  flavor?: Flavor
19
18
  fullWidth?: boolean;
19
+ stopPropagation?: boolean;
20
20
  size?: Omit<Size, "xxLarge" | "xLarge" | "large" | "xSmall">
21
21
  } & OptionalListener<'onClick', ClickData>>
22
22
  >;
@@ -29,10 +29,18 @@ export const Button = withMemo(function Button<ClickData>({
29
29
  fullWidth = false,
30
30
  flavor = Flavor.Accent,
31
31
  type = ButtonType.Primary,
32
+ stopPropagation = true,
32
33
  ...props
33
34
  }: ButtonProps<ClickData>) {
34
35
  const [onClick, otherProps] = useListenerWithExtractedProps<'onClick', ClickData>('onClick', props);
35
36
 
37
+ const realOnClick = useCallback((ev: MouseEvent) => {
38
+ if (stopPropagation) {
39
+ ev.stopPropagation();
40
+ }
41
+ onClick?.(ev);
42
+ }, [onClick, stopPropagation]);
43
+
36
44
  const classes = {
37
45
  [styles.primary]: type === ButtonType.Primary,
38
46
  [styles.secondary]: type === ButtonType.Secondary,
@@ -41,7 +49,7 @@ export const Button = withMemo(function Button<ClickData>({
41
49
  };
42
50
 
43
51
  return (
44
- <button {...otherProps} disabled={disabled} type="button" onClick={onClick}
52
+ <button {...otherProps} disabled={disabled} type="button" onClick={realOnClick}
45
53
  className={classNames(styles.button, {[styles.fullWidth]: fullWidth}, classes, flavor, className)}>
46
54
  {children}
47
55
  </button>
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { useCallback, useRef, useState, MouseEvent } from 'react';
3
- import { Color, ColorChangeHandler, ColorResult, SketchPicker } from 'react-color';
2
+ import { useCallback, useRef, useState, MouseEvent, useMemo } from 'react';
3
+ import { ColorResult, Sketch } from '@uiw/react-color';
4
4
  import { OptionalListener, useListener } from '../../Hooks/useListener';
5
5
  import { withMemo } from '../../../helper/withMemo';
6
6
  import styles from './colorInput.scss';
@@ -16,7 +16,6 @@ export type ColorInputProps<OnChangeData> = {
16
16
  label?: string;
17
17
  onChangeColor?: (newColor: string) => void;
18
18
  onOpen?: (currentColor: string) => void;
19
- onChangeColorComplete?: (newColor: string) => void;
20
19
  onClose?: (newColor: string) => void;
21
20
  disableAlpha?: boolean;
22
21
  presetColors?: string[];
@@ -24,26 +23,13 @@ export type ColorInputProps<OnChangeData> = {
24
23
  disabled?: boolean
25
24
  error?: string;
26
25
  className?: string;
27
- } & OptionalListener<'onChange', OnChangeData>;
28
-
29
- function convertToHex(color: { r: number; g: number; b: number; a?: number }, disableAlpha?: boolean) {
30
- let newColor = `#${color.r.toString(16).padStart(2, '0')}${color.g.toString(16).padStart(2, '0')}${color.b
31
- .toString(16)
32
- .padStart(2, '0')}`;
33
- if (color.a !== undefined && !disableAlpha) {
34
- newColor += Math.round(color.a * 255)
35
- .toString(16)
36
- .padStart(2, '0');
37
- }
38
- return newColor;
39
- }
26
+ } & OptionalListener<'onChange', OnChangeData, ColorResult>;
40
27
 
41
28
  function ColorInput<OnChangeData>({
42
29
  defaultValue,
43
30
  value,
44
31
  label,
45
32
  onChangeColor,
46
- onChangeColorComplete,
47
33
  onOpen,
48
34
  onClose,
49
35
  disableAlpha,
@@ -65,34 +51,34 @@ function ColorInput<OnChangeData>({
65
51
  const [isOpen, setIsOpen] = useState(false);
66
52
  const [position, setPosition] = useState({x: 0, y: 0});
67
53
 
68
- const {colors, addColor} = useSharedSelectedColor(sharedColorKey);
54
+ const {colors, addColor} = useSharedSelectedColor(sharedColorKey, presetColors);
69
55
  const realIsOpen = disabled !== true && isOpen;
70
56
 
71
- const colVal: Color = value ?? color;
57
+ const colVal = useMemo(() => {
58
+ const newVal = value ?? color;
59
+ if (disableAlpha && newVal.length === 9) {
60
+ return newVal.substring(0, 7);
61
+ }
62
+ if (disableAlpha && newVal.length === 5) {
63
+ return newVal.substring(0,4);
64
+ }
65
+ return newVal;
66
+ }, [value, color, disableAlpha]);
67
+
72
68
  // Selectors
73
69
 
74
70
  // Callbacks
75
- const onChangeWithData = useListener<'onChange', OnChangeData>('onChange', otherProps);
76
- const onChange = useCallback<ColorChangeHandler>(
77
- (newColor: ColorResult, e) => {
78
- const hexColor = convertToHex(newColor.rgb, disableAlpha);
71
+ const onChangeWithData = useListener<'onChange', OnChangeData, ColorResult>('onChange', otherProps);
72
+ const onChange = useCallback(
73
+ (newColor: ColorResult) => {
74
+ const hexColor = newColor.hexa;
79
75
  setColor(hexColor);
80
76
  if (onChangeColor) {
81
77
  onChangeColor(hexColor);
82
78
  }
83
- onChangeWithData(e);
84
- },
85
- [disableAlpha, onChangeColor, onChangeWithData]
86
- );
87
- const onChangeComplete = useCallback(
88
- (newColor: ColorResult) => {
89
- const hexColor = convertToHex(newColor.rgb, disableAlpha);
90
- setColor(hexColor);
91
- if (onChangeColorComplete) {
92
- onChangeColorComplete(hexColor);
93
- }
79
+ onChangeWithData(newColor);
94
80
  },
95
- [disableAlpha, onChangeColorComplete]
81
+ [onChangeColor, onChangeWithData]
96
82
  );
97
83
 
98
84
  const onMenuClose = useCallback(
@@ -141,10 +127,9 @@ function ColorInput<OnChangeData>({
141
127
  <>
142
128
  <span className={classNames(styles.colorInput, className)}>
143
129
  <Menu x={position.x} y={position.y} isOpen={realIsOpen} onClose={onMenuClose}>
144
- <SketchPicker
130
+ <Sketch
145
131
  color={colVal}
146
132
  onChange={onChange}
147
- onChangeComplete={onChangeComplete}
148
133
  disableAlpha={disableAlpha}
149
134
  presetColors={presetColors ?? colors}
150
135
  />
@@ -7,6 +7,22 @@
7
7
  height: 1rem;
8
8
  display: inline-block;
9
9
  border: 1px solid black;
10
+ position: relative;
11
+
12
+ &:before {
13
+ content: '';
14
+ position: absolute;
15
+ top: 0;
16
+ left: 0;
17
+ right: 0;
18
+ bottom: 0;
19
+ display: block;
20
+ width: 100%;
21
+ height: 100%;
22
+ z-index: -1;
23
+ background: repeating-conic-gradient(#cccccc 0% 25%, #ffffff 0% 50%);
24
+ }
25
+
10
26
  }
11
27
 
12
28
  .modalContainer {
@@ -31,3 +47,26 @@
31
47
  margin-right: 0.2rem;
32
48
  }
33
49
  }
50
+
51
+
52
+ :global(.w-color-swatch) {
53
+ z-index: 0;
54
+
55
+ > div {
56
+ position: relative;
57
+
58
+ &:before {
59
+ content: '';
60
+ position: absolute;
61
+ top: 0;
62
+ left: 0;
63
+ right: 0;
64
+ bottom: 0;
65
+ display: block;
66
+ width: 100%;
67
+ height: 100%;
68
+ z-index: -1;
69
+ background: repeating-conic-gradient(#cccccc 0% 25%, #ffffff 0% 50%);
70
+ }
71
+ }
72
+ }
@@ -1,9 +1,14 @@
1
- import { useCallback, useMemo, useRef, useState } from 'react';
1
+ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
2
  import { Random } from '@ainias42/js-helper';
3
3
 
4
4
  const sharedSelectedColor: Record<string, { colors: string[]; updateFunctions: (() => void)[] }> = {};
5
5
 
6
- export function useSharedSelectedColor(key?: string, numberSavedColors = 15) {
6
+ function localStorageKey (key: string) {
7
+ return `sharedSelectedColor-${key}`;
8
+ };
9
+
10
+ export function useSharedSelectedColor(key: string|undefined, predefinedColors: string[] = [], numberSavedColors = 16) {
11
+ const shouldSaveToLocalStorage = typeof window !== 'undefined' && window.localStorage !== undefined && !!key;
7
12
  const [, setVersion] = useState(1);
8
13
  const innerKey = useRef(Random.getStringRandom(12));
9
14
  const realKey = key ?? innerKey.current;
@@ -22,6 +27,10 @@ export function useSharedSelectedColor(key?: string, numberSavedColors = 15) {
22
27
 
23
28
  const addColor = useCallback(
24
29
  (newColor: string) => {
30
+ if (predefinedColors.includes(newColor)) {
31
+ return;
32
+ }
33
+
25
34
  sharedSelectedColor[realKey].colors = sharedSelectedColor[realKey].colors.filter(
26
35
  (color) => color !== newColor
27
36
  );
@@ -29,12 +38,32 @@ export function useSharedSelectedColor(key?: string, numberSavedColors = 15) {
29
38
  if (sharedSelectedColor[realKey].colors.length > numberSavedColors) {
30
39
  sharedSelectedColor[realKey].colors.splice(numberSavedColors, 1);
31
40
  }
41
+ if (shouldSaveToLocalStorage){
42
+ localStorage.setItem(localStorageKey(realKey), JSON.stringify(sharedSelectedColor[realKey].colors));
43
+ }
32
44
 
33
45
  // triggers rerender
34
46
  sharedSelectedColor[realKey].updateFunctions.forEach((u) => u());
35
47
  },
36
- [numberSavedColors, realKey]
48
+ [numberSavedColors, predefinedColors, realKey, shouldSaveToLocalStorage]
37
49
  );
38
50
 
39
- return { colors: sharedSelectedColor[realKey]?.colors, addColor };
51
+ useLayoutEffect(() => {
52
+ if (shouldSaveToLocalStorage){
53
+ const savedColors = localStorage.getItem(localStorageKey(realKey));
54
+ if (savedColors) {
55
+ sharedSelectedColor[realKey].colors = JSON.parse(savedColors);
56
+ setVersion((old) => old + 1);
57
+ }
58
+ }
59
+ }, [realKey, shouldSaveToLocalStorage]);
60
+
61
+ const realColors = useMemo(() => {
62
+ if (predefinedColors.length > 0) {
63
+ return [...predefinedColors, ...sharedSelectedColor[realKey].colors.filter(color => !predefinedColors.includes(color))].slice(0, numberSavedColors);
64
+ }
65
+ return sharedSelectedColor[realKey].colors;
66
+ }, [numberSavedColors, predefinedColors, realKey]);
67
+
68
+ return { colors: realColors, addColor };
40
69
  }
@@ -33,8 +33,8 @@ export const FileInputController = withMemo(function FileInputController<Values
33
33
  const setErrorMessage = useCallback((error: string) => {
34
34
  setError(name, {
35
35
  message: error,
36
- })
37
- }, []);
36
+ });
37
+ }, [name, setError]);
38
38
 
39
39
  return (
40
40
  <FileInput
@@ -35,7 +35,7 @@ export const HookForm = withMemo(function HookForm<TFieldValues extends FieldVal
35
35
 
36
36
  return <FormProvider {...methods}>
37
37
  <SendFormContext.Provider value={innerOnSend}>
38
- <LoadingArea loading={methods.formState.isSubmitting} __allowChildren={"all"}>
38
+ <LoadingArea loading={methods.formState.isSubmitting} __allowChildren="all">
39
39
  {children}
40
40
  </LoadingArea>
41
41
  </SendFormContext.Provider>
@@ -0,0 +1,53 @@
1
+ import { useCallback } from 'react';
2
+ import { AnyObject, InferType, Maybe, ObjectSchema, ValidationError } from 'yup';
3
+
4
+ // TODO set translate function from somewhere else
5
+ export function useYupResolver<ObjectType extends Maybe<AnyObject>>(validationSchema: ObjectSchema<ObjectType>, translate: (key: string, args?: Record<string, string | number>) => string) {
6
+
7
+ return useCallback(
8
+ async (data: InferType<ObjectSchema<ObjectType>>) => {
9
+ try {
10
+ const values = await validationSchema.validate(data, {
11
+ abortEarly: false,
12
+ });
13
+
14
+ return {
15
+ values,
16
+ errors: {},
17
+ };
18
+ } catch (errors) {
19
+ const reducedErrors = (errors.inner as ValidationError[]).reduce(
20
+ (allErrors, currentError) => {
21
+ if (currentError.path === undefined) {
22
+ return allErrors;
23
+ }
24
+
25
+ let message = currentError.message as
26
+ | string
27
+ | {
28
+ key: string;
29
+ args?: Record<string, string | number>;
30
+ };
31
+ if (typeof message === 'object') {
32
+ message = translate(message.key, message.args);
33
+ } else {
34
+ message = translate(message);
35
+ }
36
+ allErrors[currentError.path] = {
37
+ type: currentError.type ?? 'validation',
38
+ message,
39
+ };
40
+ return allErrors;
41
+ },
42
+ {} as Record<string, { type: string; message: string }>,
43
+ );
44
+
45
+ return {
46
+ values: {},
47
+ errors: reducedErrors,
48
+ };
49
+ }
50
+ },
51
+ [translate, validationSchema],
52
+ );
53
+ }
@@ -25,5 +25,5 @@ export const FormError = withMemo(function FormError({error}: FormErrorProps) {
25
25
  return null;
26
26
  }
27
27
 
28
- return <Block className={styles.error}><Text>{error}</Text></Block>
28
+ return <Block className={styles.error}><Text>{error}</Text></Block>;
29
29
  }, styles);
@@ -16,7 +16,6 @@ import { Image } from '../../../Image/Image';
16
16
  import { Clickable } from '../../../Clickable/Clickable';
17
17
  import { Inline } from '../../../Layout/Inline';
18
18
  import { FileType } from "./FileType";
19
- import { InlineBlock } from "../../../Layout/InlineBlock";
20
19
  import { FormError } from "../../FormError";
21
20
 
22
21