Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): overflow-menu #306

Merged
merged 9 commits into from
Mar 14, 2019
4 changes: 2 additions & 2 deletions src/framework/theme/service/style/style.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import { getThemeValue } from '../theme';

export function createThemedStyle(mapping: ThemedStyleType, theme: ThemeType): StyleType {
return Object.keys(mapping).reduce((acc: StyleType, current: string): StyleType => {
return mapping ? Object.keys(mapping).reduce((acc: StyleType, current: string): StyleType => {
32penkin marked this conversation as resolved.
Show resolved Hide resolved
const mappingValue: any = mapping[current];

if (mappingValue instanceof Object) {
Expand All @@ -16,5 +16,5 @@ export function createThemedStyle(mapping: ThemedStyleType, theme: ThemeType): S
}

return acc;
}, {});
}, {}) : {};
32penkin marked this conversation as resolved.
Show resolved Hide resolved
}
16 changes: 16 additions & 0 deletions src/framework/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ import {
Placement as PopoverPlacement,
Placements as PopoverPlacements,
} from './popover/type';
import {
OverflowMenuItem as OverflowMenuItemComponent,
Props as OverflowMenuItemProps,
OverflowMenuItemType,
} from './overflowMenu/overflowMenuItem.component';
import {
OverflowMenu as OverflowMenuComponent,
Props as OverflowMenuProps,
} from './overflowMenu/overflowMenu.component';

const Button = styled<ButtonComponent, ButtonProps>(ButtonComponent);
const ButtonGroup = styled<ButtonGroupComponent, ButtonGroupProps>(ButtonGroupComponent);
Expand All @@ -107,6 +116,8 @@ const TopNavigationBar = styled<TopNavigationBarComponent, TopNavigationBarProps
const TopNavigationBarAction =
styled<TopNavigationBarActionComponent, TopNavigationBarActionProps>(TopNavigationBarActionComponent);
const Modal = styled<ModalComponent, ModalProps>(ModalComponent);
const OverflowMenuItem = styled<OverflowMenuItemComponent, OverflowMenuItemProps>(OverflowMenuItemComponent);
const OverflowMenu = styled<OverflowMenuComponent, OverflowMenuProps>(OverflowMenuComponent);

