react-native-molecules 0.5.0-beta.14 → 0.5.0-beta.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.
@@ -9,7 +9,7 @@ import {
9
9
  } from 'react';
10
10
  import { View } from 'react-native';
11
11
 
12
- import { useControlledValue, useSubcomponents } from '../../hooks';
12
+ import { useControlledValue } from '../../hooks';
13
13
  import { accordionStyles } from './utils';
14
14
 
15
15
  export type Props = Omit<ComponentPropsWithRef<typeof View>, 'children'> & {
@@ -38,8 +38,6 @@ const Accordion = (
38
38
  onChange: onChangeProp,
39
39
  });
40
40
 
41
- const { AccordionItem } = useSubcomponents({ children, allowedChildren: ['AccordionItem'] });
42
-
43
41
  const onPressItem = useCallback(
44
42
  (id: string) => {
45
43
  const isSelected = Array.isArray(expandedItemIds)
@@ -70,9 +68,7 @@ const Accordion = (
70
68
 
71
69
  return (
72
70
  <View style={[accordionStyles.root, style]} {...rest} ref={ref}>
73
- <AccordionContext.Provider value={contextValue}>
74
- {AccordionItem}
75
- </AccordionContext.Provider>
71
+ <AccordionContext.Provider value={contextValue}>{children}</AccordionContext.Provider>
76
72
  </View>
77
73
  );
78
74
  };
@@ -10,8 +10,9 @@ import {
10
10
  } from 'react';
11
11
  import { View, type ViewProps } from 'react-native';
12
12
 
13
- import { useControlledValue, useSubcomponents } from '../../hooks';
13
+ import { useControlledValue } from '../../hooks';
14
14
  import type { WithElements } from '../../types';
15
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
15
16
  import { AccordionContext } from './Accordion';
16
17
  import { accordionItemStyles } from './utils';
17
18
 
@@ -45,9 +46,17 @@ const AccordionItem = memo(
45
46
 
46
47
  const groupContext = useContext(AccordionContext);
47
48
 
48
- const { AccordionItem_Header, AccordionItem_Content } = useSubcomponents({
49
+ const {
50
+ AccordionItem_Header,
51
+ AccordionItem_Content,
52
+ rest: restChildren,
53
+ } = extractSubcomponents({
49
54
  children,
50
- allowedChildren: ['AccordionItem_Header', 'AccordionItem_Content'],
55
+ allowedChildren: [
56
+ { name: 'AccordionItem_Header', allowMultiple: false },
57
+ { name: 'AccordionItem_Content', allowMultiple: false },
58
+ ],
59
+ includeRest: true,
51
60
  });
52
61
 
53
62
  useEffect(() => {
@@ -76,8 +85,9 @@ const AccordionItem = memo(
76
85
  return (
77
86
  <View style={[accordionItemStyles.root, style]} {...rest} ref={ref}>
78
87
  <AccordionItemContext.Provider value={contextValue}>
79
- {AccordionItem_Header[0]}
80
- {contextValue.expanded ? AccordionItem_Content[0] : null}
88
+ {AccordionItem_Header}
89
+ {contextValue.expanded ? AccordionItem_Content : null}
90
+ {restChildren}
81
91
  </AccordionItemContext.Provider>
82
92
  </View>
83
93
  );
@@ -1,11 +1,16 @@
1
- import { memo } from 'react';
1
+ import { memo, useContext } from 'react';
2
2
  import { View, type ViewProps } from 'react-native';
3
3
 
4
+ import { AccordionItemContext } from './utils';
4
5
  import { accordionItemContentStyles } from './utils';
5
6
 
6
7
  export type Props = ViewProps & {};
7
8
 
8
9
  const AccordionItemContent = memo(({ style, children, ...rest }: Props) => {
10
+ const { expanded } = useContext(AccordionItemContext);
11
+
12
+ if (!expanded) return null;
13
+
9
14
  return (
10
15
  <View style={[accordionItemContentStyles.root, style]} {...rest}>
11
16
  {children}
@@ -6,7 +6,7 @@ import type { WithElements } from '../../types';
6
6
  import { resolveStateVariant } from '../../utils';
7
7
  import { Text } from '../Text';
8
8
  import { TouchableRipple, type TouchableRippleProps } from '../TouchableRipple';
9
- import { AccordionItemContext } from './AccordionItem';
9
+ import { AccordionItemContext } from './utils';
10
10
  import { accordionItemHeaderStyles } from './utils';
11
11
 
12
12
  export type AccordionHeaderElementProps = {
@@ -1,7 +1,13 @@
1
+ import { createContext } from 'react';
1
2
  import { StyleSheet } from 'react-native-unistyles';
2
3
 
3
4
  import { getRegisteredComponentStylesWithFallback } from '../../core';
4
5
 
6
+ export const AccordionItemContext = createContext({
7
+ expanded: false,
8
+ onExpandedChange: (_expanded: boolean) => {},
9
+ });
10
+
5
11
  const accordionStylesDefault = StyleSheet.create({
6
12
  root: {},
7
13
  });
@@ -1,7 +1,7 @@
1
1
  import { createContext, memo, useMemo } from 'react';
2
2
  import { View } from 'react-native';
3
3
 
4
- import { useSubcomponents } from '../../hooks';
4
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
5
5
  import { Surface } from '../Surface';
6
6
  import type { AppbarBaseProps, AppbarType } from './types';
7
7
  import { appbarBaseStyles } from './utils';
@@ -26,9 +26,19 @@ const AppbarBase = ({
26
26
  };
27
27
  }, [innerContainerStyleProp, scrolling, style]);
28
28
 
29
- const { Appbar_Left, Appbar_Right, Appbar_Title } = useSubcomponents({
29
+ const {
30
+ Appbar_Left,
31
+ Appbar_Right,
32
+ Appbar_Title,
33
+ rest: restChildren,
34
+ } = extractSubcomponents({
30
35
  children,
31
- allowedChildren: ['Appbar_Left', 'Appbar_Right', 'Appbar_Title'],
36
+ allowedChildren: [
37
+ { name: 'Appbar_Left', allowMultiple: false },
38
+ { name: 'Appbar_Right', allowMultiple: false },
39
+ { name: 'Appbar_Title', allowMultiple: false },
40
+ ],
41
+ includeRest: true,
32
42
  });
33
43
 
34
44
  const contextValue = useMemo(() => ({ type: _type }), [_type]);
@@ -37,17 +47,12 @@ const AppbarBase = ({
37
47
  <Surface elevation={elevation} style={containerStyle} {...rest}>
38
48
  <AppbarContext.Provider value={contextValue}>
39
49
  <View style={innerContainerStyle}>
40
- {Appbar_Left[0]}
41
- <>
42
- {_type === 'center-aligned' || _type === 'small' ? (
43
- Appbar_Title[0]
44
- ) : (
45
- <View />
46
- )}
47
- </>
48
- {Appbar_Right[0]}
50
+ {Appbar_Left}
51
+ <>{_type === 'center-aligned' || _type === 'small' ? Appbar_Title : <View />}</>
52
+ {Appbar_Right}
49
53
  </View>
50
- <>{(_type === 'medium' || _type === 'large') && Appbar_Title[0]}</>
54
+ {(_type === 'medium' || _type === 'large') && Appbar_Title}
55
+ {restChildren}
51
56
  </AppbarContext.Provider>
52
57
  </Surface>
53
58
  );
@@ -10,4 +10,4 @@ export const Button = Object.assign(ButtonBase, {
10
10
  });
11
11
 
12
12
  export type { Props as ButtonProps } from './Button';
13
- export { buttonIconStyles, buttonStyles, buttonTextStyles } from './utils';
13
+ export { ButtonContext, buttonIconStyles, buttonStyles, buttonTextStyles } from './utils';
@@ -1,4 +1,4 @@
1
- import { forwardRef, memo, useCallback, useMemo, useRef, useState } from 'react';
1
+ import { forwardRef, memo, useCallback, useMemo, useRef } from 'react';
2
2
  import { StyleSheet } from 'react-native';
3
3
 
4
4
  import { useLatest, useToggle } from '../../hooks';
@@ -9,6 +9,7 @@ import { TextInput } from '../TextInput';
9
9
  import DatePickerInputModal from './DatePickerInputModal';
10
10
  import DatePickerInputWithoutModal from './DatePickerInputWithoutModal';
11
11
  import type { DatePickerInputProps } from './types';
12
+ import { DatePickerInputContext } from './utils';
12
13
 
13
14
  function DatePickerInput(
14
15
  {
@@ -24,111 +25,99 @@ function DatePickerInput(
24
25
  startYear,
25
26
  endYear,
26
27
  dockedPopoverContentProps,
28
+ children,
27
29
  //locale = 'en',
28
30
  ...rest
29
31
  }: DatePickerInputProps,
30
32
  ref: any,
31
33
  ) {
32
34
  const triggerRef = useRef(null);
33
- const { state: isOpen, onToggle } = useToggle(false);
34
- const [visible, setVisible] = useState<boolean>(false);
35
+ const { state: isOpen, onToggle, handleOpen, handleClose } = useToggle(false);
35
36
 
36
37
  const onDismiss = useCallback(() => {
37
- setVisible(false);
38
- }, [setVisible]);
38
+ handleClose();
39
+ }, [handleClose]);
39
40
 
40
41
  const onChangeRef = useLatest(onChange);
41
42
 
42
43
  const onInnerConfirm = useCallback(
43
44
  ({ date }: any) => {
44
- setVisible(false);
45
+ handleClose();
45
46
  onChangeRef.current(date);
46
47
  },
47
- [setVisible, onChangeRef],
48
+ [handleClose, onChangeRef],
48
49
  );
49
50
 
50
51
  const onPressCalendarIcon = useCallback(() => {
51
52
  if (pickerMode === 'docked') {
52
53
  onToggle();
54
+ } else {
55
+ handleOpen();
53
56
  }
54
- setVisible(true);
55
- }, [pickerMode, onToggle]);
57
+ }, [pickerMode, onToggle, handleOpen]);
56
58
 
57
- const renderers = useMemo(() => {
58
- return {
59
- modal: (
60
- <DatePickerInputModal
61
- date={value}
62
- mode="single"
63
- isOpen={visible}
64
- onClose={onDismiss}
65
- onConfirm={onInnerConfirm}
66
- locale={locale}
67
- validRange={validRange}
68
- />
69
- ),
70
- docked: (
71
- <DatePickerDocked
72
- date={value}
73
- locale={locale}
74
- startYear={startYear}
75
- endYear={endYear}
76
- onChange={onInnerConfirm}
77
- isOpen={isOpen}
78
- onClose={onDismiss}
79
- onToggle={onToggle}
80
- triggerRef={triggerRef}
81
- popoverContentProps={dockedPopoverContentProps}
82
- />
83
- ),
84
- };
85
- }, [
86
- dockedPopoverContentProps,
87
- endYear,
88
- isOpen,
89
- locale,
90
- onDismiss,
91
- onInnerConfirm,
92
- onToggle,
93
- startYear,
94
- validRange,
95
- value,
96
- visible,
97
- ]);
98
-
99
- const rightElement = useMemo(
100
- () => (
101
- <>
102
- {withModal || pickerMode === 'docked' ? (
103
- <>
104
- <IconButton
105
- ref={triggerRef}
106
- style={styles.calendarButton}
107
- name={calendarIcon}
108
- onPress={onPressCalendarIcon}
109
- disabled={disabled}
110
- />
111
- {renderers[pickerMode]}
112
- </>
113
- ) : null}
114
- </>
59
+ const renderers = {
60
+ modal: (
61
+ <DatePickerInputModal
62
+ date={value}
63
+ mode="single"
64
+ isOpen={isOpen}
65
+ onClose={onDismiss}
66
+ onConfirm={onInnerConfirm}
67
+ locale={locale}
68
+ validRange={validRange}
69
+ />
115
70
  ),
116
- [calendarIcon, disabled, onPressCalendarIcon, pickerMode, renderers, withModal],
71
+ docked: (
72
+ <DatePickerDocked
73
+ date={value}
74
+ locale={locale}
75
+ startYear={startYear}
76
+ endYear={endYear}
77
+ onChange={onInnerConfirm}
78
+ isOpen={isOpen}
79
+ onClose={onDismiss}
80
+ onToggle={onToggle}
81
+ triggerRef={triggerRef}
82
+ popoverContentProps={dockedPopoverContentProps}
83
+ />
84
+ ),
85
+ };
86
+
87
+ const contextValue = useMemo(
88
+ () => ({ isOpen, onPressTrigger: onPressCalendarIcon }),
89
+ [isOpen, onPressCalendarIcon],
117
90
  );
118
91
 
119
92
  return (
120
- <DatePickerInputWithoutModal
121
- ref={ref}
122
- {...rest}
123
- disabled={disabled}
124
- value={value}
125
- inputMode={inputMode}
126
- validRange={validRange}
127
- onChange={onChange}
128
- // locale={locale}
129
- >
130
- <TextInput.Right>{rightElement}</TextInput.Right>
131
- </DatePickerInputWithoutModal>
93
+ <DatePickerInputContext value={contextValue}>
94
+ <DatePickerInputWithoutModal
95
+ ref={ref}
96
+ {...rest}
97
+ disabled={disabled}
98
+ value={value}
99
+ inputMode={inputMode}
100
+ validRange={validRange}
101
+ onChange={onChange}
102
+ // locale={locale}
103
+ >
104
+ <TextInput.Right>
105
+ {withModal || pickerMode === 'docked' ? (
106
+ <>
107
+ <IconButton
108
+ ref={triggerRef}
109
+ style={styles.calendarButton}
110
+ name={calendarIcon}
111
+ onPress={onPressCalendarIcon}
112
+ disabled={disabled}
113
+ />
114
+ </>
115
+ ) : null}
116
+ </TextInput.Right>
117
+ {children}
118
+ {renderers[pickerMode]}
119
+ </DatePickerInputWithoutModal>
120
+ </DatePickerInputContext>
132
121
  );
133
122
  }
134
123
 
@@ -7,4 +7,5 @@ export const DatePickerInput = getRegisteredComponentWithFallback(
7
7
  );
8
8
 
9
9
  export type { DatePickerInputProps } from './types';
10
- export { datePickerInputStyles } from './utils';
10
+ export { DatePickerInputContext, datePickerInputStyles } from './utils';
11
+ export type { DatePickerInputContextType } from './utils';
@@ -1,7 +1,16 @@
1
+ import { createContext } from 'react';
1
2
  import { StyleSheet } from 'react-native-unistyles';
2
3
 
3
4
  import { getRegisteredComponentStylesWithFallback } from '../../core';
4
5
 
6
+ export type DatePickerInputContextType = {
7
+ onPressTrigger: () => void;
8
+ };
9
+
10
+ export const DatePickerInputContext = createContext<DatePickerInputContextType>({
11
+ onPressTrigger: () => {},
12
+ });
13
+
5
14
  const datePickerInputStylesDefault = StyleSheet.create({
6
15
  root: {
7
16
  minWidth: 150,
@@ -3,25 +3,36 @@ import { View, type ViewProps } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
 
5
5
  import { getRegisteredComponentStylesWithFallback } from '../../core';
6
- import { useSubcomponents } from '../../hooks';
6
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
7
7
 
8
8
  export type Props = Omit<ViewProps, 'children'> & {
9
9
  children: ReactElement | ReactElement[];
10
10
  };
11
11
 
12
- const allowedChildren = ['Drawer_Footer', 'Drawer_Header', 'Drawer_Content'];
12
+ const allowedChildren = [
13
+ { name: 'Drawer_Footer', allowMultiple: false },
14
+ { name: 'Drawer_Header', allowMultiple: false },
15
+ { name: 'Drawer_Content', allowMultiple: false },
16
+ ];
13
17
 
14
18
  const Drawer = ({ style, children, ...rest }: Props) => {
15
- const { Drawer_Header, Drawer_Footer, Drawer_Content } = useSubcomponents({
19
+ const {
20
+ Drawer_Header,
21
+ Drawer_Footer,
22
+ Drawer_Content,
23
+ rest: restChildren,
24
+ } = extractSubcomponents({
16
25
  children,
17
26
  allowedChildren,
27
+ includeRest: true,
18
28
  });
19
29
 
20
30
  return (
21
31
  <View style={[drawerStyles.root, style]} {...rest}>
22
- {Drawer_Header[0]}
23
- {Drawer_Content[0]}
24
- {Drawer_Footer[0]}
32
+ {Drawer_Header}
33
+ {Drawer_Content}
34
+ {Drawer_Footer}
35
+ {restChildren}
25
36
  </View>
26
37
  );
27
38
  };
@@ -5,6 +5,7 @@ import useFilePicker from '../../hooks/useFilePicker';
5
5
  import type { DocumentPickerOptions, DocumentResult } from '../../utils/DocumentPicker';
6
6
  import { IconButton } from '../IconButton';
7
7
  import { TextInput, type TextInputProps } from '../TextInput';
8
+ import { FilePickerContext } from './utils';
8
9
 
9
10
  export type OmitProp =
10
11
  | 'editable'
@@ -84,14 +85,18 @@ const FilePicker = ({
84
85
  });
85
86
  }, [onValueChange, openFilePicker]);
86
87
 
88
+ const contextValue = useMemo(() => ({ onPressTrigger: onPress }), [onPress]);
89
+
87
90
  return (
88
- <TextInput value={displayText} {...rest} editable={false} ref={ref}>
89
- <TextInput.Label>Choose file</TextInput.Label>
90
- <TextInput.Right>
91
- <IconButton type="material-community" name="upload" onPress={onPress} />
92
- </TextInput.Right>
93
- {children}
94
- </TextInput>
91
+ <FilePickerContext value={contextValue}>
92
+ <TextInput value={displayText} {...rest} editable={false} ref={ref}>
93
+ <TextInput.Label>Choose file</TextInput.Label>
94
+ <TextInput.Right>
95
+ <IconButton type="material-community" name="upload" onPress={onPress} />
96
+ </TextInput.Right>
97
+ {children}
98
+ </TextInput>
99
+ </FilePickerContext>
95
100
  );
96
101
  };
97
102
 
@@ -4,4 +4,5 @@ import FilePickerDefault from './FilePicker';
4
4
  export const FilePicker = getRegisteredComponentWithFallback('FilePicker', FilePickerDefault);
5
5
 
6
6
  export type { Props as FilePickerProps } from './FilePicker';
7
- export { defaultStyles } from './utils';
7
+ export { FilePickerContext, defaultStyles } from './utils';
8
+ export type { FilePickerContextType } from './utils';
@@ -1,7 +1,16 @@
1
+ import { createContext } from 'react';
1
2
  import { StyleSheet } from 'react-native-unistyles';
2
3
 
3
4
  import { getRegisteredComponentStylesWithFallback } from '../../core';
4
5
 
6
+ export type FilePickerContextType = {
7
+ onPressTrigger: () => void;
8
+ };
9
+
10
+ export const FilePickerContext = createContext<FilePickerContextType>({
11
+ onPressTrigger: () => {},
12
+ });
13
+
5
14
  const filePickerStylesDefault = StyleSheet.create({
6
15
  root: {},
7
16
  });
@@ -1,30 +1,36 @@
1
1
  import { memo, type ReactElement } from 'react';
2
2
  import { View, type ViewProps } from 'react-native';
3
3
 
4
- import { useSubcomponents } from '../../hooks';
4
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
5
5
  import { navigationRailStyles } from './utils';
6
6
 
7
7
  export type Props = Omit<ViewProps, 'children'> & {
8
8
  children: ReactElement | ReactElement[];
9
9
  };
10
10
  const allowedChildren = [
11
- 'NavigationRail_Header',
12
- 'NavigationRail_Content',
13
- 'NavigationRail_Footer',
11
+ { name: 'NavigationRail_Header', allowMultiple: false },
12
+ { name: 'NavigationRail_Content', allowMultiple: false },
13
+ { name: 'NavigationRail_Footer', allowMultiple: false },
14
14
  ];
15
15
 
16
16
  const NavigationRail = ({ children, style, ...rest }: Props) => {
17
- const { NavigationRail_Header, NavigationRail_Content, NavigationRail_Footer } =
18
- useSubcomponents({
19
- children,
20
- allowedChildren,
21
- });
17
+ const {
18
+ NavigationRail_Header,
19
+ NavigationRail_Content,
20
+ NavigationRail_Footer,
21
+ rest: restChildren,
22
+ } = extractSubcomponents({
23
+ children,
24
+ allowedChildren,
25
+ includeRest: true,
26
+ });
22
27
 
23
28
  return (
24
29
  <View style={[navigationRailStyles.root, style]} {...rest}>
25
30
  {NavigationRail_Header[0]}
26
31
  {NavigationRail_Content[0]}
27
32
  {NavigationRail_Footer[0]}
33
+ {restChildren}
28
34
  </View>
29
35
  );
30
36
  };
@@ -22,7 +22,8 @@ import {
22
22
  } from 'react-native';
23
23
 
24
24
  import { typedMemo } from '../../hocs';
25
- import { useControlledValue, useSubcomponents } from '../../hooks';
25
+ import { useControlledValue } from '../../hooks';
26
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
26
27
  import { noop } from '../../utils/lodash';
27
28
  import type { TabItemProps } from './TabItem';
28
29
  import { tabsStyles } from './utils';
@@ -81,7 +82,7 @@ export const TabBase = <T extends string | number>({
81
82
  variant,
82
83
  });
83
84
 
84
- const { Tabs_Item: tabItems } = useSubcomponents({
85
+ const { Tabs_Item: tabItems } = extractSubcomponents({
85
86
  children,
86
87
  allowedChildren: ['Tabs_Item'],
87
88
  });
@@ -34,8 +34,8 @@ import {
34
34
  import { useActionState } from '../../hooks/useActionState';
35
35
  import useControlledValue from '../../hooks/useControlledValue';
36
36
  import useLatest from '../../hooks/useLatest';
37
- import useSubcomponents from '../../hooks/useSubcomponents';
38
37
  import { createSyntheticEvent, resolveStateVariant } from '../../utils';
38
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
39
39
  import { HelperText } from '../HelperText';
40
40
  import { Icon } from '../Icon';
41
41
  import { StateLayer } from '../StateLayer';
@@ -215,7 +215,7 @@ const TextInput = ({
215
215
  TextInput_SupportingText,
216
216
  TextInput_Outline,
217
217
  rest: restChildren,
218
- } = useSubcomponents({
218
+ } = extractSubcomponents({
219
219
  children,
220
220
  allowedChildren: [
221
221
  { name: 'TextInput_Label', allowMultiple: false },
@@ -2,41 +2,63 @@ import {
2
2
  createContext,
3
3
  memo,
4
4
  type ReactElement,
5
+ type RefObject,
5
6
  useCallback,
6
7
  useEffect,
7
8
  useMemo,
8
9
  useRef,
9
10
  } from 'react';
10
- import { Text, type ViewProps, type ViewStyle } from 'react-native';
11
11
 
12
- import { useSubcomponents, useToggle } from '../../hooks';
13
- import { Popover, type PopoverProps } from '../Popover';
14
- import { tooltipStyles } from './utils';
12
+ import { useToggle } from '../../hooks';
13
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
15
14
 
16
- export type Props = Omit<PopoverProps, 'isOpen' | 'triggerRef'> & {
15
+ export type Props = {
17
16
  fadeInDelay?: number;
18
17
  fadeOutDelay?: number;
19
- showArrow?: boolean;
20
- style?: ViewStyle;
21
- children: ReactElement | ReactElement[];
22
18
  hoverableContent?: boolean;
19
+ children: ReactElement | ReactElement[];
20
+ };
21
+
22
+ export type TooltipContextValue = {
23
+ isOpen: boolean;
24
+ triggerRef: RefObject<any>;
25
+ onOpen: () => void;
26
+ onClose: () => void;
27
+ onMouseEnter?: () => void;
28
+ onMouseLeave?: () => void;
23
29
  };
24
30
 
31
+ export const TooltipContext = createContext<TooltipContextValue>({
32
+ isOpen: false,
33
+ onOpen: () => {},
34
+ onClose: () => {},
35
+ triggerRef: { current: null },
36
+ });
37
+
25
38
  const Tooltip = ({
26
- style,
27
39
  children,
28
40
  fadeInDelay = 100,
29
41
  fadeOutDelay = 300,
30
- showArrow = false,
31
42
  hoverableContent = false,
32
- ...rest
33
43
  }: Props) => {
34
44
  const { state: isOpen, setState: setIsOpen } = useToggle(false);
35
45
  const triggerRef = useRef(null);
36
46
  const timeOutRef = useRef<NodeJS.Timeout>(undefined);
37
- const popoverTimeoutRef = useRef<NodeJS.Timeout>(undefined);
38
47
  const preventCloseRef = useRef(false);
39
48
 
49
+ const {
50
+ Tooltip_Trigger,
51
+ Tooltip_Content,
52
+ rest: restChildren,
53
+ } = extractSubcomponents({
54
+ children,
55
+ allowedChildren: [
56
+ { name: 'Tooltip_Trigger', allowMultiple: false },
57
+ { name: 'Tooltip_Content', allowMultiple: false },
58
+ ],
59
+ includeRest: true,
60
+ });
61
+
40
62
  const onClose = useCallback(() => {
41
63
  if (preventCloseRef.current) return;
42
64
  clearTimeout(timeOutRef.current);
@@ -48,34 +70,13 @@ const Tooltip = ({
48
70
  timeOutRef.current = setTimeout(() => setIsOpen(true), fadeInDelay);
49
71
  }, [fadeInDelay, setIsOpen]);
50
72
 
51
- // const setPopoverOpen = useCallback(
52
- // (_isOpen: boolean) => {
53
- // clearTimeout(popoverTimeoutRef.current);
54
- // popoverTimeoutRef.current = setTimeout(
55
- // () => setIsOpen(_isOpen),
56
- // isOpen ? fadeInDelay : fadeOutDelay,
57
- // );
58
- // },
59
- // [fadeInDelay, fadeOutDelay, isOpen, setIsOpen],
60
- // );
61
-
62
- const { Tooltip_Trigger, Tooltip_Content } = useSubcomponents({
63
- children,
64
- allowedChildren: ['Tooltip_Trigger', 'Tooltip_Content'],
65
- });
66
-
67
- const contextValue = useMemo(
73
+ const contextValue = useMemo<TooltipContextValue>(
68
74
  () => ({
75
+ isOpen,
69
76
  triggerRef,
70
77
  onOpen,
71
78
  onClose,
72
- }),
73
- [onClose, onOpen],
74
- );
75
-
76
- const { popoverContentProps, popoverStyle } = useMemo(
77
- () => ({
78
- popoverContentProps: (hoverableContent
79
+ ...(hoverableContent
79
80
  ? {
80
81
  onMouseEnter: () => {
81
82
  preventCloseRef.current = true;
@@ -88,50 +89,24 @@ const Tooltip = ({
88
89
  setIsOpen(false);
89
90
  },
90
91
  }
91
- : {}) as ViewProps,
92
- popoverStyle: [tooltipStyles.content, style],
92
+ : {}),
93
93
  }),
94
- [hoverableContent, setIsOpen, style],
94
+ [hoverableContent, isOpen, onClose, onOpen, setIsOpen],
95
95
  );
96
96
 
97
97
  useEffect(() => {
98
- const popoverTimeout = popoverTimeoutRef;
99
-
100
98
  return () => {
101
99
  clearTimeout(timeOutRef.current);
102
- clearTimeout(popoverTimeout.current);
103
100
  };
104
101
  }, []);
105
102
 
106
103
  return (
107
104
  <TooltipContext.Provider value={contextValue}>
108
- {Tooltip_Trigger[0]}
109
- {isOpen && (
110
- <Popover
111
- isOpen={isOpen}
112
- inverted
113
- // placement={placement}
114
- showArrow={showArrow}
115
- // backdropStyles={styles.backdrop}
116
- triggerRef={triggerRef}
117
- // setIsOpen={setPopoverOpen}
118
- {...popoverContentProps}
119
- {...rest}
120
- style={popoverStyle}
121
- // contentTextStyles={contentTextStyles}
122
- // popoverContentProps={popoverContentProps}
123
- onClose={onClose}>
124
- <Text style={tooltipStyles.contentText}>{Tooltip_Content[0]}</Text>
125
- </Popover>
126
- )}
105
+ {Tooltip_Trigger}
106
+ {Tooltip_Content}
107
+ {restChildren}
127
108
  </TooltipContext.Provider>
128
109
  );
129
110
  };
130
111
 
131
- export const TooltipContext = createContext({
132
- onOpen: () => {},
133
- onClose: () => {},
134
- triggerRef: null as any,
135
- });
136
-
137
112
  export default memo(Tooltip);
@@ -1,11 +1,38 @@
1
- import { memo, type ReactElement, type ReactNode } from 'react';
1
+ import { memo, type ReactNode, useContext, useMemo } from 'react';
2
+ import { type StyleProp, Text, type ViewStyle } from 'react-native';
2
3
 
3
- export type Props = {
4
- children: ReactElement | ReactNode;
4
+ import { Popover, type PopoverProps } from '../Popover';
5
+ import { TooltipContext } from './Tooltip';
6
+ import { tooltipStyles } from './utils';
7
+
8
+ export type Props = Omit<PopoverProps, 'isOpen' | 'triggerRef' | 'onClose' | 'children'> & {
9
+ children?: ReactNode;
5
10
  };
6
11
 
7
- const TooltipContent = memo(({ children }: Props) => {
8
- return <>{children}</>;
12
+ const TooltipContent = memo(({ children, style, ...rest }: Props) => {
13
+ const { isOpen, triggerRef, onClose, onMouseEnter, onMouseLeave } = useContext(TooltipContext);
14
+
15
+ const popoverStyle = useMemo<StyleProp<ViewStyle>>(
16
+ () => [tooltipStyles.content, style],
17
+ [style],
18
+ );
19
+
20
+ if (!isOpen) return null;
21
+
22
+ return (
23
+ <Popover
24
+ isOpen={isOpen}
25
+ inverted
26
+ triggerRef={triggerRef}
27
+ onClose={onClose}
28
+ {...rest}
29
+ style={popoverStyle}
30
+ // @ts-ignore — onMouseEnter/onMouseLeave spread onto the inner View via ...rest in Popover
31
+ onMouseEnter={onMouseEnter}
32
+ onMouseLeave={onMouseLeave}>
33
+ <Text style={tooltipStyles.contentText}>{children}</Text>
34
+ </Popover>
35
+ );
9
36
  });
10
37
 
11
38
  TooltipContent.displayName = 'Tooltip_Content';
@@ -10,7 +10,7 @@ export const TooltipDefault = Object.assign(TooltipComponent, {
10
10
 
11
11
  export const Tooltip = getRegisteredComponentWithFallback('Tooltip', TooltipDefault);
12
12
 
13
- export type { Props as TooltipProps } from './Tooltip';
13
+ export type { TooltipContextValue, Props as TooltipProps } from './Tooltip';
14
14
  export type { Props as TooltipContentProps } from './TooltipContent';
15
15
  export type { Props as TooltipTriggerProps } from './TooltipTrigger';
16
16
  export { tooltipStyles } from './utils';
package/hooks/index.tsx CHANGED
@@ -18,6 +18,5 @@ 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 { default as useSubcomponents, type UseSubcomponentsProps } from './useSubcomponents';
22
21
  export * from './useTheme';
23
22
  export { default as useToggle } from './useToggle';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-molecules",
3
- "version": "0.5.0-beta.14",
3
+ "version": "0.5.0-beta.15",
4
4
  "author": "Thet Aung <thetaung.dev@gmail.com>",
5
5
  "license": "MIT",
6
6
  "main": "index.ts",
@@ -0,0 +1,89 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import { Children, type FC, isValidElement } from 'react';
3
+
4
+ export type ExtractSubcomponentsArgs<T extends string> = {
5
+ children: ReactNode;
6
+ /**
7
+ * array of displayName as string
8
+ * */
9
+ allowedChildren: (T | { name: T; allowMultiple?: boolean })[];
10
+ /**
11
+ * If true, also returns the remaining children that don't match any of the allowedChildren
12
+ * in a `rest` property
13
+ */
14
+ includeRest?: boolean;
15
+ };
16
+
17
+ export type ExtractSubcomponentsResult<T extends string, IncludeRest extends boolean = false> = {
18
+ [key in T]: ReactElement[];
19
+ } & (IncludeRest extends true ? { rest: ReactNode[] } : {});
20
+
21
+ /**
22
+ * This will return an object with the displayNames as the property names
23
+ * eg. allowedChildren: ['Drawer_Header', 'Drawer_Content', 'Drawer_Footer', 'DrawerItem'];
24
+ *
25
+ * return value -> {
26
+ * Drawer_Header: [],
27
+ * Drawer_Content: [],
28
+ * Drawer_Footer: [],
29
+ * DrawerItem: [],
30
+ * }
31
+ *
32
+ * If includeRest is true, also returns:
33
+ * {
34
+ * ...above,
35
+ * rest: [remaining children that don't match allowedChildren]
36
+ * }
37
+ * */
38
+ export function extractSubcomponents<
39
+ T extends string = string,
40
+ IncludeRest extends boolean = false,
41
+ >({
42
+ children,
43
+ allowedChildren,
44
+ includeRest,
45
+ }: ExtractSubcomponentsArgs<T> & { includeRest?: IncludeRest }): ExtractSubcomponentsResult<
46
+ T,
47
+ IncludeRest
48
+ > {
49
+ // Single map: name → allowMultiple
50
+ const allowedMap = new Map<T, boolean>();
51
+ for (const entry of allowedChildren) {
52
+ if (typeof entry === 'string') {
53
+ allowedMap.set(entry as T, true);
54
+ } else {
55
+ allowedMap.set(entry.name, entry.allowMultiple ?? true);
56
+ }
57
+ }
58
+
59
+ const result = (includeRest ? { rest: [] } : {}) as ExtractSubcomponentsResult<T, IncludeRest>;
60
+ for (const name of allowedMap.keys()) {
61
+ (result as any)[name] = [];
62
+ }
63
+
64
+ Children.forEach(children, child => {
65
+ if (!isValidElement(child)) {
66
+ if (includeRest) (result as any).rest.push(child);
67
+ return;
68
+ }
69
+
70
+ const displayName = (child.type as FC)?.displayName as T | undefined;
71
+ if (!displayName) {
72
+ if (includeRest) (result as any).rest.push(child);
73
+ return;
74
+ }
75
+
76
+ const allowMultiple = allowedMap.get(displayName);
77
+ if (allowMultiple !== undefined) {
78
+ if (allowMultiple) {
79
+ (result as any)[displayName].push(child);
80
+ } else {
81
+ (result as any)[displayName] = [child];
82
+ }
83
+ } else if (includeRest) {
84
+ (result as any).rest.push(child);
85
+ }
86
+ });
87
+
88
+ return result;
89
+ }
@@ -1,91 +0,0 @@
1
- import type { ReactElement, ReactNode } from 'react';
2
- import { Children, type FC, isValidElement, useMemo } from 'react';
3
-
4
- export type UseSubcomponentsProps<T extends string> = {
5
- children: ReactNode;
6
- /**
7
- * array of displayName as string
8
- * */
9
- allowedChildren: (T | { name: T; allowMultiple?: boolean })[];
10
- /**
11
- * If true, also returns the remaining children that don't match any of the allowedChildren
12
- * in a `rest` property
13
- */
14
- includeRest?: boolean;
15
- };
16
-
17
- export type UseSubcomponentsResult<T extends string, IncludeRest extends boolean = false> = {
18
- [key in T]: ReactElement[];
19
- } & (IncludeRest extends true ? { rest: ReactNode[] } : {});
20
-
21
- /**
22
- * This will return an object with the displayNames as the property names
23
- * eg. allowedChildren: ['Drawer_Header', 'Drawer_Content', 'Drawer_Footer', 'DrawerItem'];
24
- *
25
- * return value -> {
26
- * Drawer_Header: [],
27
- * Drawer_Content: [],
28
- * Drawer_Footer: [],
29
- * DrawerItem: [],
30
- * }
31
- *
32
- * If includeRest is true, also returns:
33
- * {
34
- * ...above,
35
- * rest: [remaining children that don't match allowedChildren]
36
- * }
37
- * */
38
- function useSubcomponents<T extends string = string, IncludeRest extends boolean = false>({
39
- children,
40
- allowedChildren,
41
- includeRest,
42
- }: UseSubcomponentsProps<T> & { includeRest?: IncludeRest }): UseSubcomponentsResult<
43
- T,
44
- IncludeRest
45
- > {
46
- return useMemo(() => {
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
- );
52
-
53
- const nameSet = new Set(configs.map(c => c.name));
54
- const allowMultipleMap = new Map(configs.map(c => [c.name, c.allowMultiple]));
55
-
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 => {
62
- if (!isValidElement(child)) {
63
- if (includeRest) {
64
- (result as any).rest.push(child);
65
- }
66
- return;
67
- }
68
-
69
- const displayName = (child.type as FC)?.displayName as T | undefined;
70
-
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];
77
- }
78
-
79
- return;
80
- }
81
-
82
- if (includeRest) {
83
- (result as any).rest.push(child);
84
- }
85
- });
86
-
87
- return result;
88
- }, [allowedChildren, children, includeRest]);
89
- }
90
-
91
- export default useSubcomponents;