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.
@@ -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 || !onPress;
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';
@@ -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 | DocumentResult[]) => void): Promise<void> => {
18
- const omittedOptions = omitBy(options, isNil);
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
- [multiple, onCancel, onError, options],
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
- // this will create properties with default empty array values even if they don't exist in the children
48
- const defaultContext = allowedChildren.reduce(
49
- (context, childName) => {
50
- return {
51
- ...context,
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 childArray = Children.toArray(children);
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
- return childArray.reduce((context, child) => {
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
- return {
65
- ...context,
66
- rest: [...(context as any).rest, child],
67
- };
64
+ (result as any).rest.push(child);
68
65
  }
69
- return context;
66
+ return;
70
67
  }
71
68
 
72
- const displayName = (child.type as FC)?.displayName as string | undefined;
69
+ const displayName = (child.type as FC)?.displayName as T | undefined;
73
70
 
74
- if (!displayName || !allowedChildren.includes(displayName as T)) {
75
- // Unmatched elements go to rest if includeRest is enabled
76
- if (includeRest) {
77
- return {
78
- ...context,
79
- rest: [...(context as any).rest, child],
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
- return context;
78
+
79
+ return;
80
+ }
81
+
82
+ if (includeRest) {
83
+ (result as any).rest.push(child);
83
84
  }
85
+ });
84
86
 
85
- return {
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.10",
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.15",
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.15",
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
- }: DocumentPickerOptions): Promise<DocumentResult | DocumentResult[]> => {
42
+ onCancel,
43
+ onError,
44
+ }: DocumentPickerOptions): Promise<DocumentResult[]> => {
35
45
  // SSR guard
36
46
  if (Platform.OS !== 'web') {
37
- return { type: 'cancel' };
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
- if (input.files) {
55
- const response: Promise<DocumentResult>[] = [];
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
- Array.from(input.files).forEach(file => response.push(resolveFileData(file)));
70
+ Array.from(input.files).forEach(file => response.push(resolveFileData(file)));
58
71
 
59
- return resolve(Promise.all(response));
60
- } else {
61
- resolve({ type: 'cancel' });
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: ({ type }: DocumentPickerOptions) => getDocumentAsyncWeb({ type, multiple: false }),
74
- pickMultiple: ({ type }: DocumentPickerOptions) =>
75
- getDocumentAsyncWeb({ type, multiple: true }),
99
+ pickSingle,
100
+ pickMultiple,
76
101
  };
@@ -12,7 +12,6 @@ export type DocumentPickerOptions = Omit<RNDocumentPickerOptions, 'allowMultiSel
12
12
  multiple?: boolean;
13
13
  /**
14
14
  * runs when the DocumentPicker is cancelled
15
- * currently, only supported on IOS and Android
16
15
  */
17
16
  onCancel?: () => void;
18
17
  /**
@@ -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;