Skip to content

Commit

Permalink
feat(components): add direct manipulation methods to input components
Browse files Browse the repository at this point in the history
  • Loading branch information
artyorsh authored Jan 14, 2020
1 parent 1aa3864 commit 01399d7
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 38 deletions.
29 changes: 29 additions & 0 deletions src/components/ui/autocomplete/autocomplete.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from 'react-native';
import {
Input,
InputComponent,
InputElement,
InputProps,
} from '../input/input.component';
Expand Down Expand Up @@ -54,6 +55,15 @@ interface State {
*
* @extends React.Component
*
* @method {() => void} focus - Focuses Autocomplete and sets data list visible.
*
* @method {() => void} blur - Removes focus from Autocomplete and sets data list invisible.
* This is the opposite of `focus()`.
*
* @method {() => boolean} isFocused - Returns true if the Autocomplete is currently focused and visible.
*
* @method {() => void} clear - Removes all text from the Autocomplete.
*
* @property {AutocompleteOption[]} data - Options displayed in component.
* Each option can be any type, but should contain `title` property.
*
Expand Down Expand Up @@ -87,11 +97,29 @@ export class Autocomplete<O extends Option = Option> extends React.Component<Aut
optionsVisible: false,
};

private inputRef: React.RefObject<InputComponent> = React.createRef();

private get data(): O[] {
const hasData: boolean = this.props.data && this.props.data.length > 0;
return hasData && this.props.data || this.props.placeholderData;
}

public focus = (): void => {
this.inputRef.current.focus();
};

public blur = (): void => {
this.inputRef.current.blur();
};

public isFocused = (): boolean => {
return this.inputRef.current.isFocused();
};

public clear = (): void => {
this.inputRef.current.clear();
};

