@ainias42/react-bootstrap-mobile 0.2.13 → 0.2.15
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/bin/updateCopies.js +1 -0
- package/bootstrapReactMobile.ts +1 -0
- package/dist/bootstrapReactMobile.d.ts +1 -0
- package/dist/bootstrapReactMobile.js +201 -68
- package/dist/bootstrapReactMobile.js.map +1 -1
- package/dist/src/Components/FormElements/Button/Button.d.ts +2 -1
- package/dist/src/Components/FormElements/Controller/useYupResolver.d.ts +11 -0
- package/dist/src/Components/FormElements/SearchSelectInput/SearchSelectInput.d.ts +8 -1
- package/dist/src/Components/FormElements/Slider/Slider.d.ts +2 -1
- package/dist/src/Components/FormElements/Switch/Switch.d.ts +4 -1
- package/dist/src/Components/SpoilerList/Spoiler/Spoiler.d.ts +2 -1
- package/package.json +3 -2
- package/src/Components/Dialog/dialogBackground.scss +1 -0
- package/src/Components/FormElements/Button/Button.tsx +11 -3
- package/src/Components/FormElements/ColorInput/ColorInput.tsx +1 -1
- package/src/Components/FormElements/ColorInput/sharedSelectedColor.ts +1 -1
- package/src/Components/FormElements/Controller/useYupResolver.ts +53 -0
- package/src/Components/FormElements/SearchSelectInput/SearchSelectInput.tsx +161 -129
- package/src/Components/FormElements/SearchSelectInput/seachSelectInput.scss +6 -1
- package/src/Components/FormElements/Slider/Slider.tsx +9 -1
- package/src/Components/FormElements/Switch/Switch.tsx +25 -14
- package/src/Components/FormElements/Switch/switch.scss +18 -16
- package/src/Components/Icon/Icon.tsx +0 -1
- package/src/Components/Menu/Menu.tsx +43 -42
- package/src/Components/SpoilerList/Spoiler/Spoiler.tsx +24 -18
|
@@ -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;
|
|
@@ -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.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "Mobile React Components using Bootstrap",
|
|
5
5
|
"main": "dist/bootstrapReactMobile",
|
|
6
6
|
"scripts": {
|
|
@@ -101,6 +101,7 @@
|
|
|
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
|
}
|
|
@@ -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={
|
|
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
2
|
import { useCallback, useRef, useState, MouseEvent, useMemo } from 'react';
|
|
3
|
-
import { ColorResult, Sketch
|
|
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';
|
|
@@ -45,7 +45,7 @@ export function useSharedSelectedColor(key: string|undefined, predefinedColors:
|
|
|
45
45
|
// triggers rerender
|
|
46
46
|
sharedSelectedColor[realKey].updateFunctions.forEach((u) => u());
|
|
47
47
|
},
|
|
48
|
-
[numberSavedColors, realKey, shouldSaveToLocalStorage]
|
|
48
|
+
[numberSavedColors, predefinedColors, realKey, shouldSaveToLocalStorage]
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
useLayoutEffect(() => {
|
|
@@ -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
|
+
}
|
|
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
import { OptionalListener } from '../../Hooks/useListener';
|
|
3
3
|
import { SelectOption } from '../Select/Select';
|
|
4
4
|
import classNames from 'classnames';
|
|
5
|
-
import { ChangeEventHandler, KeyboardEvent, useCallback, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { ChangeEventHandler, KeyboardEvent, ReactNode, useCallback, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { ArrayHelper } from '@ainias42/js-helper';
|
|
7
7
|
import { RbmComponentProps } from '../../RbmComponentProps';
|
|
8
8
|
import { withMemo } from '../../../helper/withMemo';
|
|
@@ -22,147 +22,179 @@ export type SearchSelectInputProps<OnChangeData> = RbmComponentProps<
|
|
|
22
22
|
options: SelectOption[];
|
|
23
23
|
onChangeValue?: (newValues: string[]) => void;
|
|
24
24
|
values: string[];
|
|
25
|
+
renderSelectableOptions?: (option: SelectOption, isActive: boolean, index: number, activeIndex: number) => ReactNode,
|
|
26
|
+
renderSelectedOption?: (option: SelectOption) => ReactNode,
|
|
27
|
+
showSelectedOptions?: boolean;
|
|
28
|
+
closeOnSelect?: boolean;
|
|
29
|
+
enableSearch?: boolean;
|
|
30
|
+
allowDeselect?: boolean;
|
|
25
31
|
} & OptionalListener<'onChange', OnChangeData>
|
|
26
32
|
>;
|
|
27
33
|
|
|
28
34
|
export const SearchSelectInput = withMemo(function SearchSelectInput<OnChangeData>({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
setSelectedIndex(0);
|
|
74
|
-
}, []);
|
|
75
|
-
const onFocus = useCallback(() => updateSuggestionPosition(), [updateSuggestionPosition]);
|
|
76
|
-
|
|
77
|
-
const toggleOption = useCallback(
|
|
78
|
-
(_: any, value: string) => {
|
|
79
|
-
const newValues = [...values];
|
|
80
|
-
const index = values.indexOf(value);
|
|
81
|
-
if (index === -1) {
|
|
82
|
-
newValues.push(value);
|
|
83
|
-
} else {
|
|
84
|
-
newValues.splice(index, 1);
|
|
35
|
+
label,
|
|
36
|
+
options,
|
|
37
|
+
values,
|
|
38
|
+
onChangeValue,
|
|
39
|
+
className,
|
|
40
|
+
renderSelectableOptions,
|
|
41
|
+
renderSelectedOption,
|
|
42
|
+
showSelectedOptions = false,
|
|
43
|
+
closeOnSelect = false,
|
|
44
|
+
enableSearch = true,
|
|
45
|
+
allowDeselect = true,
|
|
46
|
+
style,
|
|
47
|
+
}: SearchSelectInputProps<OnChangeData>) {
|
|
48
|
+
// Variables
|
|
49
|
+
const indexedOptions = useMemo(() => ArrayHelper.arrayToObject(options, (opt) => opt.value), [options]);
|
|
50
|
+
|
|
51
|
+
// Refs
|
|
52
|
+
const containerRef = useRef<HTMLLabelElement>(null);
|
|
53
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
54
|
+
const window = useWindow();
|
|
55
|
+
|
|
56
|
+
// States
|
|
57
|
+
const [searchText, setSearchText] = useState('');
|
|
58
|
+
const [suggestionsPosition, setSuggestionsPosition] = useState<
|
|
59
|
+
{ top: number; left: number; right: number } | undefined
|
|
60
|
+
>(undefined);
|
|
61
|
+
|
|
62
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
63
|
+
|
|
64
|
+
const selectableOptions = useMemo(() => {
|
|
65
|
+
if (!suggestionsPosition) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return options.filter(
|
|
69
|
+
(option) => (showSelectedOptions || !values.includes(option.value)) && (!enableSearch || option.label.toLowerCase().includes(searchText.toLowerCase()))
|
|
70
|
+
);
|
|
71
|
+
}, [suggestionsPosition, options, showSelectedOptions, values, enableSearch, searchText]);
|
|
72
|
+
|
|
73
|
+
// Selectors
|
|
74
|
+
|
|
75
|
+
// Callbacks
|
|
76
|
+
const updateSuggestionPosition = useCallback(() => {
|
|
77
|
+
if (!containerRef.current) {
|
|
78
|
+
return;
|
|
85
79
|
}
|
|
86
|
-
|
|
80
|
+
const {left, right, bottom: top} = containerRef.current.getBoundingClientRect();
|
|
81
|
+
setSuggestionsPosition({top, left, right: (window?.innerWidth ?? 0) - right});
|
|
82
|
+
}, [window?.innerWidth]);
|
|
83
|
+
|
|
84
|
+
const onChange = useCallback<ChangeEventHandler<HTMLInputElement>>((ev) => {
|
|
85
|
+
if (!enableSearch){
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
setSearchText(ev.target.value);
|
|
87
89
|
setSelectedIndex(0);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
toggleOption(undefined, selectableOptions[selectedIndex].value);
|
|
90
|
+
}, [enableSearch]);
|
|
91
|
+
const onFocus = useCallback(() => updateSuggestionPosition(), [updateSuggestionPosition]);
|
|
92
|
+
|
|
93
|
+
const toggleOption = useCallback(
|
|
94
|
+
(_: any, value: string) => {
|
|
95
|
+
const newValues = [...values];
|
|
96
|
+
const index = values.indexOf(value);
|
|
97
|
+
if (index === -1) {
|
|
98
|
+
newValues.push(value);
|
|
99
|
+
} else {
|
|
100
|
+
newValues.splice(index, 1);
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
-
setSelectedIndex(
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
setSearchText('');
|
|
103
|
+
setSelectedIndex(0);
|
|
104
|
+
onChangeValue?.(newValues);
|
|
105
|
+
if (closeOnSelect) {
|
|
106
|
+
if (containerRef.current?.contains(document.activeElement)) {
|
|
107
|
+
inputRef.current?.focus();
|
|
108
|
+
requestAnimationFrame(() => {
|
|
109
|
+
inputRef.current?.blur();
|
|
110
|
+
});
|
|
105
111
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
[closeOnSelect, onChangeValue, values]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const onKeyPress = useCallback(
|
|
118
|
+
(e: KeyboardEvent<HTMLInputElement>) => {
|
|
119
|
+
if (e.key === 'Enter' && !e.defaultPrevented) {
|
|
120
|
+
if (selectedIndex < selectableOptions.length) {
|
|
121
|
+
toggleOption(undefined, selectableOptions[selectedIndex].value);
|
|
112
122
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
} else if (e.key === 'ArrowDown') {
|
|
124
|
+
setSelectedIndex((old) => {
|
|
125
|
+
if (old + 1 >= selectableOptions.length) {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
return old + 1;
|
|
129
|
+
});
|
|
130
|
+
} else if (e.key === 'ArrowUp') {
|
|
131
|
+
setSelectedIndex((old) => {
|
|
132
|
+
if (old - 1 < 0) {
|
|
133
|
+
return Math.max(selectableOptions.length - 1, 0);
|
|
134
|
+
}
|
|
135
|
+
return old - 1;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
[toggleOption, selectableOptions, selectedIndex]
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Effects
|
|
119
143
|
|
|
120
|
-
|
|
144
|
+
// Other
|
|
121
145
|
|
|
122
|
-
|
|
146
|
+
// Render Functions
|
|
147
|
+
const renderOption = (value: string) => {
|
|
148
|
+
const option = indexedOptions[value];
|
|
149
|
+
if (!option) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
123
152
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
153
|
+
const element = renderSelectedOption?.(option) ?? <InlineBlock className={styles.tag}>
|
|
154
|
+
<Text size={TEXT_SIZE.xSmall}>{indexedOptions[value]?.label}</Text>
|
|
155
|
+
</InlineBlock>;
|
|
156
|
+
|
|
157
|
+
const onClickProps = allowDeselect ? {onClick: toggleOption, onClickData: value} : {};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Clickable {...onClickProps} key={option.key} __allowChildren="all">
|
|
161
|
+
{element}
|
|
162
|
+
</Clickable>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
const renderSelectableOption = (opt: SelectOption, index: number) => {
|
|
166
|
+
const isActive = index === selectedIndex;
|
|
167
|
+
const element = renderSelectableOptions?.(opt, isActive, index, selectedIndex) ?? (
|
|
168
|
+
<Block className={classNames(styles.selectableOption, {[styles.active]: index === selectedIndex})}>
|
|
169
|
+
<Text>{opt.label}</Text>
|
|
170
|
+
</Block>);
|
|
171
|
+
|
|
172
|
+
return <Clickable onClick={toggleOption} onClickData={opt.value} key={opt.key} __allowChildren="all">
|
|
173
|
+
{element}
|
|
174
|
+
</Clickable>;
|
|
175
|
+
};
|
|
129
176
|
|
|
130
177
|
return (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
179
|
+
<label className={classNames(styles.input, className)} style={style} ref={containerRef}>
|
|
180
|
+
{label ? <span className={styles.label}>{label}</span> : null}
|
|
181
|
+
<Flex className={styles.inputContainer} horizontal={true}>
|
|
182
|
+
<InlineBlock>{values.map(renderOption)}</InlineBlock>
|
|
183
|
+
<Grow __allowChildren="html">
|
|
184
|
+
<input
|
|
185
|
+
ref={inputRef}
|
|
186
|
+
className={classNames(styles.text, {[styles.disabled]: !enableSearch})}
|
|
187
|
+
value={searchText}
|
|
188
|
+
onChange={onChange}
|
|
189
|
+
onKeyDown={onKeyPress}
|
|
190
|
+
onFocus={onFocus}
|
|
191
|
+
/>
|
|
192
|
+
</Grow>
|
|
193
|
+
</Flex>
|
|
194
|
+
<InlineBlock className={styles.selectableOptionContainer} style={suggestionsPosition}>
|
|
195
|
+
{selectableOptions.map(renderSelectableOption)}
|
|
134
196
|
</InlineBlock>
|
|
135
|
-
</
|
|
197
|
+
</label>
|
|
136
198
|
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
<Clickable onClick={toggleOption} onClickData={opt.value} key={opt.key}>
|
|
140
|
-
<Block className={classNames(styles.selectableOption, { [styles.active]: index === selectedIndex })}>
|
|
141
|
-
<Text>{opt.label}</Text>
|
|
142
|
-
</Block>
|
|
143
|
-
</Clickable>
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
148
|
-
<label className={classNames(styles.input, className)} style={style} ref={containerRef}>
|
|
149
|
-
{label ? <span className={styles.label}>{label}</span> : null}
|
|
150
|
-
<Flex className={styles.inputContainer} horizontal={true}>
|
|
151
|
-
<InlineBlock>{values.map(renderOption)}</InlineBlock>
|
|
152
|
-
<Grow __allowChildren="html">
|
|
153
|
-
<input
|
|
154
|
-
className={styles.text}
|
|
155
|
-
value={searchText}
|
|
156
|
-
onChange={onChange}
|
|
157
|
-
onKeyDown={onKeyPress}
|
|
158
|
-
onFocus={onFocus}
|
|
159
|
-
/>
|
|
160
|
-
</Grow>
|
|
161
|
-
</Flex>
|
|
162
|
-
<InlineBlock className={styles.selectableOptionContainer} style={suggestionsPosition}>
|
|
163
|
-
{selectableOptions.map(renderSelectableOption)}
|
|
164
|
-
</InlineBlock>
|
|
165
|
-
</label>
|
|
166
|
-
);
|
|
167
|
-
},
|
|
168
|
-
styles);
|
|
199
|
+
},
|
|
200
|
+
styles);
|
|
@@ -34,10 +34,15 @@
|
|
|
34
34
|
|
|
35
35
|
.label {
|
|
36
36
|
display: block;
|
|
37
|
-
font-weight: bold;
|
|
37
|
+
font-weight: var(--label-font-weight, bold);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
.text {
|
|
41
|
+
&.disabled {
|
|
42
|
+
caret-color: transparent;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
width: 100%;
|
|
42
47
|
background-color: transparent;
|
|
43
48
|
border: 0;
|
|
@@ -14,6 +14,7 @@ export type SliderProps<OnChangeData, OnChangeValueData, OnChangeDoneData> = Rbm
|
|
|
14
14
|
Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>,
|
|
15
15
|
{
|
|
16
16
|
value?: number;
|
|
17
|
+
stopPropagation?: boolean;
|
|
17
18
|
} & OptionalListener<'onChange', OnChangeData> &
|
|
18
19
|
OptionalListener<'onChangeValue', OnChangeValueData, number> &
|
|
19
20
|
OptionalListener<'onChangeDone', OnChangeDoneData>
|
|
@@ -23,6 +24,7 @@ export type SliderProps<OnChangeData, OnChangeValueData, OnChangeDoneData> = Rbm
|
|
|
23
24
|
export const Slider = withMemo(function Slider<OnChangeData, OnChangeValueData, OnChangeDoneData>({
|
|
24
25
|
className,
|
|
25
26
|
style,
|
|
27
|
+
stopPropagation = true,
|
|
26
28
|
...props
|
|
27
29
|
}: SliderProps<OnChangeData, OnChangeValueData, OnChangeDoneData>) {
|
|
28
30
|
// Variables
|
|
@@ -57,6 +59,12 @@ export const Slider = withMemo(function Slider<OnChangeData, OnChangeValueData,
|
|
|
57
59
|
[onChange, onChangeValue]
|
|
58
60
|
);
|
|
59
61
|
|
|
62
|
+
const checkStopPropagation = useCallback((ev: React.MouseEvent) => {
|
|
63
|
+
if (stopPropagation) {
|
|
64
|
+
ev.stopPropagation();
|
|
65
|
+
}
|
|
66
|
+
}, [stopPropagation]);
|
|
67
|
+
|
|
60
68
|
// Effects
|
|
61
69
|
const innerRef = useOnChangeDone(onChangeDone) as MutableRefObject<HTMLInputElement|null>;
|
|
62
70
|
|
|
@@ -66,7 +74,7 @@ export const Slider = withMemo(function Slider<OnChangeData, OnChangeValueData,
|
|
|
66
74
|
|
|
67
75
|
return (
|
|
68
76
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
69
|
-
<label className={classNames(styles.slider, className)} style={style}>
|
|
77
|
+
<label className={classNames(styles.slider, className)} style={style} onClick={checkStopPropagation}>
|
|
70
78
|
<input
|
|
71
79
|
type="range"
|
|
72
80
|
{...otherPropsWithoutData}
|