Skip to content

Commit

Permalink
feat(components): add direct manipulation methods to modal components
Browse files Browse the repository at this point in the history
  • Loading branch information
artyorsh authored Jan 16, 2020
1 parent 231ba89 commit 8b2721a
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 33 deletions.
14 changes: 14 additions & 0 deletions src/components/ui/autocomplete/autocomplete.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ interface State {
*
* @extends React.Component
*
* @method {() => void} show - Sets data list visible.
*
* @method {() => void} hide - Sets data list invisible.
*
* @method {() => void} focus - Focuses Autocomplete and sets data list visible.
*
* @method {() => void} blur - Removes focus from Autocomplete and sets data list invisible.
Expand Down Expand Up @@ -97,13 +101,22 @@ export class Autocomplete<O extends Option = Option> extends React.Component<Aut
optionsVisible: false,
};

private popoverRef: React.RefObject<Popover> = React.createRef();
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 show = (): void => {
this.popoverRef.current.show();
};

public hide = (): void => {
this.popoverRef.current.hide();
};

public focus = (): void => {
this.inputRef.current.focus();
};
Expand Down Expand Up @@ -191,6 +204,7 @@ export class Autocomplete<O extends Option = Option> extends React.Component<Aut

return (
<Popover
ref={this.popoverRef}
style={styles.popover}
visible={this.state.optionsVisible}
fullWidth={true}
Expand Down
11 changes: 11 additions & 0 deletions src/components/ui/datepicker/baseDatepicker.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ export abstract class BaseDatepickerComponent<P, D = Date> extends React.Compone
visible: false,
};

private popoverRef: React.RefObject<Popover> = React.createRef();

public show = (): void => {
this.popoverRef.current.show();
};

public hide = (): void => {
this.popoverRef.current.hide();
};

public focus = (): void => {
this.setState({ visible: true }, this.dispatchActive);
};
Expand Down Expand Up @@ -309,6 +319,7 @@ export abstract class BaseDatepickerComponent<P, D = Date> extends React.Compone
<View style={style}>
{labelElement}
<Popover
ref={this.popoverRef}
style={[popover, styles.popover]}
placement={placement}
visible={this.state.visible}
Expand Down
4 changes: 4 additions & 0 deletions src/components/ui/datepicker/datepicker.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type DatepickerElement<D = Date> = React.ReactElement<DatepickerProps<D>>
*
* @extends React.Component
*
* @method {() => void} show - Sets picker visible.
*
* @method {() => void} hide - Sets picker invisible.
*
* @method {() => void} focus - Focuses Datepicker and sets it visible.
*
* @method {() => void} blur - Removes focus from Datepicker and sets it invisible. This is the opposite of `focus()`.
Expand Down
4 changes: 4 additions & 0 deletions src/components/ui/datepicker/rangeDatepicker.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type RangeDatepickerElement<D = Date> = React.ReactElement<RangeDatepicke
*
* @extends React.Component
*
* @method {() => void} show - Sets picker visible.
*
* @method {() => void} hide - Sets picker invisible.
*
* @method {() => void} focus - Focuses Datepicker and sets it visible.
*
* @method {() => void} blur - Removes focus from Datepicker and sets it invisible. This is the opposite of `focus()`.
Expand Down
84 changes: 59 additions & 25 deletions src/components/ui/modal/modal.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@ import {
ModalPresentingConfig,
ModalService,
} from '@kitten/theme';
import { MeasureElement } from '../measure/measure.component';
import {
MeasureElement,
MeasuringElement,
} from '../measure/measure.component';
import {
Frame,
Point,
} from '../measure/type';

export interface ModalProps extends ViewProps, ModalPresentingConfig {
visible?: boolean;
visible: boolean;
children: React.ReactNode;
}

export type ModalElement = React.ReactElement<ModalProps>;

interface State {
contentFrame: Frame;
forceMeasure: boolean;
}