export {
Button,
Expand Down Expand Up @@ -152,4 +163,9 @@ export {
ButtonAlignments,
PopoverPlacement,
PopoverPlacements,
OverflowMenuItem,
OverflowMenuItemProps,
OverflowMenuItemType,
OverflowMenu,
OverflowMenuProps,
};
5 changes: 4 additions & 1 deletion src/framework/ui/modal/modal.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class Modal extends React.Component<Props> {
const dialog: React.ReactElement<ViewProps> =
<Animated.View
{...derivedProps}
style={animationStyle}
style={[animationStyle, styles.animatedWrapper]}
onStartShouldSetResponder={this.onStartShouldSetResponder}
onResponderRelease={this.onResponderRelease}
onStartShouldSetResponderCapture={this.onStartShouldSetResponderCapture}
Expand All @@ -177,4 +177,7 @@ const styles = StyleSheet.create({
width: 0,
height: 0,
},
animatedWrapper: {
alignSelf: 'flex-start',
},
});
38 changes: 32 additions & 6 deletions src/framework/ui/modal/modal.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ exports[`@modal component checks * component styled with mappings 1`] = `
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
pointerEvents="box-none"
style={Object {}}
style={
Object {
"alignSelf": "flex-start",
}
}
theme={
Object {
"blue-dark": "#2541CC",
Expand Down Expand Up @@ -93,7 +97,11 @@ exports[`@modal component checks * modal closes on passed prop 1`] = `
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
pointerEvents="box-none"
style={Object {}}
style={
Object {
"alignSelf": "flex-start",
}
}
visible={true}
>
<View
Expand Down Expand Up @@ -191,7 +199,11 @@ exports[`@modal component checks * modal closes on passed prop 2`] = `
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
pointerEvents="box-none"
style={Object {}}
style={
Object {
"alignSelf": "flex-start",
}
}
visible={true}
>
<View
Expand Down Expand Up @@ -275,7 +287,11 @@ exports[`@modal component checks * modal component close on backDrop checks 1`]
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
pointerEvents="box-none"
style={Object {}}
style={
Object {
"alignSelf": "flex-start",
}
}
visible={true}
>
<View
Expand Down Expand Up @@ -333,7 +349,11 @@ exports[`@modal component checks * modal component close on backDrop checks 2`]
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
pointerEvents="box-none"
style={Object {}}
style={
Object {
"alignSelf": "flex-start",
}
}
visible={true}
>
<View
Expand Down Expand Up @@ -378,7 +398,11 @@ exports[`@modal component checks * modal component renders properly 1`] = `
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
pointerEvents="box-none"
style={Object {}}
style={
Object {
"alignSelf": "flex-start",
}
}
visible={true}
>
<View
Expand Down Expand Up @@ -468,6 +492,7 @@ exports[`@modal component checks * with animations 1`] = `
pointerEvents="box-none"
style={
Object {
"alignSelf": "flex-start",
"transform": Array [
Object {
"translateY": 1334,
Expand Down Expand Up @@ -564,6 +589,7 @@ exports[`@modal component checks * with animations 2`] = `
pointerEvents="box-none"
style={
Object {
"alignSelf": "flex-start",
"opacity": 0,
}
}
Expand Down
132 changes: 132 additions & 0 deletions src/framework/ui/overflowMenu/overflowMenu.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React from 'react';
import {
ViewProps,
View,
} from 'react-native';
import {
StyledComponentProps,
StyleType,
styled,
} from '@kitten/theme';
import {
OverflowMenuItemType,
OverflowMenuItem as OverflowMenuItemComponent,
Props as OverflowMenuItemProps,
} from './overflowMenuItem.component';
import {
Popover as PopoverComponent,
Props as PopoverProps,
} from '../popover/popover.component';
import { Omit } from '../service/type';

type MenuItemElement = React.ReactElement<OverflowMenuItemProps>;
export type MenuItemType = Omit<OverflowMenuItemType, 'size'>;
32penkin marked this conversation as resolved.
Show resolved Hide resolved

interface OverflowMenuProps {
children: React.ReactElement<any>;
items: MenuItemType[];
size?: string;
onSelect?: (index: number) => void;
}

const Popover = styled<PopoverComponent, PopoverProps>(PopoverComponent);
const OverflowMenuItem =
styled<OverflowMenuItemComponent, OverflowMenuItemProps>(OverflowMenuItemComponent);

export type Props = & StyledComponentProps & OverflowMenuProps & Omit<PopoverProps, 'content'>;

export class OverflowMenu extends React.Component<Props> {

static defaultProps: Partial<Props> = {
32penkin marked this conversation as resolved.
Show resolved Hide resolved
size: 'medium',
};

private onSelect = (index: number): void => {
if (this.props.onSelect && this.props.visible) {
32penkin marked this conversation as resolved.
Show resolved Hide resolved
this.props.onSelect(index);
}
};

private isFirstItem = (index: number): boolean => {
return index === 0;
};

private isLastItem = (index: number): boolean => {
return this.props.items.length - 1 === index;
};

private isSingleItem = (): boolean => {
return this.props.items.length === 1;
};

private getPopoverStyle = (style: StyleType): StyleType => ({
...style.popover,
borderRadius: style.borderRadius,
});

private getMenuItemStyle = (style: StyleType, index: number): StyleType => {
const borderRadius: number = style.menuItem.borderRadius;

if (this.isFirstItem(index) && !this.isSingleItem()) {
return {
borderTopLeftRadius: borderRadius,
borderTopRightRadius: borderRadius,
};
} else if (this.isLastItem(index) && !this.isSingleItem()) {
return {
borderBottomLeftRadius: borderRadius,
borderBottomRightRadius: borderRadius,
};
} else if (this.isSingleItem()) {
return {
borderRadius: borderRadius,
};
} else {
32penkin marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
};

private renderMenuItem = (item: MenuItemType, index: number): MenuItemElement => {
const { size, themedStyle } = this.props;
const itemStyle: StyleType = this.getMenuItemStyle(themedStyle, index);

return (
<OverflowMenuItem
{...item}
size={size}
isLastItem={this.isLastItem(index)}
style={itemStyle}
key={index}
onPress={() => this.onSelect(index)}
32penkin marked this conversation as resolved.
Show resolved Hide resolved
/>
);
};

private renderMenuContent = (): React.ReactElement<ViewProps> => {
const { items, style } = this.props;
const menuItems: MenuItemElement[] = items
32penkin marked this conversation as resolved.
Show resolved Hide resolved
.map((item: MenuItemType, index: number) => this.renderMenuItem(item, index));

return (
<View style={style}>
{menuItems}
</View>
);
};

public render(): React.ReactNode {
const { children, themedStyle, ...restProps } = this.props;
32penkin marked this conversation as resolved.
Show resolved Hide resolved
const menu: React.ReactElement<ViewProps> = this.renderMenuContent();
const popoverStyle: StyleType = this.getPopoverStyle(themedStyle);

return (
<Popover
{...restProps}
indicatorOffset={2}
content={menu}
style={[popoverStyle, restProps.style]}>
{children}
</Popover>
);
}
}
Loading