private onInputFocus = (e: InputFocusEvent): void => {
this.setState({ optionsVisible: true });

Expand Down Expand Up @@ -143,6 +171,7 @@ export class Autocomplete<O extends Option = Option> extends React.Component<Aut
private renderInputElement = (props: Partial<InputProps>): InputElement => {
return (
<Input
ref={this.inputRef}
{...props}
onFocus={this.onInputFocus}
/>
Expand Down
171 changes: 149 additions & 22 deletions src/components/ui/datepicker/baseDatepicker.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ImageStyle,
StyleProp,
StyleSheet,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
Expand All @@ -20,43 +21,70 @@ import {
Text,
TextElement,
} from '../text/text.component';
import { IconElement } from '../icon/icon.component';
import { Popover } from '../popover/popover.component';
import { BaseCalendarProps } from '../calendar/baseCalendar.component';
import { NativeDateService } from '../calendar/service/nativeDate.service';
import { CalendarElement } from '../calendar/calendar.component';
import { RangeCalendarElement } from '../calendar/rangeCalendar.component';
import { PopoverPlacements } from '../popover/type';
import { NativeDateService } from '../calendar/service/nativeDate.service';
import { isValidString } from '../support/services';
import {
PopoverPlacement,
PopoverPlacements,
} from '../popover/type';

type IconProp = (style: StyleType) => IconElement;


export interface BaseDatepickerProps<D = Date> extends StyledComponentProps,
TouchableOpacityProps,
BaseCalendarProps<D> {

controlStyle?: StyleProp<ViewStyle>;
label?: string;
caption?: string;
captionIcon?: IconProp;
icon?: (style: ImageStyle) => React.ReactElement<ImageProps>;
status?: string;
size?: string;
placeholder?: string;
labelStyle?: StyleProp<TextStyle>;
captionStyle?: StyleProp<TextStyle>;
placement?: PopoverPlacement | string;
}

interface State {
visible: boolean;
}

type DatepickerChildren<D = Date> = [CalendarElement<D>, React.ReactElement];

const FULL_DATE_FORMAT_STRING: string = 'DD/MM/YYYY';

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

static defaultProps: Partial<BaseDatepickerProps> = {
dateService: new NativeDateService(),
placeholder: 'dd/mm/yyyy',
placement: PopoverPlacements.BOTTOM_START,
};

public state: State = {
visible: false,
};

public focus = (): void => {
this.setState({ visible: true }, this.dispatchActive);
};

public blur = (): void => {
this.setState({ visible: true }, this.dispatchActive);
};

public isFocused = (): boolean => {
return this.state.visible;
};

public abstract clear(): void;

protected abstract getComponentTitle(): string;

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

private getComponentStyle = (style: StyleType): StyleType => {
const {
textMarginHorizontal,
textFontFamily,
textFontSize,
textLineHeight,
textFontWeight,
textFontFamily,
textColor,
placeholderColor,
iconWidth,
iconHeight,
iconMarginHorizontal,
iconTintColor,
labelColor,
labelFontSize,
labelLineHeight,
labelMarginBottom,
labelFontWeight,
captionMarginTop,
captionColor,
captionFontSize,
captionLineHeight,
captionFontWeight,
captionIconWidth,
captionIconHeight,
captionIconMarginRight,
captionIconTintColor,
popoverWidth,
...containerStyles
...controlParameters
} = style;

return {
control: containerStyles,
icon: {
width: iconWidth,
height: iconHeight,
tintColor: iconTintColor,
control: controlParameters,
captionContainer: {
marginTop: captionMarginTop,
},
text: {
marginHorizontal: textMarginHorizontal,
fontFamily: textFontFamily,
fontSize: textFontSize,
lineHeight: textLineHeight,
fontWeight: textFontWeight,
lineHeight: textLineHeight,
color: textColor,
fontFamily: textFontFamily,
},
placeholder: {
marginHorizontal: textMarginHorizontal,
color: placeholderColor,
},
icon: {
width: iconWidth,
height: iconHeight,
marginHorizontal: iconMarginHorizontal,
tintColor: iconTintColor,
},
label: {
color: labelColor,
fontSize: labelFontSize,
lineHeight: labelLineHeight,
marginBottom: labelMarginBottom,
fontWeight: labelFontWeight,
},
captionIcon: {
width: captionIconWidth,
height: captionIconHeight,
tintColor: captionIconTintColor,
marginRight: captionIconMarginRight,
},
captionLabel: {
fontSize: captionFontSize,
fontWeight: captionFontWeight,
lineHeight: captionLineHeight,
color: captionColor,
},
popover: {
width: popoverWidth,
marginBottom: captionMarginTop,
},
};
};

private onPress = (event: GestureResponderEvent): void => {
this.setVisibility();
this.toggleVisibility();

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

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

private renderLabelElement = (style: TextStyle): TextElement => {
return (
<Text
key={1}
style={[style, styles.label, this.props.labelStyle]}>
{this.props.label}
</Text>
);
};

private renderCaptionElement = (style: TextStyle): TextElement => {
return (
<Text
key={2}
style={[style, styles.captionLabel, this.props.captionStyle]}>
{this.props.caption}
</Text>
);
};

private renderCaptionIconElement = (style: ImageStyle): IconElement => {
const iconElement: IconElement = this.props.captionIcon(style);

return React.cloneElement(iconElement, {
key: 3,
style: [style, iconElement.props.style],
});
};

private renderTextElement = (style: StyleType): TextElement => {
return (
<Text
Expand Down Expand Up @@ -180,42 +283,66 @@ export abstract class BaseDatepickerComponent<P, D = Date> extends React.Compone
);
};

private renderComponentChildren = (style: StyleType): DatepickerChildren<D> => {
private renderComponentChildren = (style: StyleType): React.ReactElement[] => {
return [
this.renderCalendar(),
isValidString(this.props.label) && this.renderLabelElement(style.label),
this.renderControlElement(style),
isValidString(this.props.caption) && this.renderCaptionElement(style.captionLabel),
this.props.captionIcon && this.renderCaptionIconElement(style.captionIcon),
];
};

public render(): React.ReactElement<ViewProps> {
const { themedStyle, style } = this.props;
const { themedStyle, style, placement } = this.props;
const { popover, ...componentStyle }: StyleType = this.getComponentStyle(themedStyle);

const [calendarElement, controlElement] = this.renderComponentChildren(componentStyle);
const [
calendarElement,
labelElement,
controlElement,
captionElement,
captionIconElement,
] = this.renderComponentChildren(componentStyle);

return (
<View style={style}>
{labelElement}
<Popover
style={[popover, styles.popover]}
placement={PopoverPlacements.BOTTOM_START}
placement={placement}
visible={this.state.visible}
content={calendarElement}
onBackdropPress={this.setVisibility}>
onBackdropPress={this.toggleVisibility}>
{controlElement}
</Popover>
<View style={[componentStyle.captionContainer, styles.captionContainer]}>
{captionIconElement}
{captionElement}
</View>
</View>
);
}
}

const styles = StyleSheet.create({
popover: {
borderWidth: 0,
},
control: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
popover: {
borderWidth: 0,
label: {
textAlign: 'left',
},
captionContainer: {
flexDirection: 'row',
alignItems: 'center',
},
captionLabel: {
textAlign: 'left',
},
});

Loading

0 comments on commit 01399d7

Please sign in to comment.