Skip to content

Commit 01399d7

Browse files
authored
feat(components): add direct manipulation methods to input components
1 parent 1aa3864 commit 01399d7

File tree

6 files changed

+293
-38
lines changed

6 files changed

+293
-38
lines changed

src/components/ui/autocomplete/autocomplete.component.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from 'react-native';
1313
import {
1414
Input,
15+
InputComponent,
1516
InputElement,
1617
InputProps,
1718
} from '../input/input.component';
@@ -54,6 +55,15 @@ interface State {
5455
*
5556
* @extends React.Component
5657
*
58+
* @method {() => void} focus - Focuses Autocomplete and sets data list visible.
59+
*
60+
* @method {() => void} blur - Removes focus from Autocomplete and sets data list invisible.
61+
* This is the opposite of `focus()`.
62+
*
63+
* @method {() => boolean} isFocused - Returns true if the Autocomplete is currently focused and visible.
64+
*
65+
* @method {() => void} clear - Removes all text from the Autocomplete.
66+
*
5767
* @property {AutocompleteOption[]} data - Options displayed in component.
5868
* Each option can be any type, but should contain `title` property.
5969
*
@@ -87,11 +97,29 @@ export class Autocomplete<O extends Option = Option> extends React.Component<Aut
8797
optionsVisible: false,
8898
};
8999

100+
private inputRef: React.RefObject<InputComponent> = React.createRef();
101+
90102
private get data(): O[] {
91103
const hasData: boolean = this.props.data && this.props.data.length > 0;
92104
return hasData && this.props.data || this.props.placeholderData;
93105
}
94106

