@ainias42/react-bootstrap-mobile 0.1.9 → 0.1.11
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/.eslintrc.json +2 -0
- package/bin/release.sh +5 -0
- package/bin/updateCopies.js +1 -0
- package/bootstrapReactMobile.ts +1 -0
- package/dist/bootstrapReactMobile.d.ts +1 -0
- package/dist/bootstrapReactMobile.js +383 -97
- package/dist/bootstrapReactMobile.js.map +1 -1
- package/dist/src/Components/FormElements/CheckBox/Checkbox.d.ts +5 -3
- package/dist/src/Components/FormElements/ImageInput/MultipleFileInput.d.ts +20 -0
- package/dist/src/Components/FormElements/Textarea/Textarea.d.ts +1 -1
- package/dist/src/Components/FullScreen/FullScreen.d.ts +4 -5
- package/dist/src/Components/Layout/Grid/GridItem.d.ts +4 -1
- package/package.json +1 -1
- package/src/Components/FormElements/CheckBox/Checkbox.tsx +39 -10
- package/src/Components/FormElements/CheckBox/checkbox.scss +2 -0
- package/src/Components/FormElements/ColorInput/ColorInput.tsx +16 -3
- package/src/Components/FormElements/ImageInput/ImageInput.tsx +2 -1
- package/src/Components/FormElements/ImageInput/MultipleFileInput.tsx +240 -0
- package/src/Components/FormElements/ImageInput/imageInput.scss +41 -6
- package/src/Components/FormElements/Textarea/Textarea.tsx +5 -5
- package/src/Components/FullScreen/FullScreen.tsx +5 -10
- package/src/Components/Layout/Grid/GridItem.tsx +15 -0
- package/src/Components/Layout/Grid/grid.scss +23 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { RbmComponentProps } from '../../RbmComponentProps';
|
|
2
2
|
import { InputHTMLAttributes } from 'react';
|
|
3
|
-
|
|
3
|
+
import { Override } from '@ainias42/js-helper';
|
|
4
|
+
import { OptionalListener } from '../../Hooks/useListener';
|
|
5
|
+
export type CheckboxProps<OnChangeData, OnChangeCheckedData> = RbmComponentProps<Override<InputHTMLAttributes<HTMLInputElement>, {
|
|
4
6
|
label?: string;
|
|
5
7
|
children?: string;
|
|
6
8
|
isLabelBeforeCheckbox?: boolean;
|
|
7
|
-
} &
|
|
8
|
-
declare function Checkbox({ children, label, isLabelBeforeCheckbox, id, className, style, ...props }: CheckboxProps): JSX.Element;
|
|
9
|
+
} & OptionalListener<'onChange', OnChangeData> & OptionalListener<'onChangeChecked', OnChangeCheckedData, boolean>>>;
|
|
10
|
+
declare function Checkbox<OnChangeData, OnChangeCheckedData>({ children, label, isLabelBeforeCheckbox, id, className, style, ...props }: CheckboxProps<OnChangeData, OnChangeCheckedData>): JSX.Element;
|
|
9
11
|
declare const tmp: typeof Checkbox;
|
|
10
12
|
export { tmp as Checkbox };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RbmComponentProps } from '../../RbmComponentProps';
|
|
2
|
+
import { Override } from '../../../TypeHelpers';
|
|
3
|
+
import { InputHTMLAttributes } from 'react';
|
|
4
|
+
import { Listener } from '../../Hooks/useListener';
|
|
5
|
+
export type FileType = {
|
|
6
|
+
name: string;
|
|
7
|
+
url: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
uploaded?: boolean;
|
|
10
|
+
blob?: Blob;
|
|
11
|
+
};
|
|
12
|
+
export type MultipleImageInputProps<OnChangeFilesData> = RbmComponentProps<Override<Omit<InputHTMLAttributes<HTMLInputElement>, 'defaultValue' | 'onChange'>, {
|
|
13
|
+
value: FileType[];
|
|
14
|
+
label?: string;
|
|
15
|
+
mimeTypes?: string[];
|
|
16
|
+
maxFiles?: number;
|
|
17
|
+
maxSizePerFile?: number;
|
|
18
|
+
onError?: (error: string) => void;
|
|
19
|
+
} & Listener<'onChangeFiles', OnChangeFilesData, FileType[]>>>;
|
|
20
|
+
export declare const MultipleFileInput: <OnChangeData>({ className, style, value, label, mimeTypes, maxFiles, maxSizePerFile, onError, ...otherProps }: MultipleImageInputProps<OnChangeData>) => JSX.Element;
|
|
@@ -7,6 +7,6 @@ export type TextareaProps<OnChangeData> = RbmComponentProps<Override<TextareaHTM
|
|
|
7
7
|
onChangeText?: (newText: string) => void;
|
|
8
8
|
onEnter?: (newText: string) => void;
|
|
9
9
|
} & OptionalListener<'onChange', OnChangeData>>>;
|
|
10
|
-
declare function Textarea<OnChangeData>({ label, className, style,
|
|
10
|
+
declare function Textarea<OnChangeData>({ label, className, style, onKeyUp, onChangeText, onEnter, ...otherProps }: TextareaProps<OnChangeData>): JSX.Element;
|
|
11
11
|
declare const TextareaMemo: typeof Textarea;
|
|
12
12
|
export { TextareaMemo as Textarea };
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { ComponentPropsWithoutRef
|
|
2
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
3
3
|
import { Override } from '../../TypeHelpers';
|
|
4
|
-
|
|
4
|
+
import { RbmComponentProps } from '../RbmComponentProps';
|
|
5
|
+
export type FullScreenProps<AsType extends keyof JSX.IntrinsicElements> = RbmComponentProps<Override<ComponentPropsWithoutRef<AsType>, {
|
|
5
6
|
as?: AsType;
|
|
6
7
|
fullscreenKey?: string;
|
|
7
8
|
onEnterFullscreen?: () => void;
|
|
8
9
|
onLeaveFullscreen?: () => void;
|
|
9
10
|
}>>;
|
|
10
|
-
declare function FullScreen<AsTag extends keyof JSX.IntrinsicElements = 'span'>({ children, as, fullscreenKey, onEnterFullscreen, onLeaveFullscreen, ...otherProps }: FullScreenProps<AsTag>): React.
|
|
11
|
-
ref: React.RefObject<React.ComponentRef<AsTag>>;
|
|
12
|
-
}, string | React.JSXElementConstructor<any>>;
|
|
11
|
+
declare function FullScreen<AsTag extends keyof JSX.IntrinsicElements = 'span'>({ children, as, fullscreenKey, onEnterFullscreen, onLeaveFullscreen, ...otherProps }: FullScreenProps<AsTag>): React.DetailedReactHTMLElement<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
|
|
13
12
|
declare const FullScreenMemo: typeof FullScreen;
|
|
14
13
|
export { FullScreenMemo as FullScreen };
|
|
@@ -7,19 +7,22 @@ export type GridItemProps = RbmComponentProps<{
|
|
|
7
7
|
lg?: number;
|
|
8
8
|
xl?: number;
|
|
9
9
|
xxl?: number;
|
|
10
|
+
print?: number;
|
|
10
11
|
startXs?: number;
|
|
11
12
|
startSm?: number;
|
|
12
13
|
startMd?: number;
|
|
13
14
|
startLg?: number;
|
|
14
15
|
startXl?: number;
|
|
15
16
|
startXxl?: number;
|
|
17
|
+
startPrint?: number;
|
|
16
18
|
orderXs?: number;
|
|
17
19
|
orderSm?: number;
|
|
18
20
|
orderMd?: number;
|
|
19
21
|
orderLg?: number;
|
|
20
22
|
orderXl?: number;
|
|
21
23
|
orderXxl?: number;
|
|
24
|
+
orderPrint?: number;
|
|
22
25
|
}>;
|
|
23
|
-
declare function GridItem({ style, children, className, __allowChildren, size, sm, md, lg, xl, xxl, startXs, startMd, startSm, startLg, startXl, startXxl, orderXs, orderSm, orderMd, orderLg, orderXxl, orderXl, }: GridItemProps): JSX.Element;
|
|
26
|
+
declare function GridItem({ style, children, className, __allowChildren, size, sm, md, lg, xl, xxl, print, startXs, startMd, startSm, startLg, startXl, startXxl, startPrint, orderXs, orderSm, orderMd, orderLg, orderXxl, orderXl, orderPrint, }: GridItemProps): JSX.Element;
|
|
24
27
|
declare const GridItemMemo: typeof GridItem;
|
|
25
28
|
export { GridItemMemo as GridItem };
|
package/package.json
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { RbmComponentProps } from '../../RbmComponentProps';
|
|
3
|
-
import { InputHTMLAttributes } from 'react';
|
|
3
|
+
import { ChangeEventHandler, InputHTMLAttributes, useCallback } from 'react';
|
|
4
4
|
|
|
5
5
|
import styles from './checkbox.scss';
|
|
6
6
|
import { withMemo } from '../../../helper/withMemo';
|
|
7
7
|
import classNames from 'classnames';
|
|
8
|
+
import { Override } from '@ainias42/js-helper';
|
|
9
|
+
import { OptionalListener, useListenerWithExtractedProps } from '../../Hooks/useListener';
|
|
8
10
|
|
|
9
|
-
export type CheckboxProps = RbmComponentProps<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export type CheckboxProps<OnChangeData, OnChangeCheckedData> = RbmComponentProps<
|
|
12
|
+
Override<
|
|
13
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
14
|
+
{
|
|
15
|
+
label?: string;
|
|
16
|
+
children?: string;
|
|
17
|
+
isLabelBeforeCheckbox?: boolean;
|
|
18
|
+
} & OptionalListener<'onChange', OnChangeData> &
|
|
19
|
+
OptionalListener<'onChangeChecked', OnChangeCheckedData, boolean>
|
|
20
|
+
>
|
|
15
21
|
>;
|
|
16
22
|
|
|
17
|
-
function Checkbox({
|
|
23
|
+
function Checkbox<OnChangeData, OnChangeCheckedData>({
|
|
18
24
|
children,
|
|
19
25
|
label = '',
|
|
20
26
|
isLabelBeforeCheckbox = false,
|
|
@@ -22,7 +28,7 @@ function Checkbox({
|
|
|
22
28
|
className,
|
|
23
29
|
style,
|
|
24
30
|
...props
|
|
25
|
-
}: CheckboxProps) {
|
|
31
|
+
}: CheckboxProps<OnChangeData, OnChangeCheckedData>) {
|
|
26
32
|
// Variables
|
|
27
33
|
|
|
28
34
|
// States
|
|
@@ -30,6 +36,23 @@ function Checkbox({
|
|
|
30
36
|
// Refs
|
|
31
37
|
|
|
32
38
|
// Callbacks
|
|
39
|
+
const [onChange, otherPropsWithoutChange] = useListenerWithExtractedProps<'onChange', OnChangeData>(
|
|
40
|
+
'onChange',
|
|
41
|
+
props
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const [onChangeChecked, otherPropsWithoutData] = useListenerWithExtractedProps<
|
|
45
|
+
'onChangeChecked',
|
|
46
|
+
OnChangeCheckedData
|
|
47
|
+
>('onChangeChecked', otherPropsWithoutChange);
|
|
48
|
+
|
|
49
|
+
const onChangeInner = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
|
50
|
+
(e) => {
|
|
51
|
+
onChangeChecked(e.target.checked);
|
|
52
|
+
onChange(e);
|
|
53
|
+
},
|
|
54
|
+
[onChange, onChangeChecked]
|
|
55
|
+
);
|
|
33
56
|
|
|
34
57
|
// Effects
|
|
35
58
|
|
|
@@ -49,7 +72,13 @@ function Checkbox({
|
|
|
49
72
|
<span className={classNames(styles.checkbox, className)} style={style}>
|
|
50
73
|
<label htmlFor={id} key={id}>
|
|
51
74
|
<span className={styles.label}>{preLabel}</span>
|
|
52
|
-
<input
|
|
75
|
+
<input
|
|
76
|
+
{...otherPropsWithoutData}
|
|
77
|
+
type="checkbox"
|
|
78
|
+
id={id}
|
|
79
|
+
className={styles.input}
|
|
80
|
+
onChange={onChangeInner}
|
|
81
|
+
/>
|
|
53
82
|
<span className={styles.checkmark} />
|
|
54
83
|
<span className={styles.label}>{label}</span>
|
|
55
84
|
</label>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useCallback, useRef, useState, MouseEvent } from 'react';
|
|
2
|
+
import { useCallback, useRef, useState, MouseEvent, useEffect, useLayoutEffect } from 'react';
|
|
3
3
|
import { Color, ColorChangeHandler, ColorResult, SketchPicker } from 'react-color';
|
|
4
4
|
import { OptionalListener, useListener } from '../../Hooks/useListener';
|
|
5
5
|
import { withMemo } from '../../../helper/withMemo';
|
|
@@ -50,6 +50,7 @@ function ColorInput<OnChangeData>({
|
|
|
50
50
|
|
|
51
51
|
// Refs
|
|
52
52
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
53
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
53
54
|
|
|
54
55
|
// States
|
|
55
56
|
const [color, setColor] = useState<string>(value ?? defaultValue ?? '#000000FF');
|
|
@@ -89,7 +90,6 @@ function ColorInput<OnChangeData>({
|
|
|
89
90
|
(e: MouseEvent) => {
|
|
90
91
|
if (e.target === containerRef?.current) {
|
|
91
92
|
setIsOpen(false);
|
|
92
|
-
console.log('onContainerClick', colVal);
|
|
93
93
|
addColor(colVal);
|
|
94
94
|
onClose?.(colVal);
|
|
95
95
|
}
|
|
@@ -107,6 +107,19 @@ function ColorInput<OnChangeData>({
|
|
|
107
107
|
);
|
|
108
108
|
|
|
109
109
|
// Effects
|
|
110
|
+
useLayoutEffect(() => {
|
|
111
|
+
if (!modalRef.current) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const dimension = modalRef.current.getBoundingClientRect();
|
|
115
|
+
if (dimension.right > window.innerWidth || dimension.bottom > window.innerHeight) {
|
|
116
|
+
const newPosition = {
|
|
117
|
+
x: Math.max(0, Math.min(window.innerWidth - dimension.width, position.x)),
|
|
118
|
+
y: Math.max(0, Math.min(window.innerHeight - dimension.height, position.y)),
|
|
119
|
+
};
|
|
120
|
+
setPosition(newPosition);
|
|
121
|
+
}
|
|
122
|
+
}, [position]);
|
|
110
123
|
|
|
111
124
|
// Other
|
|
112
125
|
|
|
@@ -115,7 +128,7 @@ function ColorInput<OnChangeData>({
|
|
|
115
128
|
<span className={styles.colorInput}>
|
|
116
129
|
{isOpen ? (
|
|
117
130
|
<div onClick={onContainerClick} className={styles.modalContainer} ref={containerRef}>
|
|
118
|
-
<div className={styles.modal} style={{ top: position.y, left: position.x }}>
|
|
131
|
+
<div className={styles.modal} style={{ top: position.y, left: position.x }} ref={modalRef}>
|
|
119
132
|
<SketchPicker
|
|
120
133
|
color={colVal}
|
|
121
134
|
onChange={onChange}
|
|
@@ -23,6 +23,7 @@ export type ImageInputProps<OnChangeData> = RbmComponentProps<
|
|
|
23
23
|
>
|
|
24
24
|
>;
|
|
25
25
|
|
|
26
|
+
// TODO use MultipleFileInput internal
|
|
26
27
|
function ImageInput<OnChangeData>({
|
|
27
28
|
className,
|
|
28
29
|
style,
|
|
@@ -79,7 +80,7 @@ function ImageInput<OnChangeData>({
|
|
|
79
80
|
|
|
80
81
|
return (
|
|
81
82
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
82
|
-
<label className={classNames(styles.
|
|
83
|
+
<label className={classNames(styles.fileInput, className)} style={style}>
|
|
83
84
|
{label ? <span>{label}</span> : null}
|
|
84
85
|
<img
|
|
85
86
|
src={(value ?? image)?.url}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { RbmComponentProps } from '../../RbmComponentProps';
|
|
3
|
+
import { Override } from '../../../TypeHelpers';
|
|
4
|
+
import { ChangeEventHandler, DragEvent, DragEventHandler, InputHTMLAttributes, useCallback, useRef } from 'react';
|
|
5
|
+
import { Listener, useListenerWithExtractedProps } from '../../Hooks/useListener';
|
|
6
|
+
|
|
7
|
+
import styles from './imageInput.scss';
|
|
8
|
+
import { withMemo } from '../../../helper/withMemo';
|
|
9
|
+
import classNames from 'classnames';
|
|
10
|
+
import { Block } from '../../Layout/Block';
|
|
11
|
+
import { Text } from '../../Text/Text';
|
|
12
|
+
import { Flex } from '../../Layout/Flex';
|
|
13
|
+
import { Grow } from '../../Layout/Grow';
|
|
14
|
+
import { Icon } from '../../Icon/Icon';
|
|
15
|
+
import { faPlus, faTimesCircle } from '@fortawesome/free-solid-svg-icons';
|
|
16
|
+
import { Image } from '../../Image/Image';
|
|
17
|
+
import { Clickable } from '../../Clickable/Clickable';
|
|
18
|
+
import { Inline } from '../../Layout/Inline';
|
|
19
|
+
|
|
20
|
+
export type FileType = { name: string; url: string; mimeType: string; uploaded?: boolean; blob?: Blob };
|
|
21
|
+
|
|
22
|
+
export type MultipleImageInputProps<OnChangeFilesData> = RbmComponentProps<
|
|
23
|
+
Override<
|
|
24
|
+
Omit<InputHTMLAttributes<HTMLInputElement>, 'defaultValue' | 'onChange'>,
|
|
25
|
+
{
|
|
26
|
+
value: FileType[];
|
|
27
|
+
label?: string;
|
|
28
|
+
mimeTypes?: string[];
|
|
29
|
+
maxFiles?: number;
|
|
30
|
+
maxSizePerFile?: number;
|
|
31
|
+
onError?: (error: string) => void;
|
|
32
|
+
} & Listener<'onChangeFiles', OnChangeFilesData, FileType[]>
|
|
33
|
+
>
|
|
34
|
+
>;
|
|
35
|
+
|
|
36
|
+
export const MultipleFileInput = withMemo(function MultipleImageInput<OnChangeData>({
|
|
37
|
+
className,
|
|
38
|
+
style,
|
|
39
|
+
value,
|
|
40
|
+
label,
|
|
41
|
+
mimeTypes = ['image/*'],
|
|
42
|
+
maxFiles = 1,
|
|
43
|
+
maxSizePerFile = 1024 * 1024 * 10,
|
|
44
|
+
onError,
|
|
45
|
+
...otherProps
|
|
46
|
+
}: MultipleImageInputProps<OnChangeData>) {
|
|
47
|
+
// Variables
|
|
48
|
+
|
|
49
|
+
// Refs
|
|
50
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
51
|
+
|
|
52
|
+
// States
|
|
53
|
+
|
|
54
|
+
// Selectors
|
|
55
|
+
|
|
56
|
+
// Callbacks
|
|
57
|
+
const checkMimeType = useCallback(
|
|
58
|
+
(fileType: string) => {
|
|
59
|
+
return mimeTypes.some((type) => {
|
|
60
|
+
if (type === '*/*' || type === '*') {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (type.endsWith('/*')) {
|
|
64
|
+
return fileType.startsWith(type.substring(0, type.length - 2));
|
|
65
|
+
}
|
|
66
|
+
return fileType === type;
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
[mimeTypes]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const [onChangeFiles, ...props] = useListenerWithExtractedProps('onChangeFiles', otherProps);
|
|
73
|
+
const getBase64 = useCallback((inputFiles: Blob[]) => {
|
|
74
|
+
const promises = inputFiles.map(
|
|
75
|
+
(file) =>
|
|
76
|
+
new Promise<string>((resolve, reject) => {
|
|
77
|
+
const reader = new FileReader();
|
|
78
|
+
reader.onload = () => {
|
|
79
|
+
resolve(reader.result as string);
|
|
80
|
+
};
|
|
81
|
+
reader.onerror = reject;
|
|
82
|
+
reader.readAsDataURL(file);
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
return Promise.all(promises);
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
const onNewFiles = useCallback(
|
|
89
|
+
async (newFiles: File[]) => {
|
|
90
|
+
if (newFiles.length + value.length > maxFiles) {
|
|
91
|
+
onError?.(`Es sind nur ${maxFiles} Dateien erlaubt.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (newFiles.some((file) => file.size > maxSizePerFile)) {
|
|
96
|
+
onError?.(`Eine Datei ist zu groß. Jede Datei darf nur ${maxSizePerFile / 1024 / 1024}MB groß sein.`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (newFiles.some((file) => !checkMimeType(file.type))) {
|
|
101
|
+
onError?.('Eine Datei ist im falschen Format');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const newUrls = await getBase64(newFiles);
|
|
106
|
+
const newValue = newFiles.map((file, index) => ({
|
|
107
|
+
name: file.name,
|
|
108
|
+
url: newUrls[index],
|
|
109
|
+
mimeType: file.type,
|
|
110
|
+
blob: file,
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
onChangeFiles([...value, ...newValue]);
|
|
114
|
+
},
|
|
115
|
+
[checkMimeType, getBase64, maxFiles, maxSizePerFile, onChangeFiles, onError, value]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const onInputChange = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
|
119
|
+
async (e) => {
|
|
120
|
+
if (!e.target.files || e.target.files.length === 0) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const newFiles = Array.from(e.target.files);
|
|
125
|
+
await onNewFiles(newFiles);
|
|
126
|
+
},
|
|
127
|
+
[onNewFiles]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const removeFile = useCallback(
|
|
131
|
+
(_: any, index: number) => {
|
|
132
|
+
if (index >= 0 && index < value.length) {
|
|
133
|
+
const newData = [...value];
|
|
134
|
+
newData.splice(index, 1);
|
|
135
|
+
onChangeFiles(newData);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
[onChangeFiles, value]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const onDrop = useCallback<DragEventHandler>(
|
|
142
|
+
async (event) => {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
|
|
145
|
+
const files: File[] = [];
|
|
146
|
+
if (event.dataTransfer.items) {
|
|
147
|
+
for (let i = 0; i < event.dataTransfer.items.length; i++) {
|
|
148
|
+
if (event.dataTransfer.items[i].kind === 'file') {
|
|
149
|
+
const file = event.dataTransfer.items[i].getAsFile();
|
|
150
|
+
if (file) {
|
|
151
|
+
files.push(file);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
for (let i = 0; i < event.dataTransfer.files.length; i++) {
|
|
157
|
+
files.push(event.dataTransfer.files[i]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await onNewFiles(files);
|
|
162
|
+
},
|
|
163
|
+
[onNewFiles]
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const onDragOver = useCallback((e: DragEvent) => e.preventDefault(), []);
|
|
167
|
+
|
|
168
|
+
// Effects
|
|
169
|
+
|
|
170
|
+
// Other
|
|
171
|
+
|
|
172
|
+
// Render Functions
|
|
173
|
+
const renderFile = (file: FileType) => {
|
|
174
|
+
if (file.mimeType.startsWith('image/')) {
|
|
175
|
+
return (
|
|
176
|
+
<Image
|
|
177
|
+
key={file.url}
|
|
178
|
+
src={file.url}
|
|
179
|
+
alt={file.name}
|
|
180
|
+
className={classNames(styles.previewImage, file.url)}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
// TODO style
|
|
185
|
+
return (
|
|
186
|
+
<Block>
|
|
187
|
+
<Text>{file.name}</Text>
|
|
188
|
+
</Block>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<label
|
|
194
|
+
className={classNames(styles.fileInput, className)}
|
|
195
|
+
style={style}
|
|
196
|
+
onDrop={onDrop}
|
|
197
|
+
onDragOver={onDragOver}
|
|
198
|
+
>
|
|
199
|
+
<Flex horizontal={true}>
|
|
200
|
+
{!!label && (
|
|
201
|
+
<Grow>
|
|
202
|
+
<Text>{label}</Text>
|
|
203
|
+
</Grow>
|
|
204
|
+
)}
|
|
205
|
+
{maxFiles > 1 && (
|
|
206
|
+
<Inline>
|
|
207
|
+
<Text>
|
|
208
|
+
{value.length}/{maxFiles}
|
|
209
|
+
</Text>
|
|
210
|
+
</Inline>
|
|
211
|
+
)}
|
|
212
|
+
</Flex>
|
|
213
|
+
<Flex horizontal={true} className={styles.previewContainer}>
|
|
214
|
+
{value?.map((file, index) => (
|
|
215
|
+
<Grow className={styles.preview} center={true} key={file.url}>
|
|
216
|
+
{renderFile(file)}
|
|
217
|
+
<Clickable className={styles.previewRemove} onClick={removeFile} onClickData={index}>
|
|
218
|
+
<Icon icon={faTimesCircle} />
|
|
219
|
+
</Clickable>
|
|
220
|
+
</Grow>
|
|
221
|
+
))}
|
|
222
|
+
{value.length < maxFiles && (
|
|
223
|
+
<Grow className={styles.addFile} center={true} __allowChildren="html">
|
|
224
|
+
<Icon icon={faPlus} />
|
|
225
|
+
<input
|
|
226
|
+
{...props}
|
|
227
|
+
ref={inputRef}
|
|
228
|
+
className={styles.value}
|
|
229
|
+
onChange={onInputChange}
|
|
230
|
+
type="file"
|
|
231
|
+
multiple={maxFiles > 1}
|
|
232
|
+
accept={mimeTypes.join(', ')}
|
|
233
|
+
/>
|
|
234
|
+
</Grow>
|
|
235
|
+
)}
|
|
236
|
+
</Flex>
|
|
237
|
+
</label>
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
styles);
|
|
@@ -1,18 +1,53 @@
|
|
|
1
|
-
.
|
|
1
|
+
.fileInput {
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: column;
|
|
4
4
|
position: relative;
|
|
5
5
|
cursor: pointer;
|
|
6
6
|
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
.previewContainer {
|
|
8
|
+
align-items: stretch;
|
|
9
|
+
flex-grow: 1;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
.preview {
|
|
12
|
+
position: relative;
|
|
13
|
+
border: 1px solid var(--border-light);
|
|
14
|
+
|
|
15
|
+
.previewRemove {
|
|
16
|
+
position: absolute;
|
|
17
|
+
top: 5px;
|
|
18
|
+
right: 5px;
|
|
19
|
+
opacity: 0;
|
|
20
|
+
font-size: 1.5rem;
|
|
21
|
+
|
|
22
|
+
svg {
|
|
23
|
+
background: white;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&:hover .previewRemove {
|
|
28
|
+
opacity: 0.7;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.previewImage {
|
|
33
|
+
width: 100%;
|
|
34
|
+
height: 100%;
|
|
35
|
+
object-fit: contain;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.addFile {
|
|
39
|
+
position: relative;
|
|
40
|
+
border-radius: 3px;
|
|
41
|
+
background-color: var(--border-light);
|
|
42
|
+
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
align-items: center;
|
|
46
|
+
font-size: 2rem;
|
|
13
47
|
}
|
|
14
48
|
}
|
|
15
49
|
|
|
50
|
+
|
|
16
51
|
.value {
|
|
17
52
|
position: absolute;
|
|
18
53
|
top: 0;
|
|
@@ -23,7 +23,7 @@ function Textarea<OnChangeData>({
|
|
|
23
23
|
label,
|
|
24
24
|
className,
|
|
25
25
|
style,
|
|
26
|
-
|
|
26
|
+
onKeyUp,
|
|
27
27
|
onChangeText,
|
|
28
28
|
onEnter,
|
|
29
29
|
...otherProps
|
|
@@ -48,14 +48,14 @@ function Textarea<OnChangeData>({
|
|
|
48
48
|
|
|
49
49
|
const realOnKeyPress = useCallback(
|
|
50
50
|
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
51
|
-
if (
|
|
52
|
-
|
|
51
|
+
if (onKeyUp) {
|
|
52
|
+
onKeyUp(e);
|
|
53
53
|
}
|
|
54
54
|
if (onEnter && e.key === 'Enter' && !e.defaultPrevented) {
|
|
55
55
|
onEnter((e.target as HTMLTextAreaElement).value);
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
|
-
[onEnter,
|
|
58
|
+
[onEnter, onKeyUp]
|
|
59
59
|
);
|
|
60
60
|
|
|
61
61
|
// Effects
|
|
@@ -67,7 +67,7 @@ function Textarea<OnChangeData>({
|
|
|
67
67
|
return (
|
|
68
68
|
<label className={classNames(styles.container, className)} style={style}>
|
|
69
69
|
{label ? <span className={styles.label}>{label}</span> : null}
|
|
70
|
-
<textarea {...otherProps}
|
|
70
|
+
<textarea {...otherProps} onKeyUp={realOnKeyPress} className={styles.textarea} onChange={onChange} />
|
|
71
71
|
</label>
|
|
72
72
|
);
|
|
73
73
|
}
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ComponentPropsWithoutRef,
|
|
4
|
-
ComponentRef,
|
|
5
|
-
PropsWithChildren,
|
|
6
|
-
useCallback,
|
|
7
|
-
useEffect,
|
|
8
|
-
useMemo,
|
|
9
|
-
useRef,
|
|
10
|
-
} from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ComponentRef, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
11
3
|
import { Override } from '../../TypeHelpers';
|
|
12
4
|
import { withMemo } from '../../helper/withMemo';
|
|
13
5
|
import { useWindow } from '../../WindowContext/WindowContext';
|
|
6
|
+
import { RbmComponentProps } from '../RbmComponentProps';
|
|
14
7
|
|
|
15
|
-
export type FullScreenProps<AsType extends keyof JSX.IntrinsicElements> =
|
|
8
|
+
export type FullScreenProps<AsType extends keyof JSX.IntrinsicElements> = RbmComponentProps<
|
|
16
9
|
Override<
|
|
17
10
|
ComponentPropsWithoutRef<AsType>,
|
|
18
11
|
{ as?: AsType; fullscreenKey?: string; onEnterFullscreen?: () => void; onLeaveFullscreen?: () => void }
|
|
@@ -81,6 +74,8 @@ function FullScreen<AsTag extends keyof JSX.IntrinsicElements = 'span'>({
|
|
|
81
74
|
// Render Functions
|
|
82
75
|
const element = as ?? 'span';
|
|
83
76
|
const props = useMemo(() => ({ ...otherProps, ref: containerRef }), [otherProps]);
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
78
|
+
// @ts-ignore
|
|
84
79
|
return React.createElement(element, props, children);
|
|
85
80
|
}
|
|
86
81
|
|