react-native-molecules 0.5.0-beta.10 → 0.5.0-beta.12
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/components/Checkbox/CheckboxBase.tsx +23 -128
- package/components/Checkbox/utils.ts +0 -25
- package/components/DatePickerInput/DatePickerInput.tsx +4 -2
- package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -4
- package/components/DatePickerInput/types.ts +1 -3
- package/components/DatePickerModal/CalendarEdit.tsx +10 -9
- package/components/FilePicker/FilePicker.tsx +47 -76
- package/components/HelperText/HelperText.tsx +0 -35
- package/components/IconButton/utils.ts +140 -25
- package/components/Select/Select.tsx +12 -9
- package/components/Tabs/TabItem.tsx +35 -58
- package/components/Tabs/TabLabel.tsx +5 -9
- package/components/Tabs/Tabs.tsx +154 -149
- package/components/Tabs/utils.ts +15 -2
- package/components/TextInput/TextInput.tsx +657 -574
- package/components/TextInput/index.tsx +19 -3
- package/components/TextInput/types.ts +76 -27
- package/components/TextInput/utils.ts +225 -145
- package/components/TimePickerField/TimePickerField.tsx +7 -5
- package/components/TouchableRipple/TouchableRipple.native.tsx +1 -1
- package/components/TouchableRipple/TouchableRipple.tsx +1 -1
- package/hooks/index.tsx +0 -5
- package/hooks/useFilePicker.tsx +6 -16
- package/hooks/useSubcomponents.tsx +31 -33
- package/package.json +3 -3
- package/utils/DocumentPicker/documentPicker.ts +40 -15
- package/utils/DocumentPicker/types.ts +0 -1
- package/hooks/useSearchable.tsx +0 -74
|
@@ -125,7 +125,7 @@ const TouchableRipple = (
|
|
|
125
125
|
ref: any,
|
|
126
126
|
) => {
|
|
127
127
|
// TODO - enable ripple onLongPress, need to check for mobile as well
|
|
128
|
-
const disabled = disabledProp
|
|
128
|
+
const disabled = disabledProp;
|
|
129
129
|
|
|
130
130
|
const componentStyles = touchableRippleStyles;
|
|
131
131
|
|
package/hooks/index.tsx
CHANGED
|
@@ -18,11 +18,6 @@ export { useMediaQuery } from './useMediaQuery';
|
|
|
18
18
|
export { useMergedRefs } from './useMergedRefs';
|
|
19
19
|
export { default as usePrevious } from './usePrevious';
|
|
20
20
|
export * from './useQueryFilter';
|
|
21
|
-
export {
|
|
22
|
-
default as useSearchable,
|
|
23
|
-
type UseSearchableProps,
|
|
24
|
-
useSearchInputProps,
|
|
25
|
-
} from './useSearchable';
|
|
26
21
|
export { default as useSubcomponents, type UseSubcomponentsProps } from './useSubcomponents';
|
|
27
22
|
export * from './useTheme';
|
|
28
23
|
export { default as useToggle } from './useToggle';
|
package/hooks/useFilePicker.tsx
CHANGED
|
@@ -7,15 +7,11 @@ import {
|
|
|
7
7
|
} from '../utils/DocumentPicker';
|
|
8
8
|
import { isNil, omitBy } from '../utils/lodash';
|
|
9
9
|
|
|
10
|
-
export const useFilePicker = ({
|
|
11
|
-
multiple,
|
|
12
|
-
onCancel,
|
|
13
|
-
onError,
|
|
14
|
-
...options
|
|
15
|
-
}: DocumentPickerOptions) => {
|
|
10
|
+
export const useFilePicker = (options: DocumentPickerOptions) => {
|
|
16
11
|
const openFilePicker = useCallback(
|
|
17
|
-
async (callback: (response: DocumentResult
|
|
18
|
-
const
|
|
12
|
+
async (callback: (response: DocumentResult[]) => void) => {
|
|
13
|
+
const { multiple, ...rest } = options;
|
|
14
|
+
const omittedOptions = omitBy(rest, isNil);
|
|
19
15
|
|
|
20
16
|
try {
|
|
21
17
|
let response;
|
|
@@ -30,16 +26,10 @@ export const useFilePicker = ({
|
|
|
30
26
|
} catch (e: any) {
|
|
31
27
|
// eslint-disable-next-line no-console
|
|
32
28
|
console.log('FilePicker Error', e, e?.code);
|
|
33
|
-
|
|
34
|
-
if (e?.code === 'DOCUMENT_PICKER_CANCELED.') {
|
|
35
|
-
onCancel?.();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// It might result in an error.
|
|
39
|
-
onError?.(e);
|
|
29
|
+
// Error and cancel callbacks are handled by DocumentPicker itself
|
|
40
30
|
}
|
|
41
31
|
},
|
|
42
|
-
[
|
|
32
|
+
[options],
|
|
43
33
|
);
|
|
44
34
|
|
|
45
35
|
return { openFilePicker };
|
|
@@ -6,7 +6,7 @@ export type UseSubcomponentsProps<T extends string> = {
|
|
|
6
6
|
/**
|
|
7
7
|
* array of displayName as string
|
|
8
8
|
* */
|
|
9
|
-
allowedChildren: T[];
|
|
9
|
+
allowedChildren: (T | { name: T; allowMultiple?: boolean })[];
|
|
10
10
|
/**
|
|
11
11
|
* If true, also returns the remaining children that don't match any of the allowedChildren
|
|
12
12
|
* in a `rest` property
|
|
@@ -44,49 +44,47 @@ function useSubcomponents<T extends string = string, IncludeRest extends boolean
|
|
|
44
44
|
IncludeRest
|
|
45
45
|
> {
|
|
46
46
|
return useMemo(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
[childName]: [],
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
includeRest ? { rest: [] as ReactNode[] } : {},
|
|
56
|
-
) as UseSubcomponentsResult<T, IncludeRest>;
|
|
47
|
+
const configs = allowedChildren.map(entry =>
|
|
48
|
+
typeof entry === 'string'
|
|
49
|
+
? { name: entry, allowMultiple: true as boolean }
|
|
50
|
+
: { name: entry.name, allowMultiple: entry.allowMultiple ?? true },
|
|
51
|
+
);
|
|
57
52
|
|
|
58
|
-
const
|
|
53
|
+
const nameSet = new Set(configs.map(c => c.name));
|
|
54
|
+
const allowMultipleMap = new Map(configs.map(c => [c.name, c.allowMultiple]));
|
|
59
55
|
|
|
60
|
-
|
|
56
|
+
const result = configs.reduce((acc, { name }) => {
|
|
57
|
+
(acc as any)[name] = [];
|
|
58
|
+
return acc;
|
|
59
|
+
}, (includeRest ? { rest: [] } : {}) as UseSubcomponentsResult<T, IncludeRest>);
|
|
60
|
+
|
|
61
|
+
Children.forEach(children, child => {
|
|
61
62
|
if (!isValidElement(child)) {
|
|
62
|
-
// Non-element children go to rest if includeRest is enabled
|
|
63
63
|
if (includeRest) {
|
|
64
|
-
|
|
65
|
-
...context,
|
|
66
|
-
rest: [...(context as any).rest, child],
|
|
67
|
-
};
|
|
64
|
+
(result as any).rest.push(child);
|
|
68
65
|
}
|
|
69
|
-
return
|
|
66
|
+
return;
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
const displayName = (child.type as FC)?.displayName as
|
|
69
|
+
const displayName = (child.type as FC)?.displayName as T | undefined;
|
|
73
70
|
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
};
|
|
71
|
+
if (displayName && nameSet.has(displayName)) {
|
|
72
|
+
if (allowMultipleMap.get(displayName)) {
|
|
73
|
+
(result as any)[displayName].push(child);
|
|
74
|
+
} else {
|
|
75
|
+
// Only keep the last matching child
|
|
76
|
+
(result as any)[displayName] = [child];
|
|
81
77
|
}
|
|
82
|
-
|
|
78
|
+
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (includeRest) {
|
|
83
|
+
(result as any).rest.push(child);
|
|
83
84
|
}
|
|
85
|
+
});
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
...context,
|
|
87
|
-
[displayName]: [...(context as any)[displayName], child],
|
|
88
|
-
};
|
|
89
|
-
}, defaultContext);
|
|
87
|
+
return result;
|
|
90
88
|
}, [allowedChildren, children, includeRest]);
|
|
91
89
|
}
|
|
92
90
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-molecules",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.12",
|
|
4
4
|
"author": "Thet Aung <thetaung.dev@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"react": "19.1.0",
|
|
57
57
|
"react-native": "0.81.4",
|
|
58
58
|
"react-native-svg": ">=12.1.0",
|
|
59
|
-
"react-native-unistyles": "^3.0.
|
|
59
|
+
"react-native-unistyles": "^3.0.22",
|
|
60
60
|
"react-native-web": "~0.21.1",
|
|
61
61
|
"react-native-reanimated": ">=4.0.0",
|
|
62
62
|
"react-native-redash": ">=18.0.0"
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"react-native": "0.81.4",
|
|
74
74
|
"react-native-builder-bob": "^0.17.1",
|
|
75
75
|
"react-native-reanimated": "~4.1.1",
|
|
76
|
-
"react-native-unistyles": "^3.0.
|
|
76
|
+
"react-native-unistyles": "^3.0.22",
|
|
77
77
|
"react-native-web": "~0.21.1"
|
|
78
78
|
},
|
|
79
79
|
"eslintIgnore": [
|
|
@@ -2,6 +2,14 @@ import { Platform } from 'react-native';
|
|
|
2
2
|
|
|
3
3
|
import type { DocumentPickerOptions, DocumentResult } from './types';
|
|
4
4
|
|
|
5
|
+
class OperationCanceledError extends Error {
|
|
6
|
+
code = 'OPERATION_CANCELED';
|
|
7
|
+
constructor() {
|
|
8
|
+
super('user canceled the document picker');
|
|
9
|
+
this.name = 'OperationCanceledError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
const resolveFileData = (file: File): Promise<DocumentResult> => {
|
|
6
14
|
return new Promise((resolve, reject) => {
|
|
7
15
|
const mimeType = file.type;
|
|
@@ -31,10 +39,14 @@ const resolveFileData = (file: File): Promise<DocumentResult> => {
|
|
|
31
39
|
const getDocumentAsyncWeb = async ({
|
|
32
40
|
type = '*/*',
|
|
33
41
|
multiple = false,
|
|
34
|
-
|
|
42
|
+
onCancel,
|
|
43
|
+
onError,
|
|
44
|
+
}: DocumentPickerOptions): Promise<DocumentResult[]> => {
|
|
35
45
|
// SSR guard
|
|
36
46
|
if (Platform.OS !== 'web') {
|
|
37
|
-
|
|
47
|
+
const error = new OperationCanceledError();
|
|
48
|
+
onCancel?.();
|
|
49
|
+
throw error;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
const input = document.createElement('input');
|
|
@@ -49,19 +61,27 @@ const getDocumentAsyncWeb = async ({
|
|
|
49
61
|
|
|
50
62
|
document.body.appendChild(input);
|
|
51
63
|
|
|
52
|
-
return new Promise(resolve => {
|
|
53
|
-
input.addEventListener('change', () => {
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
input.addEventListener('change', async () => {
|
|
66
|
+
try {
|
|
67
|
+
if (input.files && input.files.length > 0) {
|
|
68
|
+
const response: Promise<DocumentResult>[] = [];
|
|
56
69
|
|
|
57
|
-
|
|
70
|
+
Array.from(input.files).forEach(file => response.push(resolveFileData(file)));
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
const results = await Promise.all(response);
|
|
73
|
+
resolve(results);
|
|
74
|
+
} else {
|
|
75
|
+
const error = new OperationCanceledError();
|
|
76
|
+
onCancel?.();
|
|
77
|
+
reject(error);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
onError?.(error);
|
|
81
|
+
reject(error);
|
|
82
|
+
} finally {
|
|
83
|
+
document.body.removeChild(input);
|
|
62
84
|
}
|
|
63
|
-
|
|
64
|
-
document.body.removeChild(input);
|
|
65
85
|
});
|
|
66
86
|
|
|
67
87
|
const event = new MouseEvent('click');
|
|
@@ -69,8 +89,13 @@ const getDocumentAsyncWeb = async ({
|
|
|
69
89
|
});
|
|
70
90
|
};
|
|
71
91
|
|
|
92
|
+
const pickSingle = (options: DocumentPickerOptions = {}) =>
|
|
93
|
+
getDocumentAsyncWeb({ ...options, multiple: false });
|
|
94
|
+
|
|
95
|
+
const pickMultiple = (options: DocumentPickerOptions = {}) =>
|
|
96
|
+
getDocumentAsyncWeb({ ...options, multiple: true });
|
|
97
|
+
|
|
72
98
|
export default {
|
|
73
|
-
pickSingle
|
|
74
|
-
pickMultiple
|
|
75
|
-
getDocumentAsyncWeb({ type, multiple: true }),
|
|
99
|
+
pickSingle,
|
|
100
|
+
pickMultiple,
|
|
76
101
|
};
|
package/hooks/useSearchable.tsx
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { type ReactElement, useCallback, useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
import { IconButton } from '../components/IconButton';
|
|
4
|
-
import {
|
|
5
|
-
TextInput,
|
|
6
|
-
type TextInputElementProps,
|
|
7
|
-
type TextInputProps,
|
|
8
|
-
} from '../components/TextInput';
|
|
9
|
-
import useControlledValue from './useControlledValue';
|
|
10
|
-
|
|
11
|
-
export interface UseSearchableProps {
|
|
12
|
-
searchable?: boolean;
|
|
13
|
-
onQueryChange?: (search: string) => void;
|
|
14
|
-
query?: string;
|
|
15
|
-
searchInputProps?: Omit<TextInputProps, 'value' | 'onChangeText'>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const useSearchInputProps = (props?: Partial<TextInputProps>) => {
|
|
19
|
-
const onChange = props?.onChangeText;
|
|
20
|
-
const onPressClear = useCallback(() => {
|
|
21
|
-
onChange?.('');
|
|
22
|
-
}, [onChange]);
|
|
23
|
-
|
|
24
|
-
const right = useCallback(
|
|
25
|
-
({ color, forceFocus }: TextInputElementProps) => (
|
|
26
|
-
<IconButton
|
|
27
|
-
name={props?.value ? 'close-circle-outline' : 'magnify'}
|
|
28
|
-
onPress={props?.value ? onPressClear : forceFocus}
|
|
29
|
-
color={color}
|
|
30
|
-
/>
|
|
31
|
-
),
|
|
32
|
-
[onPressClear, props?.value],
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
return useMemo(
|
|
36
|
-
() => (props?.right === undefined ? props : { right, ...props }),
|
|
37
|
-
[props, right],
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const useSearchable = ({
|
|
42
|
-
searchable,
|
|
43
|
-
onQueryChange,
|
|
44
|
-
query,
|
|
45
|
-
searchInputProps,
|
|
46
|
-
}: UseSearchableProps = {}): ReactElement<TextInputProps> | null => {
|
|
47
|
-
const [value, onValueChange] = useControlledValue({
|
|
48
|
-
value: query || '',
|
|
49
|
-
defaultValue: '', // to disable the useControlledValue inside TextInput
|
|
50
|
-
onChange: onQueryChange,
|
|
51
|
-
disabled: !searchable,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const props = useSearchInputProps(
|
|
55
|
-
useMemo(
|
|
56
|
-
() => ({
|
|
57
|
-
...searchInputProps,
|
|
58
|
-
value,
|
|
59
|
-
onChangeText: onValueChange,
|
|
60
|
-
}),
|
|
61
|
-
[onValueChange, searchInputProps, value],
|
|
62
|
-
),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
return useMemo(() => {
|
|
66
|
-
if (!searchable || !onQueryChange) return null;
|
|
67
|
-
|
|
68
|
-
return <TextInput {...props} />;
|
|
69
|
-
}, [onQueryChange, props, searchable]);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export { useSearchInputProps };
|
|
73
|
-
|
|
74
|
-
export default useSearchable;
|