const POINT_OUTSCREEN: Point = new Point(-999, -999);
Expand All @@ -40,6 +44,10 @@ const POINT_OUTSCREEN: Point = new Point(-999, -999);
*
* @extends React.Component
*
* @method {() => void} show - Sets modal visible.
*
* @method {() => void} hide - Sets modal invisible.
*
* @property {boolean} visible - Determines whether component is visible. By default is false.
*
* @property {ReactElement | ReactElement[]} children - Determines component's children.
Expand All @@ -55,69 +63,95 @@ const POINT_OUTSCREEN: Point = new Point(-999, -999);
*
* @overview-example ModalWithBackdrop
*/
export class Modal extends React.Component<ModalProps, State> {

private modalId: string;
export class Modal extends React.PureComponent<ModalProps, State> {

public state: State = {
contentFrame: Frame.zero(),
forceMeasure: false,
};

private modalId: string;
private contentPosition: Point = POINT_OUTSCREEN;

private get contentFlexPosition(): FlexStyle {
const derivedStyle: ViewStyle = StyleSheet.flatten(this.props.style || {});
const centerInWindow: Point = this.state.contentFrame.centerOf(Frame.window()).origin;
const { x: centerX, y: centerY } = this.contentPosition;
// @ts-ignore
return { left: derivedStyle.left || centerInWindow.x, top: derivedStyle.top || centerInWindow.y };
return { left: derivedStyle.left || centerX, top: derivedStyle.top || centerY };
}

private get backdropConfig(): ModalPresentingConfig {
const { onBackdropPress, backdropStyle } = this.props;
return { onBackdropPress, backdropStyle };
}

public componentDidUpdate(): void {
if (!this.modalId && this.props.visible) {
this.modalId = ModalService.show(this.renderModalElement(this.contentFlexPosition), this.backdropConfig);
public show = (): void => {
this.modalId = ModalService.show(this.renderMeasuringContentElement(), this.backdropConfig);
};

public hide = (): void => {
this.modalId = ModalService.hide(this.modalId);
};

public componentDidUpdate(prevProps: ModalProps): void {
if (!this.modalId && this.props.visible && !this.state.forceMeasure) {
this.setState({ forceMeasure: true });
return;
}

if (this.modalId && !this.props.visible) {
this.modalId = ModalService.hide(this.modalId);
// this.contentPosition = POINT_OUTSCREEN;
this.hide();
}
}

private onContentMeasure = (frame: Frame): void => {
this.state.contentFrame = frame;
public componentWillUnmount(): void {
this.hide();
}

if (!this.modalId && this.props.visible) {
this.modalId = ModalService.show(this.renderModalElement(this.contentFlexPosition), this.backdropConfig);
}
private onContentMeasure = (contentFrame: Frame): void => {
this.state.contentFrame = contentFrame;

const displayFrame: Frame = this.state.contentFrame.centerOf(Frame.window());
this.contentPosition = displayFrame.origin;

ModalService.update(this.modalId, this.renderContentElement());
};

private renderModalElement = (style: ViewStyle): React.ReactElement<ViewProps> => {
private renderContentElement = (): React.ReactElement<ViewProps> => {
return (
<View
{...this.props}
style={[this.props.style, styles.contentView, style]}
style={[this.props.style, styles.modalView, this.contentFlexPosition]}
/>
);
};

public render(): React.ReactElement {
private renderMeasuringContentElement = (): MeasuringElement => {
return (
<MeasureElement onMeasure={this.onContentMeasure}>
{this.renderModalElement(styles.outscreen)}
{this.renderContentElement()}
</MeasureElement>
);
};

public render(): React.ReactNode {
if (!this.modalId && this.props.visible) {
this.show();
return null;
}

if (this.modalId && this.props.visible) {
ModalService.update(this.modalId, this.renderContentElement());
return null;
}

return null;
}
}

const styles = StyleSheet.create({
contentView: {
modalView: {
position: 'absolute',
},
outscreen: {
left: POINT_OUTSCREEN.x,
top: POINT_OUTSCREEN.y,
},
});
15 changes: 15 additions & 0 deletions src/components/ui/overflowMenu/overflowMenu.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export type OverflowMenuElement = React.ReactElement<OverflowMenuProps>;
*
* @extends React.Component
*
* @method {() => void} show - Sets menu visible.
*
* @method {() => void} hide - Sets menu invisible.
*
* @property {boolean} visible - Determines whether popover is visible or not.
*
* @property {OverflowMenuItemType[]} data - Determines menu items.
Expand Down Expand Up @@ -82,6 +86,16 @@ class OverflowMenuComponent extends React.Component<OverflowMenuProps> {

static styledComponentName: string = 'OverflowMenu';

private popoverRef: React.RefObject<Popover> = React.createRef();

public show = (): void => {
this.popoverRef.current.show();
};

public hide = (): void => {
this.popoverRef.current.hide();
};

private getComponentStyle = (source: StyleType): StyleType => {
const { indicatorBackgroundColor, ...containerParameters } = source;

Expand Down Expand Up @@ -117,6 +131,7 @@ class OverflowMenuComponent extends React.Component<OverflowMenuProps> {
return (
<Popover
{...restProps}
ref={this.popoverRef}
style={[container, style]}
content={contentElement}>
{children}
Expand Down
26 changes: 20 additions & 6 deletions src/components/ui/popover/popover.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const POINT_OUTSCREEN: Point = new Point(-999, -999);
*
* @extends React.Component
*
* @method {() => void} show - Sets `content` element visible.
*
* @method {() => void} hide - Sets `content` element invisible.
*
* @property {boolean} visible - Determines whether popover is visible or not.
*
* @property {ReactElement} content - Determines the content of the popover.
Expand Down Expand Up @@ -112,6 +116,14 @@ export class Popover extends React.Component<PopoverProps, State> {
return { onBackdropPress, backdropStyle };
}

public show = (): void => {
this.modalId = ModalService.show(this.renderMeasuringPopoverElement(), this.backdropConfig);
};

public hide = (): void => {
this.modalId = ModalService.hide(this.modalId);
};

public componentDidUpdate(prevProps: PopoverProps): void {
if (!this.modalId && this.props.visible && !this.state.forceMeasure) {
this.setState({ forceMeasure: true });
Expand All @@ -120,15 +132,19 @@ export class Popover extends React.Component<PopoverProps, State> {

if (this.modalId && !this.props.visible) {
this.contentPosition = POINT_OUTSCREEN;
this.modalId = ModalService.hide(this.modalId);
this.hide();
}
}

public componentWillUnmount(): void {
this.hide();
}

private onChildMeasure = (childFrame: Frame): void => {
this.state.childFrame = childFrame;

if (!this.modalId && this.props.visible) {
this.modalId = ModalService.show(this.renderMeasuringPopoverElement(), this.backdropConfig);
this.show();
return;
}

Expand Down Expand Up @@ -166,12 +182,10 @@ export class Popover extends React.Component<PopoverProps, State> {
};

private renderPopoverElement = (): PopoverViewElement => {
const { contentContainerStyle, ...props } = this.props;

return (
<PopoverView
{...props}
contentContainerStyle={[contentContainerStyle, styles.popoverView, this.contentFlexPosition]}
{...this.props}
contentContainerStyle={[this.props.contentContainerStyle, styles.popoverView, this.contentFlexPosition]}
placement={this.actualPlacement.reverse()}>
{this.renderContentElement()}
</PopoverView>
Expand Down
Loading

0 comments on commit 8b2721a

Please sign in to comment.