107+
public focus = (): void => {
108+
this.inputRef.current.focus();
109+
};
110+
111+
public blur = (): void => {
112+
this.inputRef.current.blur();
113+
};
114+
115+
public isFocused = (): boolean => {
116+
return this.inputRef.current.isFocused();
117+
};
118+
119+
public clear = (): void => {
120+
this.inputRef.current.clear();
121+
};
122+
95123
private onInputFocus = (e: InputFocusEvent): void => {
96124
this.setState({ optionsVisible: true });
97125

@@ -143,6 +171,7 @@ export class Autocomplete<O extends Option = Option> extends React.Component<Aut
143171
private renderInputElement = (props: Partial<InputProps>): InputElement => {
144172
return (
145173
<Input
174+
ref={this.inputRef}
146175
{...props}
147176
onFocus={this.onInputFocus}
148177
/>

src/components/ui/datepicker/baseDatepicker.component.tsx

Lines changed: 149 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ImageStyle,
66
StyleProp,
77
StyleSheet,
8+
TextStyle,
89
TouchableOpacity,
910
TouchableOpacityProps,
1011
View,
@@ -20,43 +21,70 @@ import {
2021
Text,
2122
TextElement,
2223
} from '../text/text.component';
24+
import { IconElement } from '../icon/icon.component';
2325
import { Popover } from '../popover/popover.component';
2426
import { BaseCalendarProps } from '../calendar/baseCalendar.component';
25-
import { NativeDateService } from '../calendar/service/nativeDate.service';
2627
import { CalendarElement } from '../calendar/calendar.component';
2728
import { RangeCalendarElement } from '../calendar/rangeCalendar.component';
28-
import { PopoverPlacements } from '../popover/type';
29+
import { NativeDateService } from '../calendar/service/nativeDate.service';
30+
import { isValidString } from '../support/services';
31+
import {
32+
PopoverPlacement,
33+
PopoverPlacements,
34+
} from '../popover/type';
35+
36+
type IconProp = (style: StyleType) => IconElement;
37+
2938

3039
export interface BaseDatepickerProps<D = Date> extends StyledComponentProps,
3140
TouchableOpacityProps,
3241
BaseCalendarProps<D> {
3342

3443
controlStyle?: StyleProp<ViewStyle>;
44+
label?: string;
45+
caption?: string;
46+
captionIcon?: IconProp;
3547
icon?: (style: ImageStyle) => React.ReactElement<ImageProps>;
3648
status?: string;
3749
size?: string;
3850
placeholder?: string;
51+
labelStyle?: StyleProp<TextStyle>;
52+
captionStyle?: StyleProp<TextStyle>;
53+
placement?: PopoverPlacement | string;
3954
}
4055

4156
interface State {
4257
visible: boolean;
4358
}
4459

45-
type DatepickerChildren<D = Date> = [CalendarElement<D>, React.ReactElement];
46-
4760
const FULL_DATE_FORMAT_STRING: string = 'DD/MM/YYYY';
4861

4962
export abstract class BaseDatepickerComponent<P, D = Date> extends React.Component<BaseDatepickerProps<D> & P, State> {
5063

5164
static defaultProps: Partial<BaseDatepickerProps> = {
5265
dateService: new NativeDateService(),
5366
placeholder: 'dd/mm/yyyy',
67+
placement: PopoverPlacements.BOTTOM_START,
5468
};
5569

5670
public state: State = {
5771
visible: false,
5872
};
5973

74+
public focus = (): void => {
75+
this.setState({ visible: true }, this.dispatchActive);
76+
};
77+
78+
public blur = (): void => {
79+
this.setState({ visible: true }, this.dispatchActive);
80+
};
81+
82+
public isFocused = (): boolean => {
83+
return this.state.visible;
84+
};
85+
86+
public abstract clear(): void;
87+
6088
protected abstract getComponentTitle(): string;
6189

6290
protected abstract renderCalendar(): CalendarElement<D> | RangeCalendarElement<D>;
@@ -67,40 +95,86 @@ export abstract class BaseDatepickerComponent<P, D = Date> extends React.Compone
6795

6896
private getComponentStyle = (style: StyleType): StyleType => {
6997
const {
98+
textMarginHorizontal,
99+
textFontFamily,
70100
textFontSize,
71101
textLineHeight,
72102
textFontWeight,
73-
textFontFamily,
74103
textColor,
104+
placeholderColor,
75105
iconWidth,
76106
iconHeight,
107+
iconMarginHorizontal,
77108
iconTintColor,
109+
labelColor,
110+
labelFontSize,
111+
labelLineHeight,
112+
labelMarginBottom,
113+
labelFontWeight,
114+
captionMarginTop,
115+
captionColor,
116+
captionFontSize,
117+
captionLineHeight,
118+
captionFontWeight,
119+
captionIconWidth,
120+
captionIconHeight,
121+
captionIconMarginRight,
122+
captionIconTintColor,
78123
popoverWidth,
79-
...containerStyles
124+
...controlParameters
80125
} = style;
81126

82127
return {
83-
control: containerStyles,
84-
icon: {
85-
width: iconWidth,
86-
height: iconHeight,
87-
tintColor: iconTintColor,
128+
control: controlParameters,
129+
captionContainer: {
130+
marginTop: captionMarginTop,
88131
},
89132
text: {
133+
marginHorizontal: textMarginHorizontal,
134+
fontFamily: textFontFamily,
90135
fontSize: textFontSize,
91-
lineHeight: textLineHeight,
92136
fontWeight: textFontWeight,
137+
lineHeight: textLineHeight,
93138
color: textColor,
94-
fontFamily: textFontFamily,
139+
},
140+
placeholder: {
141+
marginHorizontal: textMarginHorizontal,
142+
color: placeholderColor,
143+
},
144+
icon: {
145+
width: iconWidth,
146+
height: iconHeight,
147+
marginHorizontal: iconMarginHorizontal,
148+
tintColor: iconTintColor,
149+
},
150+
label: {
151+
color: labelColor,
152+
fontSize: labelFontSize,
153+
lineHeight: labelLineHeight,
154+
marginBottom: labelMarginBottom,
155+
fontWeight: labelFontWeight,
156+
},
157+
captionIcon: {
158+
width: captionIconWidth,
159+
height: captionIconHeight,
160+
tintColor: captionIconTintColor,
161+
marginRight: captionIconMarginRight,
162+
},
163+
captionLabel: {
164+
fontSize: captionFontSize,
165+
fontWeight: captionFontWeight,
166+
lineHeight: captionLineHeight,
167+
color: captionColor,
95168
},
96169
popover: {
97170
width: popoverWidth,
171+
marginBottom: captionMarginTop,
98172
},
99173
};
100174
};
101175

102176
private onPress = (event: GestureResponderEvent): void => {
103-
this.setVisibility();
177+
this.toggleVisibility();
104178

105179
if (this.props.onPress) {
106180
this.props.onPress(event);
@@ -123,7 +197,7 @@ export abstract class BaseDatepickerComponent<P, D = Date> extends React.Compone
123197
}
124198
};
125199

126-
private setVisibility = (): void => {
200+
private toggleVisibility = (): void => {
127201
const visible: boolean = !this.state.visible;
128202
this.setState({ visible }, this.dispatchActive);
129203
};
@@ -144,6 +218,35 @@ export abstract class BaseDatepickerComponent<P, D = Date> extends React.Compone
144218
});
145219
};
146220

221+
private renderLabelElement = (style: TextStyle): TextElement => {
222+
return (
223+
<Text
224+
key={1}
225+
style={[style, styles.label, this.props.labelStyle]}>
226+
{this.props.label}
227+
</Text>
228+
);
229+
};
230+
231+
private renderCaptionElement = (style: TextStyle): TextElement => {
232+
return (
233+
<Text
234+
key={2}
235+
style={[style, styles.captionLabel, this.props.captionStyle]}>
236+
{this.props.caption}
237+
</Text>
238+
);
239+
};
240+
241+
private renderCaptionIconElement = (style: ImageStyle): IconElement => {
242+
const iconElement: IconElement = this.props.captionIcon(style);
243+
244+
return React.cloneElement(iconElement, {
245+
key: 3,
246+
style: [style, iconElement.props.style],
247+
});
248+
};
249+
147250
private renderTextElement = (style: StyleType): TextElement => {
148251
return (
149252
<Text
@@ -180,42 +283,66 @@ export abstract class BaseDatepickerComponent<P, D = Date> extends React.Compone
180283
);
181284
};
182285

183-
private renderComponentChildren = (style: StyleType): DatepickerChildren<D> => {
286+
private renderComponentChildren = (style: StyleType): React.ReactElement[] => {
184287
return [
185288
this.renderCalendar(),
289+
isValidString(this.props.label) && this.renderLabelElement(style.label),
186290
this.renderControlElement(style),
291+
isValidString(this.props.caption) && this.renderCaptionElement(style.captionLabel),
292+
this.props.captionIcon && this.renderCaptionIconElement(style.captionIcon),
187293
];
188294
};
189295

190296
public render(): React.ReactElement<ViewProps> {
191-
const { themedStyle, style } = this.props;
297+
const { themedStyle, style, placement } = this.props;
192298
const { popover, ...componentStyle }: StyleType = this.getComponentStyle(themedStyle);
193299

194-
const [calendarElement, controlElement] = this.renderComponentChildren(componentStyle);
300+
const [
301+
calendarElement,
302+
labelElement,
303+
controlElement,
304+
captionElement,
305+
captionIconElement,
306+
] = this.renderComponentChildren(componentStyle);
195307

196308
return (
197309
<View style={style}>
310+
{labelElement}
198311
<Popover
199312
style={[popover, styles.popover]}
200-
placement={PopoverPlacements.BOTTOM_START}
313+
placement={placement}
201314
visible={this.state.visible}
202315
content={calendarElement}
203-
onBackdropPress={this.setVisibility}>
316+
onBackdropPress={this.toggleVisibility}>
204317
{controlElement}
205318
</Popover>
319+
<View style={[componentStyle.captionContainer, styles.captionContainer]}>
320+
{captionIconElement}
321+
{captionElement}
322+
</View>
206323
</View>
207324
);
208325
}
209326
}
210327

211328
const styles = StyleSheet.create({
329+
popover: {
330+
borderWidth: 0,
331+
},
212332
control: {
213333
flexDirection: 'row',
214334
alignItems: 'center',
215335
justifyContent: 'space-between',
216336
},
217-
popover: {
218-
borderWidth: 0,
337+
label: {
338+
textAlign: 'left',
339+
},
340+
captionContainer: {
341+
flexDirection: 'row',
342+
alignItems: 'center',
343+
},
344+
captionLabel: {
345+
textAlign: 'left',
219346
},
220347
});
221348

0 commit comments

Comments
 (0)