@ainias42/react-bootstrap-mobile 0.1.8 → 0.1.10
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/.npnignore +2 -0
- package/bin/release.sh +6 -1
- package/bin/updateCopies.js +2 -6
- package/bootstrapReactMobile.ts +1 -0
- package/dist/bootstrapReactMobile.d.ts +1 -0
- package/dist/bootstrapReactMobile.js +866 -734
- package/dist/bootstrapReactMobile.js.map +1 -0
- package/dist/src/Components/FormElements/Button/Button.d.ts +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/Layout/Container.d.ts +1 -4
- package/dist/src/Components/Layout/Grid/GridItem.d.ts +4 -1
- package/dist/src/Components/RbmComponentProps.d.ts +3 -4
- package/package.json +2 -2
- package/src/Components/FormElements/Button/Button.tsx +1 -1
- package/src/Components/FormElements/CheckBox/Checkbox.tsx +39 -10
- 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/Layout/Container.tsx +2 -6
- package/src/Components/Layout/Grid/GridItem.tsx +15 -0
- package/src/Components/Layout/Grid/grid.scss +23 -0
- package/src/Components/List/List.tsx +15 -17
- package/src/Components/RbmComponentProps.ts +3 -3
- package/webpack.config.js +1 -1
|
@@ -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;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
import { RbmComponentProps } from '../RbmComponentProps';
|
|
3
2
|
export declare const CONTAINER_CLASSES: {
|
|
4
3
|
sm: string;
|
|
@@ -10,6 +9,4 @@ export declare const CONTAINER_CLASSES: {
|
|
|
10
9
|
export type ContainerProps = RbmComponentProps<{
|
|
11
10
|
fluid?: boolean | keyof typeof CONTAINER_CLASSES;
|
|
12
11
|
}>;
|
|
13
|
-
declare
|
|
14
|
-
declare const ContainerMemo: typeof Container;
|
|
15
|
-
export { ContainerMemo as Container };
|
|
12
|
+
export declare const Container: ({ fluid, className, children, style }: ContainerProps) => JSX.Element;
|
|
@@ -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 };
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
2
|
import { Recursive } from '../TypeHelpers';
|
|
3
|
-
type
|
|
3
|
+
export type RbmChildWithoutString = Recursive<JSX.Element | undefined | null | RbmChildWithoutString[]> | false;
|
|
4
4
|
export type WithNoStringProps = {
|
|
5
|
-
children?:
|
|
5
|
+
children?: RbmChildWithoutString;
|
|
6
6
|
__allowChildren?: 'html';
|
|
7
7
|
} | {
|
|
8
8
|
children?: ReactNode;
|
|
9
9
|
__allowChildren: 'text' | 'all';
|
|
10
10
|
};
|
|
11
11
|
export type WithNoStringAndChildrenProps = {
|
|
12
|
-
children:
|
|
12
|
+
children: RbmChildWithoutString;
|
|
13
13
|
__allowChildren?: 'html';
|
|
14
14
|
} | {
|
|
15
15
|
children: ReactNode;
|
|
@@ -28,4 +28,3 @@ export type RbmComponentProps<SpecialProps, ChildrenProps = WithNoStringProps> =
|
|
|
28
28
|
className?: string;
|
|
29
29
|
style?: CSSProperties;
|
|
30
30
|
} & SpecialProps;
|
|
31
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainias42/react-bootstrap-mobile",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Mobile React Components using Bootstrap",
|
|
5
5
|
"main": "dist/bootstrapReactMobile",
|
|
6
6
|
"scripts": {
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
|
82
82
|
"classnames": "^2.3.1",
|
|
83
83
|
"isomorphic-style-loader": "^5.3.2",
|
|
84
|
-
"@ainias42/js-helper": ">=0.
|
|
84
|
+
"@ainias42/js-helper": ">=0.8.1",
|
|
85
85
|
"react-color": "^2.19.3",
|
|
86
86
|
"react-table": "^7.7.0",
|
|
87
87
|
"react-virtualized-auto-sizer": "^1.0.7",
|
|
@@ -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;
|
|
@@ -17,7 +17,7 @@ export type ContainerProps = RbmComponentProps<{
|
|
|
17
17
|
fluid?: boolean | keyof typeof CONTAINER_CLASSES;
|
|
18
18
|
}>;
|
|
19
19
|
|
|
20
|
-
function Container({ fluid, className, children, style }: ContainerProps) {
|
|
20
|
+
export const Container = withMemo(function Container({ fluid, className, children, style }: ContainerProps) {
|
|
21
21
|
// Variables
|
|
22
22
|
|
|
23
23
|
// Refs
|
|
@@ -50,8 +50,4 @@ function Container({ fluid, className, children, style }: ContainerProps) {
|
|
|
50
50
|
{children}
|
|
51
51
|
</div>
|
|
52
52
|
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Need ContainerMemo for autocompletion of phpstorm
|
|
56
|
-
const ContainerMemo = withMemo(Container, styles);
|
|
57
|
-
export { ContainerMemo as Container };
|
|
53
|
+
}, styles);
|
|
@@ -13,18 +13,21 @@ export type GridItemProps = RbmComponentProps<{
|
|
|
13
13
|
lg?: number;
|
|
14
14
|
xl?: number;
|
|
15
15
|
xxl?: number;
|
|
16
|
+
print?: number;
|
|
16
17
|
startXs?: number;
|
|
17
18
|
startSm?: number;
|
|
18
19
|
startMd?: number;
|
|
19
20
|
startLg?: number;
|
|
20
21
|
startXl?: number;
|
|
21
22
|
startXxl?: number;
|
|
23
|
+
startPrint?: number;
|
|
22
24
|
orderXs?: number;
|
|
23
25
|
orderSm?: number;
|
|
24
26
|
orderMd?: number;
|
|
25
27
|
orderLg?: number;
|
|
26
28
|
orderXl?: number;
|
|
27
29
|
orderXxl?: number;
|
|
30
|
+
orderPrint?: number;
|
|
28
31
|
}>;
|
|
29
32
|
|
|
30
33
|
function GridItem({
|
|
@@ -38,18 +41,21 @@ function GridItem({
|
|
|
38
41
|
lg,
|
|
39
42
|
xl,
|
|
40
43
|
xxl,
|
|
44
|
+
print,
|
|
41
45
|
startXs,
|
|
42
46
|
startMd,
|
|
43
47
|
startSm,
|
|
44
48
|
startLg,
|
|
45
49
|
startXl,
|
|
46
50
|
startXxl,
|
|
51
|
+
startPrint,
|
|
47
52
|
orderXs,
|
|
48
53
|
orderSm,
|
|
49
54
|
orderMd,
|
|
50
55
|
orderLg,
|
|
51
56
|
orderXxl,
|
|
52
57
|
orderXl,
|
|
58
|
+
orderPrint,
|
|
53
59
|
}: GridItemProps) {
|
|
54
60
|
// Variables
|
|
55
61
|
|
|
@@ -69,6 +75,9 @@ function GridItem({
|
|
|
69
75
|
if (xxl) {
|
|
70
76
|
classes.push(`item-xxl-${xxl}`);
|
|
71
77
|
}
|
|
78
|
+
if (print) {
|
|
79
|
+
classes.push(`item-print-${print}`);
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
if (startXs) {
|
|
74
83
|
classes.push(`start-xs-${startXs}`);
|
|
@@ -88,6 +97,9 @@ function GridItem({
|
|
|
88
97
|
if (startXxl) {
|
|
89
98
|
classes.push(`start-xxl-${startXxl}`);
|
|
90
99
|
}
|
|
100
|
+
if (startPrint) {
|
|
101
|
+
classes.push(`start-print-${startPrint}`);
|
|
102
|
+
}
|
|
91
103
|
|
|
92
104
|
if (orderXs) {
|
|
93
105
|
classes.push(`order-xs-${orderXs}`);
|
|
@@ -107,6 +119,9 @@ function GridItem({
|
|
|
107
119
|
if (orderXxl) {
|
|
108
120
|
classes.push(`order-xxl-${orderXxl}`);
|
|
109
121
|
}
|
|
122
|
+
if (orderPrint) {
|
|
123
|
+
classes.push(`order-print-${orderPrint}`);
|
|
124
|
+
}
|
|
110
125
|
|
|
111
126
|
// Refs
|
|
112
127
|
|