Skip to content

Commit 0bab50b

Browse files
authored
Create a new component - ExpandableInput (#1484)
* Create a new component - ExpandableInput * add generatedTypes * PR review fixes * Rename ExpandableInput to ExpanableOverlay
1 parent 04386f7 commit 0bab50b

File tree

9 files changed

+293
-2
lines changed

9 files changed

+293
-2
lines changed

demo/src/screens/MenuStructure.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export const navigationData = {
157157
screens: [
158158
{title: 'Native TouchableOpacity', tags: 'touchable native', screen: 'unicorn.incubator.TouchableOpacityScreen'},
159159
{title: '(New) TextField', tags: 'text field input', screen: 'unicorn.components.IncubatorTextFieldScreen'},
160+
{title: 'ExpandableOverlay', tags: 'text field expandable input picker', screen: 'unicorn.components.IncubatorExpandableOverlayScreen'},
160161
{title: 'WheelPicker (Incubator)', tags: 'wheel picker spinner experimental', screen: 'unicorn.incubator.WheelPickerScreen'},
161162
{title: 'Pan View', tags: 'pan swipe drag', screen: 'unicorn.incubator.PanViewScreen'}
162163
]
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import React, {Component} from 'react';
2+
import {StyleSheet, ScrollView} from 'react-native';
3+
import {Colors, View, Text, Incubator, TouchableOpacity} from 'react-native-ui-lib'; //eslint-disable-line
4+
import _ from 'lodash';
5+
6+
const COLOR_OPTIONS: {[key: string]: string} = {
7+
red: Colors.red30,
8+
green: Colors.green30,
9+
yellow: Colors.yellow30,
10+
blue: Colors.blue30
11+
};
12+
13+
export default class TextFieldScreen extends Component {
14+
expandableInputRef = React.createRef();
15+
expandablePickerRef = React.createRef();
16+
17+
state = {
18+
textFieldValueDraft: '',
19+
textFieldValue: '',
20+
selectedColor: 'red'
21+
};
22+
23+
updateText = (value: string) => this.setState({textFieldValueDraft: value});
24+
25+
onDone = () => {
26+
this.setState({textFieldValue: this.state.textFieldValueDraft});
27+
this.expandableInputRef.current.closeExpandable();
28+
};
29+
onCancel = () => {
30+
this.setState({textFieldValueDraft: this.state.textFieldValue});
31+
this.expandableInputRef.current.closeExpandable();
32+
};
33+
34+
onPickItem = ({customValue: color}: {customValue: string}) => {
35+
this.setState({selectedColor: color});
36+
this.expandablePickerRef.current.closeExpandable();
37+
};
38+
39+
renderInputModal = () => {
40+
const {textFieldValueDraft} = this.state;
41+
return (
42+
<>
43+
<View bg-white br20 padding-s4>
44+
<Incubator.TextField
45+
autoFocus
46+
preset={null}
47+
value={textFieldValueDraft}
48+
multiline
49+
placeholder="Enter text"
50+
containerStyle={{minHeight: 300}}
51+
onChangeText={this.updateText}
52+
/>
53+
</View>
54+
</>
55+
);
56+
};
57+
58+
renderColorRow = (colorKey: string) => {
59+
return (
60+
<View row centerV height={48}>
61+
<View width={20} height={20} br100 backgroundColor={COLOR_OPTIONS[colorKey]}/>
62+
<Text marginL-s2 body style={styles.colorRowText}>
63+
{colorKey}
64+
</Text>
65+
</View>
66+
);
67+
};
68+
69+
renderPickerContent = () => {
70+
return (
71+
<View bg-white br20 padding-s3 paddingB-60>
72+
{_.map(COLOR_OPTIONS, (_color, key) => {
73+
return (
74+
<TouchableOpacity key={key} customValue={key} onPress={this.onPickItem}>
75+
{this.renderColorRow(key)}
76+
</TouchableOpacity>
77+
);
78+
})}
79+
</View>
80+
);
81+
};
82+
83+
renderExpandableFieldExample() {
84+
const {textFieldValue} = this.state;
85+
return (
86+
<>
87+
<Text h3 marginB-s4 primary>
88+
Expandable TextField
89+
</Text>
90+
<Incubator.ExpandableOverlay
91+
ref={this.expandableInputRef}
92+
modalProps={{animationType: 'slide'}}
93+
expandableContent={this.renderInputModal()}
94+
showTopBar
95+
topBarProps={{title: 'Edit Input', doneLabel: 'Done', onCancel: this.onCancel, onDone: this.onDone}}
96+
dialogProps={{bottom: true}}
97+
>
98+
<Incubator.TextField placeholder="Expandable input" value={textFieldValue}/>
99+
</Incubator.ExpandableOverlay>
100+
</>
101+
);
102+
}
103+
104+
renderExpandablePickerExample() {
105+
const {selectedColor} = this.state;
106+
return (
107+
<>
108+
<Text h3 marginB-s4 primary>
109+
Expandable Picker
110+
</Text>
111+
<Incubator.ExpandableOverlay
112+
ref={this.expandablePickerRef}
113+
useDialog
114+
expandableContent={this.renderPickerContent()}
115+
dialogProps={{bottom: true}}
116+
>
117+
{this.renderColorRow(selectedColor)}
118+
</Incubator.ExpandableOverlay>
119+
</>
120+
);
121+
}
122+
123+
render() {
124+
return (
125+
<ScrollView keyboardShouldPersistTaps="always">
126+
<View padding-page>
127+
<Text h2 marginB-s5>
128+
ExpandableOverlay
129+
</Text>
130+
131+
{this.renderExpandableFieldExample()}
132+
{this.renderExpandablePickerExample()}
133+
</View>
134+
</ScrollView>
135+
);
136+
}
137+
}
138+
139+
const styles = StyleSheet.create({
140+
colorRowText: {
141+
textTransform: 'capitalize'
142+
}
143+
});

demo/src/screens/incubatorScreens/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {gestureHandlerRootHOC} from 'react-native-gesture-handler';
33
export function registerScreens(registrar) {
44
registrar('unicorn.incubator.TouchableOpacityScreen', () =>
55
gestureHandlerRootHOC(require('./TouchableOpacityScreen').default));
6+
registrar('unicorn.components.IncubatorExpandableOverlayScreen', () => require('./IncubatorExpandableOverlayScreen').default);
67
registrar('unicorn.components.IncubatorTextFieldScreen', () => require('./IncubatorTextFieldScreen').default);
78
registrar('unicorn.incubator.WheelPickerScreen', () => gestureHandlerRootHOC(require('./WheelPickerScreen').default));
89
registrar('unicorn.incubator.PanViewScreen', () => require('./PanViewScreen').default);

generatedTypes/src/incubator/TextField/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export declare type TextFieldProps = MarginModifiers & PaddingModifiers & Typogr
6868
/**
6969
* Predefined preset to use for styling the field
7070
*/
71-
preset?: 'default' | undefined;
71+
preset?: 'default' | null;
7272
};
7373
export declare type InternalTextFieldProps = TextFieldProps & BaseComponentInjectedProps & ForwardRefInjectedProps;
7474
interface StaticMembers {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { PropsWithChildren } from 'react';
2+
import { TouchableOpacityProps } from '../../components/touchableOpacity';
3+
import { ModalProps, ModalTopBarProps } from '../../components/modal';
4+
import { DialogProps } from '../../components/dialog';
5+
export declare type ExpandableOverlayProps = TouchableOpacityProps & PropsWithChildren<{
6+
/**
7+
* The content to render inside the expandable modal/dialog
8+
*/
9+
expandableContent?: React.ReactElement;
10+
/**
11+
* Whether to use a dialog as expandable container (by default the container will be a full screen modal)
12+
*/
13+
useDialog?: boolean;
14+
/**
15+
* The props to pass to the modal expandable container
16+
*/
17+
modalProps?: ModalProps;
18+
/**
19+
* The props to pass to the dialog expandable container
20+
*/
21+
dialogProps?: DialogProps;
22+
/**
23+
* Whether to render a modal top bar (relevant only for modal)
24+
*/
25+
showTopBar?: boolean;
26+
/**
27+
* The modal top bar props to pass on
28+
*/
29+
topBarProps?: ModalTopBarProps;
30+
}>;
31+
interface ExpandableOverlayMethods {
32+
openExpandable: () => void;
33+
closeExpandable: () => void;
34+
}
35+
declare const _default: React.ForwardRefExoticComponent<TouchableOpacityProps & {
36+
/**
37+
* The content to render inside the expandable modal/dialog
38+
*/
39+
expandableContent?: React.ReactElement<any, string | React.JSXElementConstructor<any>> | undefined;
40+
/**
41+
* Whether to use a dialog as expandable container (by default the container will be a full screen modal)
42+
*/
43+
useDialog?: boolean | undefined;
44+
/**
45+
* The props to pass to the modal expandable container
46+
*/
47+
modalProps?: ModalProps | undefined;
48+
/**
49+
* The props to pass to the dialog expandable container
50+
*/
51+
dialogProps?: DialogProps | undefined;
52+
/**
53+
* Whether to render a modal top bar (relevant only for modal)
54+
*/
55+
showTopBar?: boolean | undefined;
56+
/**
57+
* The modal top bar props to pass on
58+
*/
59+
topBarProps?: ModalTopBarProps | undefined;
60+
} & {
61+
children?: React.ReactNode;
62+
} & React.RefAttributes<ExpandableOverlayMethods>>;
63+
export default _default;

generatedTypes/src/incubator/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { default as ExpandableOverlay } from './expandableOverlay';
12
export { default as TabController } from './TabController';
23
export { default as TextField, TextFieldProps, FieldContextType } from './TextField';
34
export { default as TouchableOpacity, TouchableOpacityProps } from './TouchableOpacity';

src/incubator/TextField/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export type TextFieldProps = MarginModifiers &
9292
/**
9393
* Predefined preset to use for styling the field
9494
*/
95-
preset?: 'default' | undefined;
95+
preset?: 'default' | null;
9696
};
9797

9898
export type InternalTextFieldProps = TextFieldProps &
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, {useCallback, useState, forwardRef, PropsWithChildren, useImperativeHandle} from 'react';
2+
3+
import TouchableOpacity, {TouchableOpacityProps} from '../../components/touchableOpacity';
4+
import View from '../../components/view';
5+
import Modal, {ModalProps, ModalTopBarProps} from '../../components/modal';
6+
import Dialog, {DialogProps} from '../../components/dialog';
7+
8+
export type ExpandableOverlayProps = TouchableOpacityProps &
9+
PropsWithChildren<{
10+
/**
11+
* The content to render inside the expandable modal/dialog
12+
*/
13+
expandableContent?: React.ReactElement;
14+
/**
15+
* Whether to use a dialog as expandable container (by default the container will be a full screen modal)
16+
*/
17+
useDialog?: boolean;
18+
/**
19+
* The props to pass to the modal expandable container
20+
*/
21+
modalProps?: ModalProps;
22+
/**
23+
* The props to pass to the dialog expandable container
24+
*/
25+
dialogProps?: DialogProps;
26+
/**
27+
* Whether to render a modal top bar (relevant only for modal)
28+
*/
29+
showTopBar?: boolean;
30+
/**
31+
* The modal top bar props to pass on
32+
*/
33+
topBarProps?: ModalTopBarProps;
34+
}>;
35+
36+
interface ExpandableOverlayMethods {
37+
openExpandable: () => void;
38+
closeExpandable: () => void;
39+
}
40+
41+
const ExpandableOverlay = (props: ExpandableOverlayProps, ref: any) => {
42+
const {children, expandableContent, useDialog, modalProps, dialogProps, showTopBar, topBarProps, ...others} = props;
43+
const [expandableVisible, setExpandableVisible] = useState(false);
44+
const showExpandable = useCallback(() => setExpandableVisible(true), []);
45+
const hideExpandable = useCallback(() => setExpandableVisible(false), []);
46+
47+
useImperativeHandle(ref, () => ({
48+
openExpandable: () => {
49+
showExpandable();
50+
},
51+
closeExpandable: () => {
52+
hideExpandable();
53+
}
54+
}));
55+
56+
const renderModal = () => {
57+
return (
58+
<Modal {...modalProps} visible={expandableVisible} onDismiss={hideExpandable}>
59+
{showTopBar && <Modal.TopBar onDone={hideExpandable} {...topBarProps}/>}
60+
{expandableContent}
61+
</Modal>
62+
);
63+
};
64+
65+
const renderDialog = () => {
66+
return (
67+
<Dialog {...dialogProps} visible={expandableVisible} onDismiss={hideExpandable}>
68+
{expandableContent}
69+
</Dialog>
70+
);
71+
};
72+
73+
return (
74+
<TouchableOpacity {...others} onPress={showExpandable}>
75+
<View pointerEvents="none">{children}</View>
76+
{useDialog ? renderDialog() : renderModal()}
77+
</TouchableOpacity>
78+
);
79+
};
80+
81+
export default forwardRef<ExpandableOverlayMethods, ExpandableOverlayProps>(ExpandableOverlay);

src/incubator/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export {default as ExpandableOverlay} from './expandableOverlay';
12
// @ts-ignore
23
export {default as TabController} from './TabController';
34
export {default as TextField, TextFieldProps, FieldContextType} from './TextField';

0 commit comments

Comments
 (0)